Для работы с оборудованием DCImanager использует модули — обработчики устройств. Полный список обработчиков, поддерживаемых панелью управления см. в статье Поддерживаемые устройства. Вы можете разработать обработчик устройств самостоятельно. В статье описаны принципы создания нового обработчика устройств.
Принципы работы
Внешний обработчик устройств — исполняемый файл: скрипт или бинарный файл. Обмен данными между DCImanager и обработчиком осуществляется через stdout и stdin. Данные передаются в виде XML.
При запуске DCImanager по очереди запускает исполняемые файлы из директории /usr/local/mgr5/var/dcihandlers с ключом -info. По умолчанию директория не существует, её нужно создать. Каждый обработчик выводит через stdout XML, который описывает, какое именно устройство данный исполняемый файл обрабатывает и какие функции оно поддерживает. Таким образом DCImanager регистрирует у себя новый обработчик, который будет доступен при создании новых устройств.
Протокол взаимодействия
Регистрация обработчика
DCImanager запускает обработчик с флагом -info
testhandler -info
На stdin обработчика ничего не будет передаваться, однако обработчик должен передать XML, описывающий устройство, через свой stdout. Пример XML:
<doc>
<type>Switch</type>
<name>First External Handler</name>
<requirements>
<snmpv1/>
<snmpv2c/>
</requirements>
<supported_funcs>
<status/>
<port_on/>
<port_off/>
</supported_funcs>
</doc>
<type> — тип устройства, может принимать одно из значений: Switch (коммутатор), PDU (распределитель питания), IPMI, UPS (источник бесперебойного питания).
<name> — имя устройства, которое будет отображаться в DCImanager.
<requirements> — список входных данных, которые требуются обработчику. От этого списка зависит, какие данные будет предлагать ввести DCImanager при создании устройства на основе данного обработчика и какие входные данные будут предаваться обработчику при выполнении различных операций с устройством. Может включать:
- <snmpv1/> — SNMPv1 будет доступен на форме создания устройства, в обработчик будут передаваться данные о версии SNMP, а также Community;
- <snmpv2c/> — SNMPv2c будет доступен на форме создания устройства, в обработчик будут передаваться данные о версии SNMP, а также Community;
- <snmpv3/> — SNMPv3 будет доступен на форме создания устройства, в обработчик будут передаваться данные о версии SNMP, а также имя пользователя, пароль (auth phrase), приватный ключ (priv phrase) и уровень аутентификации;
- <ssh/> — на форме будет доступна вкладка SSH, в обработчик будут передаваться имя пользователя и пароль для авторизации по SSH;
- <telnet/> — на форме будет доступна вкладка Telnet, в обработчик будут передаваться имя пользователя и пароль для авторизации по Telnet;
- <can_collect_power/> — необходимо указывать только для обработчиков PDU и UPS. Если в панели нет ни одного устройства PDU или UPS с поддержкой этой опции, то не будет доступна вкладка со статистикой по питанию и столбцы потребления питания в списках серверов и стоек.
Можно использовать любую комбинацию вышеперечисленных параметров.
<supported_funcs>
Список функций, поддерживаемых устройством. Может включать:
- <status/> — обработчик может считать информацию об устройстве. Обязательно должна быть реализована;
- <statistics/> — обработчик может собрать статистику по трафику (для коммутаторов) или статистику потребления питания (для некоторых PDU);
- <port_on/> — обработчик может включить порт устройства;
- <port_off/> — обработчик может выключить порт устройства;
- <port_reset/> — обработчик может сбросить порт устройства (в основном используется для портов питания);
- <port_speed/> — обработчик может установить скорость порта устройства (только для коммутаторов);
- <port_duplex/> — обработчик может установить режим порта устройства (только для коммутаторов);
- <set_vlan/> — обработчик может установить VLAN порта устройства (только для коммутаторов);
- <mac_list/> — обработчик может получить список MAC-адресов на портах устройства (только для коммутаторов);
- <pvlans/> — обработчик может установить тип PVLAN основного VLAN на порту и mapped VLAN (только для коммутаторов).
Вызов функций обработчика
Входные данные
При любом вызове функции обработчика (например, выключение порта распределителя питания) вызывается исполняемый файл обработчика без параметров и через его stdin передаётся XML со всеми необходимыми данными.
Например, вызов функции status, которая должна вернуть информацию о состоянии всех портов устройства:
<doc>
<func>status</func>
<device_params>
<ip>10.10.10.2</ip>
<snmp_ver>SNMP v2c</snmp_ver>
<snmp_community>sdffga</snmp_community>
</device_params>
</doc>
Например, вызов функции port_on, которая должна включить определённый порт устройства.
<doc>
<func>port_on</func>
<device_params>
<ip>12.23.55.32</ip>
<snmp_ver>SNMP v2c</snmp_ver>
<snmp_community>asasda</snmp_community>
</device_params>
<port>
<identity>1</identity>
</port>
</doc>
Список параметров устройств:
Список параметров портов:
Выходные данные
Получив и обработав входные данные, обработчик должен вернуть результат, также в виде XML.
Например, ответ обработчика коммутатора с четырьмя портами на функцию status. Описывает все порты и их состояние:
<doc>
<hostname>comm3</hostname>
<port>
<identity>1</identity>
<description>FastEthernet 1</description>
<admin_status>on</admin_status>
<oper_status>on</oper_status>
</port>
<port>
<identity>2</identity>
<description>FastEthernet 2</description>
<admin_status>on</admin_status>
<oper_status>off</oper_status>
</port>
<port>
<identity>3</identity>
<description>FastEthernet 3</description>
<admin_status>on</admin_status>
<oper_status>off</oper_status>
</port>
<port>
<identity>4</identity>
<description>FastEthernet 4</description>
<admin_status>on</admin_status>
<oper_status>off</oper_status>
</port>
</doc>
Например, ответ на функцию port_off для порта с идентификатором "1".
<doc>
<port>
<identity>1</identity>
<admin_status>off</admin_status>
</port>
</doc>
Список параметров портов:
Также обработчик может вернуть ошибку, например:
<doc>
<error>
<type>connection</type>
<text>Failed to open connection to 12.35.56.22</text>
</error>
</doc>
Если в выходном XML есть блок <error> то всё остальное содержимое будет игнорироваться, операция завершится, кроме того будет создано уведомление об ошибке во время работы с устройством.
Соответствие входных и выходных данных
Коммутаторы (<type>Switch</type>)
Распределители питания (<type>PDU</type>)
IPMI (<type>IPMI</type>)
У IPMI нет портов т. к. он интегрирован в сервер, однако в DCImanager есть специально зарезервированный порт такого типа устройств. Его идентификатор должен быть "power" с учётом регистра.
UPS (<type>UPS</type>)
Для UPS опрашивается только состояние устройства. Данные полученные от UPS отображаются в списке источников бесперебойного питания.
Примеры обработчиков
Обработчик коммутатора
Данный обработчик аналогичен поставляемому с DCImanager обработчику SNMP Common. Управление коммутатором осуществляется посредством SNMPv2c с использованием библиотеки pysnmp 4ой версии.
#!/usr/bin/python2.7
#coding=utf8mb4
import sys
import os
import xml.etree.ElementTree as xmlET
from pysnmp.entity.rfc3413.oneliner import cmdgen
from pysnmp.proto import rfc1902
def xpath_result( root, xpath ):
res = root.findall( xpath )
if len( res ) == 1:
return res[0].text
else:
return ""
#Исключения, которые могут произойти во время работы с устройством.
class ConnectionProblem( Exception ):
def __init__( self ):
#Всего возможны два типа проблем.
#connection — проблема с соединением и...
print "<doc><error><type>connection</type><text>Unable to connect. Check address or community string.</text></error></doc>"
class UnexpectedDataProblem( Exception ):
def __init__( self, msg ):
#... unexpected_data — проблема обмена данными с устройством.
print "<doc><error><type>unexpected_data</type><text>" + msg + "</text></error></doc>"
#Различные функции преобразований значений от устройства к панели и обратно.
def CiscoPortStatusToIFXStr( val ):
return "on" if val == 1 else "off"
def CiscoPortSpeedToIFXStr( val ):
if val == 1:
return "auto"
elif val == 2:
return "auto10100"
elif val == 10000000:
return "10mbps"
elif val == 100000000:
return "100mbps"
elif val == 1000000000:
return "1gbps"
elif val == 10:
return "10gbps"
else:
raise UnexpectedDataProblem( "Unexpected speed value = " + str( val ) )
def IFXPortSpeedToCisco( val ):
if val == "auto":
return 1
elif val == "auto10100":
return 2
elif val == "10mbps":
return 10000000
elif val == "100mbps":
return 100000000
elif val == "1gbps":
return 1000000000
elif val == "10gbps":
return 10
def CiscoPortDuplexToIFXStr( val ):
if val == 1:
return "half"
elif val == 2:
return "full"
elif val == 4:
return "auto"
else:
raise UnexpectedDataProblem( "Unexpected duplex value = " + str( val ) )
def IFXPortDuplexToCisco( val ):
if val == "half":
return 1
elif val == "full":
return 2
elif val == "auto":
return 4
#Класс обработчика.
class SwitchSimpleHandler:
def __init__( self, request_from_ifx ):
#Инициализация всех объектов необходимых для SNMP-запроса.
self.__cmdGen = cmdgen.CommandGenerator()
xmlRoot = xmlET.fromstring( request_from_ifx )
self.__Community = cmdgen.CommunityData( xpath_result( xmlRoot, "./device_params/snmp_community" ) )
self.__Target = cmdgen.UdpTransportTarget((xpath_result( xmlRoot, "./device_params/ip" ), 161))
#Определяем функцию, которую вызвал DCImanager, и вызываем соотвествующий метод
func_name = xpath_result( xmlRoot, "./func" )
if func_name == "status":
self.__Status()
elif func_name == "port_off":
self.__PortOff( xpath_result( xmlRoot, "./port/identity" ) )
elif func_name == "port_on":
self.__PortOn( xpath_result( xmlRoot, "./port/identity" ) )
elif func_name == "port_speed":
self.__PortSpeed( xpath_result( xmlRoot, "./port/identity" ), xpath_result( xmlRoot, "./port/speed" ) )
elif func_name == "port_duplex":
self.__PortDuplex( xpath_result( xmlRoot, "./port/identity" ), xpath_result( xmlRoot, "./port/duplex" ) )
def __PortOff( self, ident ):
#Выключаем порт
self.__SnmpSet( "1.3.6.1.2.1.2.2.1.7." + ident, rfc1902.Integer( 2 ) )
#Сообщаем панели новое состояние порта
output = "<doc><port>"
output += "<identity>" + ident + "</identity>"
output += "<admin_status>on</admin_status>"
output += "</port></doc>"
print output
def __PortOn( self, ident ):
#Включаем порт
self.__SnmpSet( "1.3.6.1.2.1.2.2.1.7." + ident, rfc1902.Integer( 1 ) )
#Сообщаем панели новое состояние порта
output = "<doc><port>"
output += "<identity>" + ident + "</identity>"
output += "<admin_status>on</admin_status>"
output += "</port></doc>"
print output
def __PortSpeed( self, ident, val ):
#Получаем список индексов...
indexes = self.__SnmpWalk( "1.3.6.1.4.1.9.5.1.4.1.1.11" )
#Чтобы с его помощью установить новую скорость нужному порту.
#Ключ ищется по значению.
self.__SnmpSet( "1.3.6.1.4.1.9.5.1.4.1.1.9.1." + indexes.keys()[indexes.values().index( int( ident ) )],
rfc1902.Integer( IFXPortSpeedToCisco( val ) ) )
#Сообщаем панели новую скорость порта.
output = "<doc><port>"
output += "<identity>" + ident + "</identity>"
output += "<speed>" + val + "</speed>"
output += "</port></doc>"
print output
def __PortDuplex( self, ident, val ):
#Получаем список индексов...
indexes = self.__SnmpWalk( "1.3.6.1.4.1.9.5.1.4.1.1.11" )
#Чтобы с его помощью установить новый режим нужному порту.
#Ключ ищется по значению.
self.__SnmpSet( "1.3.6.1.4.1.9.5.1.4.1.1.10.1." + indexes.keys()[indexes.values().index( int( ident ) )],
rfc1902.Integer( IFXPortDuplexToCisco( val ) ) )
#Сообщаем панели новый режим порта.
output = "<doc><port>"
output += "<identity>" + ident + "</identity>"
output += "<duplex>" + val + "</duplex>"
output += "</port></doc>"
print output
def __Status( self ):
output = "<doc>"
ports = {}#Составляем словарь портов, где ключом будет идентификатор порта.
#Опеределяем описание портов.
for ident, descr in self.__SnmpWalk( "1.3.6.1.2.1.2.2.1.2" ).iteritems():
ports[ident] = self.DevicePort( ident )
ports[ident].Description = descr
#Определяем состояние портов, заданное администратором.
for ident, adm_status in self.__SnmpWalk( "1.3.6.1.2.1.2.2.1.7" ).iteritems():
ports[ident].AdminStatus = CiscoPortStatusToIFXStr( adm_status )
#Определяем реальное состояние портов.
for ident, oper_status in self.__SnmpWalk( "1.3.6.1.2.1.2.2.1.8" ).iteritems():
ports[ident].OperStatus = CiscoPortStatusToIFXStr( oper_status )
#Определяем текущие скорость и режим портов.
indexes = self.__SnmpWalk( "1.3.6.1.4.1.9.5.1.4.1.1.11" )
for ind, speed in self.__SnmpWalk( "1.3.6.1.4.1.9.5.1.4.1.1.9" ).iteritems():
ports[str( indexes[ind] )].Speed = CiscoPortSpeedToIFXStr( speed )
for ind, duplex in self.__SnmpWalk( "1.3.6.1.4.1.9.5.1.4.1.1.10" ).iteritems():
ports[str( indexes[ind] )].Duplex = CiscoPortDuplexToIFXStr( duplex )
#Сообщаем полный список портов панели.
output = "<doc>"
for port in ports.values():
output += "<port>"
output += "<identity>" + port.Identity + "</identity>"
output += "<description>" + port.Description + "</description>"
output += "<admin_status>" + port.AdminStatus + "</admin_status>"
output += "<oper_status>" + port.OperStatus + "</oper_status>"
output += "<duplex>" + port.Duplex + "</duplex>"
output += "<speed>" + port.Speed + "</speed>"
output += "</port>"
output += "</doc>"
print output
#Установка значения mib. Переданное значение должно быть приведено к типу в соответствии с rfc1902
def __SnmpSet( self, mib, value ):
errorIndication, errorStatus, errorIndex, varBinds = self.__cmdGen.setCmd( self.__Community,
self.__Target,
( cmdgen.MibVariable( mib ), value ) )
if errorIndication:
raise ConnectionProblem
#Обход дерева заданного mib.
def __SnmpWalk( self, mib ):
errorIndication, errorStatus, errorIndex, varBindTable = self.__cmdGen.nextCmd( self.__Community,
self.__Target,
cmdgen.MibVariable( mib ) )
if errorIndication:
raise ConnectionProblem
result_map = {}
for varBindTableRow in varBindTable:
for name, val in varBindTableRow:
result_map[name.prettyPrint().rpartition( "." )[2]] = val
return result_map
class DevicePort:
def __init__( self, ident ):
self.Identity = ident
Identity = ""
Description = ""
AdminStatus = "unknown"
OperStatus = "unknown"
Duplex = "unknown"
Speed = "unknown"
@staticmethod
def Info():
output = "<doc>"
#Тип устройства
output += "<type>Switch</type>"
output += "<name>SNMP Switch Handler</name>"
#Используем SNMP v2c
output += "<requirements>"
output += "<snmpv2c/>"
output += "</requirements>"
#DCImanager будет вызывать у обработчика коммутатора только 4 функции:
output += "<supported_funcs>"
#Получение списка портов
output += "<status/>"
#Выключение порта
output += "<port_off/>"
#Включение порта
output += "<port_on/>"
#Смена режима порта
output += "<port_duplex/>"
#Изменение скорости порта
output += "<port_speed/>"
output += "</supported_funcs>"
output += "</doc>"
print output
__cmdGen = None
__Target = None
__Community = None
def main():
if len(sys.argv) > 1 and sys.argv[1] == "-info":
#Если есть ключ -info, то выдаём информацию об обработчике
SwitchSimpleHandler.Info()
else:
#Во всех остальных случаях читаем поток ввода и создаём объект обработчика,
#который выполнит обработку запроса от DCImanager.
request_str = sys.stdin.read()
SwitchSimpleHandler( request_str )
#Запуск основной функции
main()
Обработчик IPMI
Данный обработчик реализует управление устройствами c IPMI v1.5 посредством утилиты ipmitool, написанной на python 2.7.
Запуск скрипта начинается с вызова функции main().
#!/usr/bin/python2.7
#coding=utf8mb4
import sys
import commands
import os
import xml.etree.ElementTree as xmlET
def xpath_result( root, xpath ):
res = root.findall( xpath )
if len( res ) == 1:
return res[0].text
else:
return ""
class IPMIhandler:
def __init__( self, request_from_ifx ):
xmlRoot = xmlET.fromstring( request_from_ifx )
#Получаем IP-адрес IPMI.
self.__IP = xpath_result( xmlRoot, "./device_params/ip" )
#Получаем пользователя IPMI, которому должно быть разрешено управление питанием.
self.__User = xpath_result( xmlRoot, "./device_params/user" ).replace( "`", "\\`" )
#Пароль в ipmitool будем передавать через переменную окружения, для безопасности.
os.putenv( "IPMI_PASSWORD", xpath_result( xmlRoot, "./device_params/pass" ) )
#Определяем функцию, которую вызвал DCImanager и вызываем соотвествующий метод
func_name = xpath_result( xmlRoot, "./func" )
if func_name == "status":
self.__Status()
elif func_name == "port_off":
self.__PortOff()
elif func_name == "port_on":
self.__PortOn()
elif func_name == "port_reset":
self.__PortReset()
def __PortOff( self ):
#Выключаем сервер через ipmitool
cmd_res, _ = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power off" )
if cmd_res == 0:
#Если команда выполнилась успешно, то возвращаем через поток вывода новое состояние.
#Для IPMI подключения identity порта всегда должно иметь значение power.
output = "<doc><port>"
output += "<identity>power</identity>"
output += "<admin_status>off</admin_status>"
output += "</port></doc>"
print output
else:
#В случае неудачного завершения команды, сообщаем о проблеме с соединением.
self.__ConnectionProblem( cmd_res )
def __PortOn( self ):
#Включаем сервер через ipmitool
cmd_res, _ = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power on" )
if cmd_res == 0:
#Если команда выполнилась успешно, то возвращаем через поток вывода новое состояние.
output = "<doc><port>"
output += "<identity>power</identity>"
output += "<admin_status>on</admin_status>"
output += "</port></doc>"
print output
else:
#В случае неудачного завершения команды, сообщаем о проблеме с соединением.
self.__ConnectionProblem( cmd_res )
def __PortReset( self ):
#Перезагружаем сервер через ipmitool
cmd_res, _ = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power reset" )
if cmd_res == 0:
#Если команда выполнилась успешно, то возвращаем через поток вывода новое состояние.
output = "<doc><port>"
output += "<identity>power</identity>"
output += "<admin_status>on</admin_status>"
output += "</port></doc>"
print output
else:
#В случае неудачного завершения команды, сообщаем о проблеме с соединением.
self.__ConnectionProblem( cmd_res )
def __Status( self ):
#Определяем состояние сервера через ipmitool
cmd_res, cmd_out = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power status" )
if cmd_res == 0:
#Если команда выполнилась успешно, то возвращаем через поток вывода новое состояние.
output = "<doc><port>"
output += "<identity>power</identity>"
#Описание отобразится в поле устройство в списке подключений сервера.
#Данный узел можно не указывать. В качестве описания будет использоваться слово Power.
output += "<description>Some IPMI</description>"
#Определяем состояние порта.
if "Chassis Power is on" in cmd_out or "Chassis Power Control: Up/On" in cmd_out:
output += "<admin_status>on</admin_status>"
elif "Chassis Power is off" in cmd_out or "Chassis Power Control: Down/Off" in cmd_out:
output += "<admin_status>off</admin_status>"
else:
#Если по выводу ipmitool не удалось определить соостояние порта, то сообщаем
#об ошибке обмена данными и завершаем выполнения метода.
self.__UnexpectedDataProblem( cmd_out )
return
output += "</port></doc>"
print output
else:
self.__ConnectionProblem( cmd_res )
def __IPMIToolStart( self ):
#Начало команды ipmitool. Ключ -E указывает на то,
#что пароль будет браться из переменных окружения.
return self.__IPMITool + " -H " + self.__IP + " -U " + self.__User + " -E "
def __ConnectionProblem( self, ipmi_res ):
#Возвращаем ошибку. В DCImanager будет зарегистрирована проблема.
output = "<doc><error>"
#Всего возможны два типа проблемы.
#connection — проблема с соединением и...
output += "<type>connection</type>"
output += "<text>ipmitool has returned " + str( ipmi_res ) + "</text>"
output += "</error></doc>"
print output
def __UnexpectedDataProblem( self, ipmi_out ):
output = "<doc><error>"
#... unexpected_data — проблема обмена данными с устройством.
output += "<type>unexpected_data</type>"
output += "<text>Unable to parse answer from ipmitool:\n" + ipmi_out + "</text>"
output += "</error></doc>"
print output
@staticmethod
def Info():
output = "<doc>"
#Тип устройства
output += "<type>IPMI</type>"
#Версия IPMI, отображаемая в выпадающем списке при создании подключения.
output += "<name>Custom IPMI v1.5 Handler</name>"
#Узел requirements в случае с IPMI не нужен.
output += "<supported_funcs>"
#DCImanager будет вызывать у обработчика IPMI только 3 функции:
#Статус устройства
output += "<status/>"
#Выключение порта
output += "<port_off/>"
#Включение
output += "<port_on/>"
#Перезагрузка
output += "<port_reset/>"
output += "</supported_funcs>"
output += "</doc>"
print output
__IPMITool = "/usr/bin/ipmitool"
__IP = ""
__User = ""
def main():
if len(sys.argv) > 1 and sys.argv[1] == "-info":
#Если есть ключ -info, то выдаём информацию об обработчике
IPMIhandler.Info()
else:
#Во всех остальных случаях читаем поток ввода и создаём объект обработчика,
#который выполнит обработку запроса от DCImanager.
request_str = sys.stdin.read()
IPMIhandler( request_str )
#Запуск основной функции
main()
Обработчик распределителя питания
Обработчик, реализующий управление питанием блейд-серверов IBM BladeServer (предоставлен одним из клиентов).
#!/usr/bin/python2.7
#coding=utf8mb4
import sys
import os
import xml.etree.ElementTree as xmlET
from pysnmp.entity.rfc3413.oneliner import cmdgen
from pysnmp.proto import rfc1902
def xpath_result( root, xpath ):
res = root.findall( xpath )
if len( res ) == 1:
return res[0].text
else:
return ""
#Исключения, которые могут произойти во время работы с устройством.
class ConnectionProblem( Exception ):
def __init__( self ):
#Всего возможны два типа проблем.
#connection — проблема с соединением и...
print "<doc><error><type>connection</type><text>Unable to connect. Check address or community string.</text></error></doc>"
class UnexpectedDataProblem( Exception ):
def __init__( self, msg ):
#... unexpected_data — проблема обмена данными с устройством.
print "<doc><error><type>unexpected_data</type><text>" + msg + "</text></error></doc>"
#Класс обработчика.
class PowerSimpleHandler:
def __init__( self, request_from_ifx ):
#Инициализация всех объектов, необходимых для SNMP-запроса.
self.__cmdGen = cmdgen.CommandGenerator()
xmlRoot = xmlET.fromstring( request_from_ifx )
self.__Community = cmdgen.CommunityData( xpath_result( xmlRoot, "./device_params/snmp_community" ), mpModel=0 )
self.__Target = cmdgen.UdpTransportTarget((xpath_result( xmlRoot, "./device_params/ip" ), 161))
#Определяем функцию, которую вызвал DCImanager и вызываем соотвествующий метод
func_name = xpath_result( xmlRoot, "./func" )
if func_name == "status":
self.__Status()
elif func_name == "port_off":
self.__PortOff( xpath_result( xmlRoot, "./port/identity" ) )
elif func_name == "port_on":
self.__PortOn( xpath_result( xmlRoot, "./port/identity" ) )
elif func_name == "port_reset":
self.__PortReset( xpath_result( xmlRoot, "./port/identity" ) )
def __PortOff( self, ident ):
#Выключаем порт
self.__SnmpSet( "1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.7." + ident, rfc1902.Integer( 0 ) )
#Сообщаем панели новое состояние порта
output = "<doc><port>"
output += "<identity>" + ident + "</identity>"
output += "<admin_status>off</admin_status>"
output += "</port></doc>"
print output
def __PortOn( self, ident ):
#Включаем порт
self.__SnmpSet( "1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.7." + ident, rfc1902.Integer( 1 ) )
#Сообщаем панели новое состояние порта
output = "<doc><port>"
output += "<identity>" + ident + "</identity>"
output += "<admin_status>on</admin_status>"
output += "</port></doc>"
print output
def __PortReset( self, ident ):
#Перезагружаем порт
self.__SnmpSet( "1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.8." + ident, rfc1902.Integer( 1 ) )
#Сообщаем панели новое состояние порта
output = "<doc><port>"
output += "<identity>" + ident + "</identity>"
output += "<admin_status>on</admin_status>"
output += "</port></doc>"
print output
def __Status( self ):
ports = {}#Составляем словарь портов, где ключом будет идентификатор порта.
#Определяем реальное состояние портов.
for ident, admin_status in self.__SnmpWalk( "1.3.6.1.4.1.2.3.51.2.22.1.5.1.1.4." ).iteritems():
ports[ident] = self.DevicePort(ident)
if admin_status:
ports[ident].AdminStatus = "on"
else:
ports[ident].AdminStatus = "off"
#Читаем названия блейд-серверов
for ident, descr in self.__SnmpWalk( "1.3.6.1.4.1.2.3.51.2.22.1.5.1.1.6." ).iteritems():
#Если блейд-сервера нет на месте, меняем (No name) на Not installed
if descr == "(No name)":
descr = "Not installed"
ports[ident].Description = descr
#Сообщаем полный список портов панели.
output = "<doc>"
for port in ports.values():
output += "<port>"
output += "<identity>" + port.Identity + "</identity>"
output += "<description>" + port.Description + "</description>"
output += "<admin_status>" + port.AdminStatus + "</admin_status>"
output += "</port>"
output += "</doc>"
print output
#Установка значения mib. Переданное значение должно быть приведено к типу в соответствии с rfc1902
def __SnmpSet( self, mib, value ):
errorIndication, errorStatus, errorIndex, varBinds = self.__cmdGen.setCmd( self.__Community,
self.__Target,
( cmdgen.MibVariable( mib ), value ) )
if errorIndication:
raise ConnectionProblem
#Обход дерева заданного mib.
def __SnmpWalk( self, mib ):
errorIndication, errorStatus, errorIndex, varBindTable = self.__cmdGen.nextCmd( self.__Community,
self.__Target,
cmdgen.MibVariable( mib ) )
if errorIndication:
raise ConnectionProblem
result_map = {}
for varBindTableRow in varBindTable:
for name, val in varBindTableRow:
result_map[name.prettyPrint().rpartition( "." )[2]] = val
return result_map
class DevicePort:
def __init__( self, ident ):
self.Identity = ident
Identity = ""
Description = "Blade"
AdminStatus = "unknown"
@staticmethod
def Info():
output = "<doc>"
#Тип устройства
output += "<type>PDU</type>"
output += "<name>Blade Power</name>"
#Используем SNMP v1
output += "<requirements>"
output += "<snmpv1/>"
output += "</requirements>"
#DCImanager будет вызывать у обработчика PDU только 4 функции:
output += "<supported_funcs>"
#Получение списка портов
output += "<status/>"
#Выключение порта
output += "<port_off/>"
#Включение порта
output += "<port_on/>"
#Перезагрузка порта
output += "<port_reset/>"
output += "</supported_funcs>"
output += "</doc>"
print output
__cmdGen = None
__Target = None
__Community = None
def main():
if len(sys.argv) > 1 and sys.argv[1] == "-info":
#Если есть ключ -info, то выдаём информацию об обработчике
PowerSimpleHandler.Info()
else:
#Во всех остальных случаях читаем поток ввода и создаём объект обработчика,
#который выполнит обработку запроса от DCImanager.
request_str = sys.stdin.read()
PowerSimpleHandler( request_str )
#Запуск основной функции
main()
Обработчик UPS
Данный обработчик имитирует ответ от UPS.
Запуск скрипта начинается с вызова функции main().
#!/usr/bin/python2.7
#coding=utf8mb4
import sys
import os
import xml.etree.ElementTree as xmlET
def xpath_result( root, xpath ):
res = root.findall( xpath )
if len( res ) == 1:
return res[0].text
else:
return ""
#Класс обработчика.
class UPSSimpleHandler:
def __init__( self, request_from_ifx ):
#Определяем функцию, которую вызвал DCImanager, и вызываем соответствующий метод
xmlRoot = xmlET.fromstring( request_from_ifx )
func_name = xpath_result( xmlRoot, "./func" )
if func_name == "status":
self.__Status()
def __Status( self ):
output = "<doc>"
#статус — OK, Warning, Critical
output += " <eq_param name='device_status'>"
output += " <strvalue>OK</strvalue>"
output += " </eq_param>"
#Входной вольтаж. Если меньше 100 — то появится предупреждение
output += " <eq_param name='input_voltage_line_A'>"
output += " <floatvalue>220</floatvalue>"
output += " </eq_param>"
output += " <eq_param name='input_voltage_line_B'>"
output += " <floatvalue>220</floatvalue>"
output += " </eq_param>"
#Нагрузка (%)
output += " <eq_param name='output_load_line_A'>"
output += " <floatvalue>60</floatvalue>"
output += " </eq_param>"
output += " <eq_param name='output_load_line_B'>"
output += " <floatvalue>60</floatvalue>"
output += " </eq_param>"
#Выходная мощность (KВт)
output += " <eq_param name='output_power_line_A'>"
output += " <floatvalue>49.23</floatvalue>"
output += " </eq_param>"
output += " <eq_param name='output_power_line_B'>"
output += " <floatvalue>40.7</floatvalue>"
output += " </eq_param>"
#Потребляемая мощность (KВт)
output += " <eq_param name='input_power_line_A'>"
output += " <floatvalue>49.23</floatvalue>"
output += " </eq_param>"
output += " <eq_param name='input_power_line_B'>"
output += " <floatvalue>40.7</floatvalue>"
output += " </eq_param>"
#Заряд батареи (мин.)
output += " <eq_param name='battary_time_remains'>"
output += " <floatvalue>24</floatvalue>"
output += " </eq_param>"
output += "</doc>"
print output
@staticmethod
def Info():
output = "<doc>\n"
#Тип устройства
output += " <type>UPS</type>\n"
output += " <name>custom UPS</name>\n"
#Используем SNMP v1
output += " <requirements>\n"
output += " <snmpv1/>\n"
output += " </requirements>\n"
#DCImanager будет вызывать у обработчика UPS только одной функции:
output += " <supported_funcs>\n"
#опрос состояния
output += " <status/>\n"
output += " </supported_funcs>\n"
output += "</doc>"
print output
def main():
if len(sys.argv) > 1 and sys.argv[1] == "-info":
#Если есть ключ -info, то выдаём информацию об обработчике
UPSSimpleHandler.Info()
else:
#Во всех остальных случаях читаем поток ввода и создаём объект обработчика,
#который выполнит обработку запроса от DCImanager.
request_str = sys.stdin.read()
UPSSimpleHandler( request_str )
#Запуск основной функции
main()