Всем привет! В этом посте я расскажу о том, как доставлять информацию о результатах действий из Veeam Backup & Replication v9.5 в MS Teams. Описанный в посте способ будет работать не только в этом, но и в любом другом мессенджере — потребуется только организовать доставку сообщений, используя профильный API.



Для начала представим себе, чего хотим добиться. Мы хотим получать в один из наших командных каналов информацию о том, что происходит на одном из наших инстансов бэкапного софта. Функция полезная, пригодится и детям и взрослым, порадует Вашу вторую половинку и послужит отличным украшением праздничного стола. Другими словами, конкретный юзкейс вы можете придумать и сами. Я ни в коем случае не хочу ограничивать вашу фантазию — мы рассмотрим прототип.

Решать задачу будем самым простым способом из возможных: грабить данные из SQL базы данных Veeam, немного парсить и отправлять в канал посредством Incoming WebHook Connector. Вот план действий:

  1. Подключаемся к базе данных Veeam и получаем информацию из [dbo].[Backup.Model.JobSessions].
  2. Создаем Incoming WebHook Connector в том канале, куда мы хотим отправлять сообщения.
  3. Собираем все вместе с помощью питоновой магии, чтобы заработало.
  4. Заворачиваем полученный скетч в docker контейнер.
  5. It just works!

Подключаемся к БД Veeam Backup & Replication


Сначала создаем пользователя на стороне MS SQL инстанса. Рассматриваем именно MS SQL, поскольку это дефолтный вариант, предустанавливаемый нашим софтом. Даем пользователю самый минимум прав — только селектить и только из одной таблицы.

Сделать это просто. Залогиньтесь на инстанс любым возможным для Вас способом и зараньте следующий запрос:

CREATE LOGIN ms_teams_watcher
WITH PASSWORD = '123@qwe'
USE [VeeamBackup] 
CREATE USER ms_teams_watcher FOR LOGIN ms_teams_watcher
GRANT SELECT ON [dbo].[Backup.Model.JobSessions] TO ms_teams_watcher

Не забудьте подставить имя базы, придумать имя пользователя и пароль.

Если Вы все сделали правильно, то попытка провести адпейт… не пройдет:



А вот селект — пожалуйста:



Слава GDPR — в рамках этого семпла ни одна продакшн база не пострадала! Теперь у нас есть пользователь ms_teams_watcher и мы можем начать собирать данные. Подключаться будем через pyodbc — никакой SQLAlchemy, только хардкор!

Для начала откроем подключение (см. class SQLConnectorVeeamDB) и получим информацию о всех сессиях, что были закончены в течение последнего запуска нашего скрипта:

s. SQLConnectorVeeamDB.select_completed_job_sessions_during_latest_hour:

query = 'select job_name,job_type, usn, end_time, result, reason '                 'from [dbo].[Backup.Model.JobSessions] '                 'where state = -1 and result != -1 and datediff(HH,[end_time],GETDATE()) <= 1 '                 'order by usn'

Самое важное для нас — получить последний usn в таблице, чтобы следующий раз запрашивать уже от него, а не по времени. Если ничего не нашли, выполним следующий запрос:

query = 'select top 1 [usn] '                     'from [dbo].[Backup.Model.JobSessions] '                     'order by usn desc'

Статистически этот usn будет выше любого, что вернется при выполнении первого запроса, но JobSessions, которые не попадают в первую выборку, все равно нам не нужны. Если мы не нашли законченных (state = -1) сессий с результатом не None (result != -1) — просто запомним usn, запишем его в ini файл и повторим запросы через некоторый интервал (но уже SQLConnectorVeeamDB.select_completed_job_sessions_after_usn).

Создаем Incoming WebHook Connector для MS Teams


Здесь все просто. Попросите вашего MS Teams админа (респект, если это вы) включить эту функцию, как это описано вот здесь. Теперь создайте connector типа Incoming WebHook. Получилось? Вы прекрасны, ничего больше делать не надо, скопируйте url и сохраните.

Собираем все вместе


Здесь можно скачать уже готовый Dockerfile, либо сам скетч, который мы будем рассматривать дальше. Только не забудьте скачать configuration.py_, положить его рядом с Dockerfile, заполнить и убрать подчеркивание из расширения.

Вот как выглядит концептуальная схема скетча (картинка кликабельна):



Как видно из схемы, самое интересное — разобрать дату, полученную из базы данных Veeam, создавая при этом объекты класса VeeamEvent (просто датахендлер) и сам процесс отправки нотификации.

class VeeamEvent(object)


В таблицу, из которой мы берем дату, логируются все задачи, которые выполняет Veeam.
Каждая из них имеет атрибут job_type. Их очень много, но я выделил самые интересные для нас — не хотим же мы слать сообщение о том, что кто-то открыл консоль программы или закончил рестор?

  • 0 — Backup job
  • 1 — Replication job
  • 3 — SureBackup job
  • 24 — File to tape job
  • 28 — Backup to tape job
  • 51 — Backup Copy job
  • 100 — Configuration Backup

Напишите в комментах, если интересно, я подскажу другие типы тасков по запросу. Законченные таски могут быть либо success, либо warning, а иногда даже и failed.

В итоге мы создаем объект, хранящий в себе все эти данные + имя задачи, usn, описание статуса ее завершения (если есть) и время завершения. Именно этот объект и передается для отправки сообщения в мессенджер.

send_notification_to_web_hook()


Тут все еще проще — используем готовую библиотеку pymsteams, которая просто собирает сообщение по спецификации и отправляет его через requests.post. В своем семпле я даже не стал использовать функции O365 cards, которые тоже поддерживаются WebHook-ами, а просто сделал несколько шаблонов сообщений — в зависимости от результата таска.

team_connection = pymsteams.connectorcard(web_hook_url)
    if event_object.job_type_name is not None:
        if event_object.result_text == 'success':
            text = 'A Veeam ' + event_object.job_type_name + ' **"' + str(event_object.job_name) + '"** has finished **successfully** at ' + str(event_object.end_time)[:-7]
            team_connection.color('005f4b')  # it's a brand color named "Veeam Sapphire", btw

Здесь пригодится фантазия — добавляйте любой текст, картинки, кнопки и т.д. Вот какие виды сообщений получились у меня:


Для задания резервного копирования, где все прошло без проблем


Для failed и warning заданий

Создаем Docker контейнер


Если у Вас еще нет Docker — почитайте A Docker Tutorial for Beginners. Если есть — нам нужно создать следующий Dockerfile:

# Version: 1.0
FROM python:3.6.2
MAINTAINER Dmitry Rozhdestvenskiy <dremsama@gmail.com>
RUN apt-get update && apt-get install -y --no-install-recommends apt-utils
RUN apt-get -y install locales
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
RUN locale-gen
RUN apt-get -y install apt-transport-https freetds-dev unixodbc-dev git
RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
RUN curl https://packages.microsoft.com/config/debian/8/prod.list > /etc/apt/sources.list.d/mssql-release.list
RUN apt-get -y update && ACCEPT_EULA=Y apt-get install msodbcsql
RUN mkdir /veeam_to_msteams
RUN git clone https://github.com/daymer/Veeam-to-MS-Teams-notification-sender-app /veeam_to_msteams
RUN pip install --upgrade pip
RUN pip install -r /veeam_to_msteams/requirements.txt
RUN mkdir /var/log/veeam_to_msteams/
ADD configuration.py /veeam_to_msteams/
RUN chmod +x /veeam_to_msteams/launch_veeam_to_msteams.sh
CMD ["/bin/bash", "/veeam_to_msteams/launch_veeam_to_msteams.sh"]

Все необходимое Docker сам скачает из репозитория (FROM python:3.6.2), Github (RUN git clone...) и packages.microsoft.com. Положите dockerfile в %directory_name%, рядом с configuration.py (Вы же не забыли скачать и заполнить его?). Соберем образ следующей командой:

docker build -t veeam_to_msteams:1.0 -f /path/%directory_name%/Dockerfile /path/%directory_name%/

Запустим контейнер:

docker run  --restart=always  -it --name veeam_to_msteams -d veeam_to_msteams:1.0  bin/bash /veeam_to_msteams/launch_veeam_to_msteams.sh

Если Вы хотите запускать скрипт на другой платформе либо вообще не хотите использовать контейнер — не беда, просто запускайте сам main.py файл, без аргументов.


Пришло мне время наполнить чашку кофе, а вам, если заинтересовались, — дописать мой скетч под нужды своего продакшена. «Вы платите только за доставку», точнее, за вами остается лишь реализация этой самой доставки в предпочтительный канал.

Сведу вместе полезные ссылки:

  1. Мой проект на Github
  2. Драйвер Python SQL — pyodbc
  3. Создание Incoming WebHook Connector
  4. Как включить Incoming WebHook Connector для MS Teams
  5. Библиотека pymsteams
  6. A Docker Tutorial for Beginners

Комментарии (2)


  1. gleb_l
    28.06.2018 20:14

    Желательно избегать использования DATEDIFF в WHERE-клаусе, так как это приведет к необходимости просчитывать скаляр datediff(hh, end_time, getdate()) для каждой проверяемой записи, и соответственно, приведет к clustered index scan вместо index seek для определения подмножества выбираемых записей. Лучше написать where… end_time > (select top 1 dateadd(hh, -1, getdate())) — чтобы планировщик запроса гарантированно единственный раз вычислил subquery, возвращающую граничную дату, с которой уже будут сравниваться все записи в вашей таблице JobSessions. Здесь уже можно построить индекс по убыванию end_date и добавить туда included-колонки state и result — тогда записи вернутся мгновенно и без блокировки таблицы


  1. masyan
    29.06.2018 11:34

    Дмитрий, вы молодец, но, имхо:
    700 метров под пуш в вебхук? ну, такое. ;)

    зачем столько слоёв?
    RUN apt-get update && apt-get install -y --no-install-recommends apt-utils
    RUN apt-get -y install locales
    потом еще раз RUN apt-get -y install apt-transport-https freetds-dev unixodbc-dev git
    ну и т.д.

    раз понаписали столько классов, то зачем тянуть либу, когда просто для инкаминг вебхука надо было собрать json и всё.

    и еще helpcenter.veeam.com/docs/backup/rest/em_web_api_reference.html?ver=95
    у вас же целый Veeam Backup Enterprise Manager RESTful API есть, зачем в базу то лазить?