Множество стоек, каждая плотно упакована серверами, маршрутизаторами, коммутаторами и прочими kvm'ами.
Нужен какой-нибудь удобный способ рулить всем этим хозайством, быстро подключаться к нужному оборудованию и
производить его настройку. Прямо чтобы пара кликов мышью и оп — перед тобой консоль нужного коммутатора.
Для мониторинга наших подопечных мы используем Zabbix.
Так почему бы не приспособить сей дивный инструмент и для этой задачи.
Ведь было бы очень удобно ткнуть в карте Zabbix на нужную стойку, перейти на её подкарту и, выбрав железку,
запустить локальный ssh/telnet/vnc клиент на своем компьютере.
Озадачившись идеей, я начал мучать поисковые машины в надежде отыскать варианты реализации.
Был найден данный тред на форуме Zabbix, но мне хотелось запускать именно локальные программы на моей машине кликом по ссылке в карте.
Еще некоторое время поплутав по закоулкам всемирной паутины и помучав знакомых программистов глупыми вопросами я вспомнил о… Python.
Да, Python, не раз пришедший на помощь в трудную минуту.
Питаю очень нежные чувства к этому языку за его простоту и приятный теплый синтаксис.
И так, вектор атаки изменился и поисковики замерли в ожидании нового вброса мыслеобразов…
Спустя некоторое время я уже четко представлял как буду решать задачу — напишу клиент-серверное приложение!
На моем компьютере будет ждать команд серверная часть, а на сервере мониторинга, при клике по ссылке, будет запускаться клиент и передавать нужную команду.
Результатом изысканий стало кроссплатформенное приложение, работает как на Linux, так и на Windows.
Эпопея проб и ошибок на пути к заветной цели ждет вас под хабракатом.
Начало пути.
В первую очередь отмечу, что синтаксис написанного справедлив для Python версии 2.7.
А версия Zabbix насчитала три двойки —
Отвечать за коммуникацию клиента и сервера поручено Python библиотеке socket.
Помимо документации мне были полезны следующие источники:
1. www.binarytides.com/python-socket-programming-tutorial
2. www.binarytides.com/code-chat-application-server-client-sockets-python
3. java-developer.livejournal.com/6920.html
Наигравшись с примерами, подготовим основу будущего приложения.
Клиент:
И сервер:
Запущеный сервер «слушает» порт 9090 на интерфейсе обратной петли, выполнив клиента мы передаем команду для запуска новой консоли, а параметр
*Основной ОС для меня является Linux, а в качестве DE выступает KDE, но пусть вас это не смущает, итоговый вариант скриптов не будет привязан к ОС.
Основа положена, двигаемся далее!
В этой главе клиент научится понимать аргументы командной строки и мы отправим серверу первую команду с карты Zabbix.
Код серверной части пока не претерпел никаких изменений, а вот в код клиента добавился импорт библиотеки sys.
Сервер должен будет запустить консоль и инициировать подключение по ssh к IP из первого аргумента переданного клиенту.
Логином выступает
А теперь пришло время поместить скрипт клиента на сервер мониторинга.
В моем случае он уютно устроился здесь — /usr/local/bin/client.py
В веб интерфейсе Zabbix перейдем в Administration -> Scripts.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/710/03c/011/71003c011276bd73497522e2e94edc47.png)
Создадим новый скрипт(Create new).
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/2c5/c1c/19a/2c5c1c19a493ad93b32b027448f80dd5.png)
Через Zabbix макрос {HOST.IP} клиенту передается IP для подключения.
Перейдя на карту увидим, что у хостов появилось меню с именем вновь созданного скрипта.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/397/c40/e5b/397c40e5bd2371d9ea920b052cead7b5.png)
Многие справедливо заметят — «Скрипт клиента до сих пор соединяется с localhost, но он же уже на сервере?!»
Все верно! Но для защиты взаимодействия сервера и клиента используется ssh туннель.
Все соединения устанавливаемые на localhost:9090 Zabbix сервера на самом деле устанавливаются со скриптом сервера на нашей локальной машине.
Для достижения такого «зеркалирования» на локальном компьютере нужно выполнить команду:
Подробнее о ssh, и тоннелях в часности, можно прочесть в замечательных статьях на хабре — 1, 2
В этой главе скрипты еще немного поумнеют и научатся отличать Windows от Linux.
Идея состоит в том, чтобы клиент при соединении с сервером первым делом узнавал на какой ОС тот запущен.
И, получив это информацию, отдавал правильную команду.
Код обоих скриптов изменился.
Клиент:
Сервер:
* для Windows переменная OS должна быть изменена соответствующе
Как видно, при коннекте к серверу клиент перво-наперво получает строку с названием ОС, и только потом посылает команду.
Обратите внимание, что бекслэши в путях для ОС Windows нужно экранировать, т.е. ставить двойной
В этой главе мы немного подправим код фронтенда Zabbix.
Научим его передавать скрипту-клиенту вторым аргументом логин зарегистрированного на веб интерфейсе администратора/оператора.
Вроде бы все здорово и уже сейчас можно пользоваться наработками для соединения с серверами, маршрутизаторами, коммутаторами.
Но что если администраторов, пользующихся Zabbix, больше одного?
Нам нужно каким-то образом дать понять скрипту кто его запустил, попросту говоря кто «ткнул» ссылку в веб интерфейсе.
Решать будем поэтапно.
Порты:
Admin — 9090
Admin1 — 9091
и т.д.
Код скрипта-клиента с нужными изменениями:
А вот решить последнюю задачу, в рамках данной главы, оказалось не так просто.
Пришлось существенно увеличить лимит кофе на эту смену и даже умыкнуть кота у жены из под бока.
Кофе был выпит, кот, промурлыкав положенное, давно свалил, а я ни на йоту не приблизился к решению…
Но ощущение близости отгадки не давало мне покоя, оно крепко вцепилось в мой мозг своими клешнями, и я знал, что яблоко вот-вот должно свалиться мне на голову.
И оно свалилось!(не в прямом смысле конечно)
Работающий вариант, как это часто бывает, был практически на поверхности.
Для выполнения всех скриптов, вызванных из веб интерфейса, используется scripts_exec.php, расположеный в корневом каталоге установленного Zabbix.
Описание скриптов хранится в MySQL базе.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/9d8/51e/922/9d851e922d8e1dfff33426fa35802902.png)
Значит исправив должным образом scripts_exec.php мы сможем передавать логин как второй аргумент для нашего скрипта-клиента при клике по ссылке.
Переменная
после нажатия ссылки, будет содержать путь к скрипту + макрос {HOST.IP} + логин админа.
А условие
заменит в таблице MySQL команду на нужную нам.
Например вот так:
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/d28/0a3/543/d280a354302d8821612d25db95f1c185.png)
И теперь скрипт-клиент сможет передать команду нужному скрипту-серверу.
В этой главе мы научим Zabbix запускать vnc и telnet клиентов.
Поговорим о скриптах-клиентах для прочих, перечисленных в заголовке, протоколов.
Листинг директории /usr/local/bin на Zabbix сервере:
Подробно останавливаться на коде не будем, в предыдущих главах это уже было сделано.
Скажу лишь, что для Windows я скачал portable версию vnc клиента и положил все в
Для telnet и vnc из скриптов нужно повторить шаги из главы 1, в веб интерфейсе Zabbix, а для ssh достаточно скорректировать название.
Файл scripts_exeс.php так же нуждается в изменении.
Наведем порядок и придадим некоторый лоск нашей разработке.
Строго говоря, здесь кросплатформеность и заканчивается.
Для ОС Linux, из множества примеров, был собран демон на Python.
Он выполняет дабл форк и преспокойно ждет команд от скриптов-клиентов.
В ОС Windows все оказалось сложнее, но, обо всем по порядку.
Листинг директории с необходимыми скриптами для ОС Linux:
Для запуска демона набираем
Для остановки соответственно
Обратите внимание на строку
из ZabbixTeams.py, она подключает врзможность импортировать скрипты из этого каталога.
Исправьте её в соответствии вашему окружению.
Получившийся демон можно стартовать руками, прилипить иконку для его запуска, завернуть в австостарт DE или ОС.
Это уже кому как нравится.
ОС Windows.
Для написания/исправления кода я пользовался Python IDE PyCharm, поэтому и скрипты разместились по пути:
![](https://habrastorage.org/r/w780q1/getpro/habr/post_images/d7d/8aa/a78/d7d8aaa7845c070ae706d9be0864d2ca.jpg)
Правильнее, конечно, было бы написать сервис для этой ОС при помощи pywin32.
Но в скриптах использованы блокирующие сокеты и не отказавшись от них полноценный сервис у меня не получался.
Возможно позже я возьмусь за asyncore или вообще перепишу все на twisted, но пока мне не хотелось усложнять.
Итоговое видео.
*со звуком все в порядке ;)
Благодарю за внимание.
by FessAectan
ServerClub
Нужен какой-нибудь удобный способ рулить всем этим хозайством, быстро подключаться к нужному оборудованию и
производить его настройку. Прямо чтобы пара кликов мышью и оп — перед тобой консоль нужного коммутатора.
Для мониторинга наших подопечных мы используем Zabbix.
Так почему бы не приспособить сей дивный инструмент и для этой задачи.
Ведь было бы очень удобно ткнуть в карте Zabbix на нужную стойку, перейти на её подкарту и, выбрав железку,
запустить локальный ssh/telnet/vnc клиент на своем компьютере.
Озадачившись идеей, я начал мучать поисковые машины в надежде отыскать варианты реализации.
Был найден данный тред на форуме Zabbix, но мне хотелось запускать именно локальные программы на моей машине кликом по ссылке в карте.
Еще некоторое время поплутав по закоулкам всемирной паутины и помучав знакомых программистов глупыми вопросами я вспомнил о… Python.
Да, Python, не раз пришедший на помощь в трудную минуту.
Питаю очень нежные чувства к этому языку за его простоту и приятный теплый синтаксис.
И так, вектор атаки изменился и поисковики замерли в ожидании нового вброса мыслеобразов…
Спустя некоторое время я уже четко представлял как буду решать задачу — напишу клиент-серверное приложение!
На моем компьютере будет ждать команд серверная часть, а на сервере мониторинга, при клике по ссылке, будет запускаться клиент и передавать нужную команду.
Результатом изысканий стало кроссплатформенное приложение, работает как на Linux, так и на Windows.
Эпопея проб и ошибок на пути к заветной цели ждет вас под хабракатом.
Пролог
Начало пути.
В первую очередь отмечу, что синтаксис написанного справедлив для Python версии 2.7.
А версия Zabbix насчитала три двойки —
Zabbix 2.2.2
Отвечать за коммуникацию клиента и сервера поручено Python библиотеке socket.
Помимо документации мне были полезны следующие источники:
1. www.binarytides.com/python-socket-programming-tutorial
2. www.binarytides.com/code-chat-application-server-client-sockets-python
3. java-developer.livejournal.com/6920.html
Наигравшись с примерами, подготовим основу будущего приложения.
Клиент:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
sock = socket.socket()
sock.connect(('localhost', 9090))
sock.send('/usr/bin/konsole -e mc')
sock.close()
И сервер:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import subprocess
sock = socket.socket()
sock.bind(('localhost', 9090))
sock.listen(1)
while True:
conn, addr = sock.accept()
data = conn.recv(1024)
if not data:
break
subprocess.call(data, shell=True)
conn.close()
Запущеный сервер «слушает» порт 9090 на интерфейсе обратной петли, выполнив клиента мы передаем команду для запуска новой консоли, а параметр
-e mc
указывает, что в консоли нужно запустить файловый менеджер midnight commander.Краткая демонстрация
*Основной ОС для меня является Linux, а в качестве DE выступает KDE, но пусть вас это не смущает, итоговый вариант скриптов не будет привязан к ОС.
Основа положена, двигаемся далее!
Глава 1
В этой главе клиент научится понимать аргументы командной строки и мы отправим серверу первую команду с карты Zabbix.
Код серверной части пока не претерпел никаких изменений, а вот в код клиента добавился импорт библиотеки sys.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import sys
sock = socket.socket()
sock.connect(('localhost', 9090))
sock.send('/usr/bin/konsole -e ssh root@'+ sys.argv[1])
sock.close()
Сервер должен будет запустить консоль и инициировать подключение по ssh к IP из первого аргумента переданного клиенту.
Логином выступает
root
.А теперь пришло время поместить скрипт клиента на сервер мониторинга.
В моем случае он уютно устроился здесь — /usr/local/bin/client.py
В веб интерфейсе Zabbix перейдем в Administration -> Scripts.
![](https://habrastorage.org/getpro/habr/post_images/710/03c/011/71003c011276bd73497522e2e94edc47.png)
Создадим новый скрипт(Create new).
![](https://habrastorage.org/getpro/habr/post_images/2c5/c1c/19a/2c5c1c19a493ad93b32b027448f80dd5.png)
Через Zabbix макрос {HOST.IP} клиенту передается IP для подключения.
Перейдя на карту увидим, что у хостов появилось меню с именем вновь созданного скрипта.
![](https://habrastorage.org/getpro/habr/post_images/397/c40/e5b/397c40e5bd2371d9ea920b052cead7b5.png)
Многие справедливо заметят — «Скрипт клиента до сих пор соединяется с localhost, но он же уже на сервере?!»
Все верно! Но для защиты взаимодействия сервера и клиента используется ssh туннель.
Все соединения устанавливаемые на localhost:9090 Zabbix сервера на самом деле устанавливаются со скриптом сервера на нашей локальной машине.
Для достижения такого «зеркалирования» на локальном компьютере нужно выполнить команду:
ssh -f -N -R 9090:127.0.0.1:9090 zabbix
Подробнее о ssh, и тоннелях в часности, можно прочесть в замечательных статьях на хабре — 1, 2
Видеоиллюстрация к первой главе
Глава 2
В этой главе скрипты еще немного поумнеют и научатся отличать Windows от Linux.
Идея состоит в том, чтобы клиент при соединении с сервером первым делом узнавал на какой ОС тот запущен.
И, получив это информацию, отдавал правильную команду.
Код обоих скриптов изменился.
Клиент:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import sys
sock = socket.socket()
sock.connect(('localhost', 9090))
os = sock.recv(64)
if os == "Windows":
sock.send('C:\\apps\\putty.exe -ssh root@' + sys.argv[1])
elif os == "Linux":
sock.send('/usr/bin/konsole -e ssh root@'+ sys.argv[1])
sock.close()
Сервер:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import subprocess
OS = "Linux"
sock = socket.socket()
sock.bind(('127.0.0.1', 9090))
sock.listen(1)
while True:
conn, addr = sock.accept()
conn.sendall(OS)
data = conn.recv(1024)
print data
if not data:
break
subprocess.call(data, shell=True)
conn.close()
* для Windows переменная OS должна быть изменена соответствующе
Как видно, при коннекте к серверу клиент перво-наперво получает строку с названием ОС, и только потом посылает команду.
Обратите внимание, что бекслэши в путях для ОС Windows нужно экранировать, т.е. ставить двойной
\\
Видео ко второй главе
На видео:
«зеркалируем» порт в Windows запустив putty
запускаем сервер
кликаем ссылку для соединения с хостом
отключаем все в Windows и проделываем то же самое в Linux
На видео:
«зеркалируем» порт в Windows запустив putty
запускаем сервер
кликаем ссылку для соединения с хостом
отключаем все в Windows и проделываем то же самое в Linux
Глава 3
В этой главе мы немного подправим код фронтенда Zabbix.
Научим его передавать скрипту-клиенту вторым аргументом логин зарегистрированного на веб интерфейсе администратора/оператора.
Вроде бы все здорово и уже сейчас можно пользоваться наработками для соединения с серверами, маршрутизаторами, коммутаторами.
Но что если администраторов, пользующихся Zabbix, больше одного?
Нам нужно каким-то образом дать понять скрипту кто его запустил, попросту говоря кто «ткнул» ссылку в веб интерфейсе.
Решать будем поэтапно.
- Закрепим за каждым администратором определенный порт.
- Научим скрипт-клиент выбирать порт в зависимости от второго аргумента — логина.
- Изменим код Zabbix для передачи второго аргумента при клике по ссылке из веб интерфейса.
Порты:
Admin — 9090
Admin1 — 9091
и т.д.
Код скрипта-клиента с нужными изменениями:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import sys
if sys.argv[2] == 'Admin':
PORT=9090
elif sys.argv[2] == 'Admin1':
PORT=9091
sock = socket.socket()
sock.connect(('localhost', PORT))
os = sock.recv(64)
if os == "Windows":
sock.send('C:\\apps\\putty.exe -ssh root@' + sys.argv[1])
elif os == "Linux":
sock.send('/usr/bin/konsole -e ssh root@'+ sys.argv[1])
sock.close()
А вот решить последнюю задачу, в рамках данной главы, оказалось не так просто.
Пришлось существенно увеличить лимит кофе на эту смену и даже умыкнуть кота у жены из под бока.
Кофе был выпит, кот, промурлыкав положенное, давно свалил, а я ни на йоту не приблизился к решению…
Но ощущение близости отгадки не давало мне покоя, оно крепко вцепилось в мой мозг своими клешнями, и я знал, что яблоко вот-вот должно свалиться мне на голову.
И оно свалилось!(не в прямом смысле конечно)
Работающий вариант, как это часто бывает, был практически на поверхности.
Для выполнения всех скриптов, вызванных из веб интерфейса, используется scripts_exec.php, расположеный в корневом каталоге установленного Zabbix.
Описание скриптов хранится в MySQL базе.
![](https://habrastorage.org/getpro/habr/post_images/9d8/51e/922/9d851e922d8e1dfff33426fa35802902.png)
Значит исправив должным образом scripts_exec.php мы сможем передавать логин как второй аргумент для нашего скрипта-клиента при клике по ссылке.
Код исправленного scripts_exec.php:
<?php
/*
** Zabbix
** Copyright (C) 2001-2014 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
require_once dirname(__FILE__).'/include/config.inc.php';
$page['title'] = _('Scripts');
$page['file'] = 'scripts_exec.php';
define('ZBX_PAGE_NO_MENU', 1);
require_once dirname(__FILE__).'/include/page_header.php';
$NewCommand4Run = '/usr/local/bin/client_ssh.py {HOST.IP} '. CWebUser::$data['alias'];
// VAR TYPE OPTIONAL FLAGS VALIDATION EXCEPTION
$fields = array(
'hostid' => array(T_ZBX_INT, O_OPT, P_ACT, DB_ID, null),
'scriptid' => array(T_ZBX_INT, O_OPT, null, DB_ID, null)
);
check_fields($fields);
ob_flush();
flush();
$scriptId = getRequest('scriptid');
$hostId = getRequest('hostid');
$data = array(
'message' => '',
'info' => DBfetch(DBselect('SELECT s.name FROM scripts s WHERE s.scriptid='.zbx_dbstr($scriptId)))
);
if ($scriptId == 5) {
DBexecute('update zabbix.scripts set command='."'".$NewCommand4Run."'".' where scriptid=5;');
}
$result = API::Script()->execute(array(
'hostid' => $hostId,
'scriptid' => $scriptId
));
$isErrorExist = false;
if (!$result) {
$isErrorExist = true;
}
elseif ($result['response'] == 'failed') {
error($result['value']);
$isErrorExist = true;
}
else {
$data['message'] = $result['value'];
}
if ($isErrorExist) {
show_error_message(
_('Cannot connect to the trapper port of zabbix server daemon, but it should be available to run the script.')
);
}
// render view
$scriptView = new CView('general.script.execute', $data);
$scriptView->render();
$scriptView->show();
require_once dirname(__FILE__).'/include/page_footer.php';
Переменная
$NewCommand4Run = '/usr/local/bin/client.py {HOST.IP} '. CWebUser::$data['alias'];
после нажатия ссылки, будет содержать путь к скрипту + макрос {HOST.IP} + логин админа.
А условие
if ($scriptId == 5) {
DBexecute('update zabbix.scripts set command='."'".$NewCommand4Run."'".' where scriptid=5;');
}
заменит в таблице MySQL команду на нужную нам.
Например вот так:
![](https://habrastorage.org/getpro/habr/post_images/d28/0a3/543/d280a354302d8821612d25db95f1c185.png)
И теперь скрипт-клиент сможет передать команду нужному скрипту-серверу.
А вот как это выглядит.
Смотреть лучше без звука, ноут у меня разбушевался уж очень)
Глава 4
В этой главе мы научим Zabbix запускать vnc и telnet клиентов.
Поговорим о скриптах-клиентах для прочих, перечисленных в заголовке, протоколов.
Листинг директории /usr/local/bin на Zabbix сервере:
-rwxr-xr-x 1 root staff 429 Mar 17 03:06 client_ssh.py
-rwxr-xr-x 1 root staff 418 Mar 17 04:58 client_telnet.py
-rwxr-xr-x 1 root staff 412 Mar 17 05:00 client_vnc.py
Листинг каждого скрипта-клиента
client_ssh.py
client_vnc.py
client_telnet.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import sys
if sys.argv[2] == 'Admin':
PORT=9090
elif sys.argv[2] == 'Admin1':
PORT=9091
sock = socket.socket()
sock.connect(('localhost', PORT))
os = sock.recv(64)
if os == "Windows":
sock.send('C:\\apps\\putty.exe -ssh root@' + sys.argv[1])
elif os == "Linux":
sock.send('/usr/bin/konsole -e ssh root@'+ sys.argv[1])
sock.close()
client_vnc.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import sys
if sys.argv[2] == 'Admin':
PORT=9090
elif sys.argv[2] == 'Admin1':
PORT=9091
sock = socket.socket()
sock.connect(('localhost', PORT))
os = sock.recv(64)
if os == "Windows":
sock.send('c:\\apps\\vnc.exe ' + sys.argv[1])
elif os == "Linux":
sock.send('/usr/bin/X11/gvncviewer '+ sys.argv[1])
sock.close()
client_telnet.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import sys
if sys.argv[2] == 'Admin':
PORT=9090
elif sys.argv[2] == 'Admin1':
PORT=9091
sock = socket.socket()
sock.connect(('localhost', PORT))
os = sock.recv(64)
if os == "Windows":
sock.send('C:\\apps\\putty.exe -telnet ' + sys.argv[1])
elif os == "Linux":
sock.send('/usr/bin/konsole -e telnet '+ sys.argv[1])
sock.close()
Подробно останавливаться на коде не будем, в предыдущих главах это уже было сделано.
Скажу лишь, что для Windows я скачал portable версию vnc клиента и положил все в
c:\apps\
Для telnet и vnc из скриптов нужно повторить шаги из главы 1, в веб интерфейсе Zabbix, а для ssh достаточно скорректировать название.
Файл scripts_exeс.php так же нуждается в изменении.
Вновь измененный код scripts_exeс.php
<?php
/*
** Zabbix
** Copyright (C) 2001-2014 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
require_once dirname(__FILE__).'/include/config.inc.php';
$page['title'] = _('Scripts');
$page['file'] = 'scripts_exec.php';
define('ZBX_PAGE_NO_MENU', 1);
require_once dirname(__FILE__).'/include/page_header.php';
$NewCommand4RunSSH = '/usr/local/bin/client_ssh.py {HOST.IP} '. CWebUser::$data['alias'];
$NewCommand4RunVNC = '/usr/local/bin/client_vnc.py {HOST.IP} '. CWebUser::$data['alias'];
$NewCommand4RunTELNET = '/usr/local/bin/client_telnet.py {HOST.IP} '. CWebUser::$data['alias'];
// VAR TYPE OPTIONAL FLAGS VALIDATION EXCEPTION
$fields = array(
'hostid' => array(T_ZBX_INT, O_OPT, P_ACT, DB_ID, null),
'scriptid' => array(T_ZBX_INT, O_OPT, null, DB_ID, null)
);
check_fields($fields);
ob_flush();
flush();
$scriptId = getRequest('scriptid');
$hostId = getRequest('hostid');
$data = array(
'message' => '',
'info' => DBfetch(DBselect('SELECT s.name FROM scripts s WHERE s.scriptid='.zbx_dbstr($scriptId)))
);
if ($scriptId == 5) {
DBexecute('update zabbix.scripts set command='."'".$NewCommand4RunSSH."'".' where scriptid=5;');
}
if ($scriptId == 6) {
DBexecute('update zabbix.scripts set command='."'".$NewCommand4RunVNC."'".' where scriptid=6;');
}
if ($scriptId == 7) {
DBexecute('update zabbix.scripts set command='."'".$NewCommand4RunTELNET."'".' where scriptid=7;');
}
$result = API::Script()->execute(array(
'hostid' => $hostId,
'scriptid' => $scriptId
));
$isErrorExist = false;
if (!$result) {
$isErrorExist = true;
}
elseif ($result['response'] == 'failed') {
error($result['value']);
$isErrorExist = true;
}
else {
$data['message'] = $result['value'];
}
if ($isErrorExist) {
show_error_message(
_('Cannot connect to the trapper port of zabbix server daemon, but it should be available to run the script.')
);
}
// render view
$scriptView = new CView('general.script.execute', $data);
$scriptView->render();
$scriptView->show();
require_once dirname(__FILE__).'/include/page_footer.php';
Уже традиционное видео.
Здесь тоже лучше без звука.
Заключение.
Наведем порядок и придадим некоторый лоск нашей разработке.
Строго говоря, здесь кросплатформеность и заканчивается.
Для ОС Linux, из множества примеров, был собран демон на Python.
Он выполняет дабл форк и преспокойно ждет команд от скриптов-клиентов.
В ОС Windows все оказалось сложнее, но, обо всем по порядку.
Листинг директории с необходимыми скриптами для ОС Linux:
fessae@workbook:~/sh/python > pwd
/home/fessae/sh/python
fessae@workbook:~/sh/python > ll
total 12
-rwxr-xr-x 1 fessae fessae 2880 Feb 28 02:56 daemon.py*
-rwxr-xr-x 1 fessae fessae 710 Mar 17 03:36 server.py*
-rwxr-xr-x 1 fessae fessae 1122 Mar 13 04:59 ZabbixTeams.py*
Код каждого из скриптов серверной части Linux.
daemon.py
server.py
ZabbixTeams.py
#!/usr/bin/env python
import sys, os, time, atexit
from signal import SIGTERM
class Daemon:
"""
A generic daemon class.
Usage: subclass the Daemon class and override the run() method
"""
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
def daemonize(self):
"""
do the UNIX double-fork magic, see Stevens' "Advanced
Programming in the UNIX Environment" for details (ISBN 0201563177)
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
"""
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# decouple from parent environment
os.chdir("/")
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin, 'r')
so = file(self.stdout, 'a+')
se = file(self.stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# write pidfile
atexit.register(self.delpid)
pid = str(os.getpid())
file(self.pidfile,'w+').write("%s\n" % pid)
def delpid(self):
os.remove(self.pidfile)
def start(self):
"""
Start the daemon
"""
# Check for a pidfile to see if the daemon already runs
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
if pid:
message = "pidfile %s already exist. Daemon already running?\n"
sys.stderr.write(message % self.pidfile)
sys.exit(1)
# Start the daemon
self.daemonize()
self.run()
def stop(self):
"""
Stop the daemon
"""
# Get the pid from the pidfile
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
if not pid:
message = "pidfile %s does not exist. Daemon not running?\n"
sys.stderr.write(message % self.pidfile)
return # not an error in a restart
# Try killing the daemon process
try:
while 1:
os.kill(pid, SIGTERM)
time.sleep(0.1)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print str(err)
sys.exit(1)
def restart(self):
"""
Restart the daemon
"""
self.stop()
self.start()
def run(self):
"""
You should override this method when you subclass Daemon. It will be called after the process has been
daemonized by start() or restart().
"""
server.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import subprocess
import sys
OS = "Linux"
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
try:
sock.bind(('127.0.0.1', 9090))
except socket.error , msg:
print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
sys.exit()
print 'Socket bind complete'
sock.listen(1)
print 'Socket now listening'
while True:
conn, addr = sock.accept()
conn.sendall(OS)
data = conn.recv(1024)
print 'Connected with ' + addr[0] + ':' + str(addr[1])
print data
if not data:
break
subprocess.call(data, shell=True)
conn.close()
if __name__ == "__main__":
main()
ZabbixTeams.py
#/usr/bin/env python
#-*- coding: utf-8 -*-
import sys
sys.path.append("/home/fessae/sh/python")
from daemon import Daemon
import server
class ZabbixTeams(Daemon):
def run(self):
while True:
server.main()
if __name__ == "__main__":
daemon = ZabbixTeams('/tmp/zabbixteams.pid',stdout='/tmp/zabbixteams.log',stderr='/tmp/zabbixteamserr.log')
if len(sys.argv) == 2:
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
else:
print "Unknown command"
sys.exit(2)
sys.exit(0)
else:
print "usage: %s start|stop|restart" % sys.argv[0]
sys.exit(2)
Для запуска демона набираем
python ZabbixTeams.py start
Для остановки соответственно
python ZabbixTeams.py stop
Обратите внимание на строку
sys.path.append("/home/fessae/sh/python")
из ZabbixTeams.py, она подключает врзможность импортировать скрипты из этого каталога.
Исправьте её в соответствии вашему окружению.
Получившийся демон можно стартовать руками, прилипить иконку для его запуска, завернуть в австостарт DE или ОС.
Это уже кому как нравится.
ОС Windows.
Для написания/исправления кода я пользовался Python IDE PyCharm, поэтому и скрипты разместились по пути:
![](https://habrastorage.org/getpro/habr/post_images/d7d/8aa/a78/d7d8aaa7845c070ae706d9be0864d2ca.jpg)
Правильнее, конечно, было бы написать сервис для этой ОС при помощи pywin32.
Но в скриптах использованы блокирующие сокеты и не отказавшись от них полноценный сервис у меня не получался.
Возможно позже я возьмусь за asyncore или вообще перепишу все на twisted, но пока мне не хотелось усложнять.
Поэтому запуск демона на Windows выглядит следующим образом.
- на рабочем столе расположен файл server.bat содержащий:
c:\Python27\python.exe C:\Users\FessAectan\PycharmProjects\ZabbixTeams\daemon.py
- daemon.py, в свою очередь, выглядит вот так:
import sys import subprocess CREATE_NO_WINDOW = 0x8000000 subprocess.Popen(["c:\Python27\python.exe", "-O","C:\Users\FessAectan\PycharmProjects\ZabbixTeams\server.py"], shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags = CREATE_NO_WINDOW)
- и, конечно же, сам server.py
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket import subprocess OS = "Windows" def main(): sock = socket.socket() print "Socket created" try: sock.bind(('127.0.0.1', 9091)) except socket.error , msg: print "Bind failed. Error Code : " + str(msg[0]) + " Message " + msg[1] sys.exit() print "Socket bind complete" sock.listen(1) print "Socket now listening" while True: conn, addr = sock.accept() conn.sendall(OS) data = conn.recv(1024) print "Connected with " + addr[0] + ":" + str(addr[1]) if not data: break subprocess.Popen(data, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags = 0x00000008) conn.close() if __name__ == "__main__": main()
Для Windows subprocess.call был заменен на subprocess.Popen.
Итоговое видео.
*со звуком все в порядке ;)
Благодарю за внимание.
by FessAectan
ServerClub