Пользовательские обработчики устройств

Материал из ISPWiki
Перейти к: навигация, поиск

Иерархия: DCImanager -> Оборудование
DCImanager Enterprise -> Оборудование

Принципы работы

Внешний обработчик устройств (далее обработчик) представляет из себя исполняемый файл, независимо от того, скрипт ли это или бинарный файл.Обмен данными между DCImanager и обработчиком происходит через stdout и stdin. Данные передаются в виде XML.

При запуске DCImanager по очереди запускает исполняемые файлы из /usr/local/mgr5/var/dcihandlers с ключом -info (По умолчанию директория /usr/local/mgr5/var/dcihandlers не существует, ее нужно создать). Каждый обработчик выводит через stdout XML, который описывает, какое именно устройство данный исполняемый файл обрабатывает и какие функции оно поддерживает. Таким образом, DCImanager регистрирует у себя новый обработчик, который будет доступен при создании новых устройств.

Протокол взаимодействия

Регистрация обработчика

DCImanager запустит ваш обработчик с флагом -info

testhandler -info

На stdin обработчика ничего передаваться не будет, однако обработчик должен передать XML, описывающий устройство, через свой stdout. Пример XML:

<doc>
<type>Switch</type>                     - Тип устройства
<name>First External Handler</name>     - Имя, которое будет отображаться в DCImanager
<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 с поддержкой этой опции, то не будет доступна вкладка со статистикой по питанию и колонки потребления питания в списках серверов и стоек.

Можно использовать любую комбинацию вышеперечисленных параметров.

ВНИМАНИЕ: DCImanager не реализует для внешних обработчиков никаких протоколов (SNMP, SSH, TELNET). Выше перечисленные параметры лишь позволят пользователю (человеку, который будет регистрировать устройство на основе вашего обработчика в DCImanager) увидеть понятные для него поля ввода, соответствующие описанным протоколам.

<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 со всеми необходимыми данными: Пример 1 - Вызов функции 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>

Пример 2 - Вызов функции 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>

<func> - имя запрашиваемой функции <device_params> - Параметры устройства, чаще всего данные для доступа. Могут включать в себя:

Имя параметра Описание Допустимые значения
ip IP-адрес устройства строка
snmp_ver Версия SNMP которая будет использоваться SNMP v1, SNMP v2c, SNMP v3
snmp_community SNMP Community строка без пробелов на латинице
snmp_user Пользователь SNMP (только при использовании SNMP v3) строка
snmp_auth_phrase Фраза для аутентификации SNMP (только при использовании SNMP v3) строка
snmp_priv_phrase Приватная фраза для SNMP (только при использовании SNMP v3) строка
snmp_auth_level Уровень аутентификации SNMP noauth, authnopriv, authpriv
telnet_pass Пароль для доступа по Telnet строка
telnet_user Имя пользователя для доступа по Telnet строка
ssh_pass Пароль для доступа по SSH строка
ssh_user Имя пользователя для доступа по SSH строка
pass Пароль для доступа к IPMI строка
user Имя пользователя для доступа к IPMI строка

<port> - порт, для которого выполняется функция, может включать в себя:

Имя параметра Описание Допустимые значения
identity Идентификатор порта, уникальный внутри устройства строка
speed Скорость, которую необходимо установить на порту с идентификатором identity  auto, auto10100, 10mbps, 100mbps, 1gbps, 10gbps
duplex Режим, который необходимо установить на порту с идентификатором identity  auto, half, full
vlan_id VLAN который необходимо установить на порту с идентификатором identity  от 1 до 4095
vlan_name Псевдоним VLAN-а который необходимо установить на порту с идентификатором identity  строка без пробелов на латинице
is_trunk Вкл./Выкл. режим trunk на порту с идентификатором identity  on - если нужно включить trunk, любая другая строка(мы обычно используем off) - если нужно отключить trunk.
trunk_members члены trunk на порту с идентификатором identity  строка с числами(VLAN ID) разделенными запятой без пробелов
vlan_pvlan тип PVLAN основного VLAN на порту с идентификатором identity  0 - отсутствие PVLAN (обычный VLAN),

1 - обнаружены разные типы PVLAN на разных коммутаторах (такого не должно быть в корректно настроенной сети),

2 - первичный (primary),

3 - изолированный (isolated)

mapped_vlan Mapped VLAN который необходимо установить на порту с идентификатором identity  от 1 до 4095,

-1 - не установлен

Выходные данные

Получив и обработав входные данные, обработчик должен вернуть результат, также в виде XML Пример 1 - ответ обработчика "маленького" коммутатора на 4 порта, на функцию 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>

Пример 2 - ответ на функцию port_off для порта с идентификатором "1".

<doc>
 <port>
  <identity>1</identity>
  <admin_status>off</admin_status>
 </port>
</doc>

<hostname> - актуально только для коммутаторов, содержит hostname коммутатора.

<port> - содержит описание разных параметров порта. Может включать в себя:

Имя параметра Описание Допустимые значения
identity Идентификатор порта, унакальный для устройства строка
description Описание порта устройства строка
admin_status Состояние порта, заданное администратором. on, off, unknown
oper_status Реальное состояние порта (например link порта коммутатора) on, off, unknown
speed Скорость порта коммутатора
auto, auto10100, 10mbps, 100mbps, 1gbps, 10gbps
duplex Режим порта коммутатора auto, half, full
rxbytes Счётчик входящих байт, прошедших через порт коммутатора числовое значение
txbytes Счётчик исодящих байт, прошедших через порт коммутатора числовое значение
rxpackets Счётчик входящих пакетов, прошедших через порт коммутатора числовое значение
txpackets Счётчик исходящих пакетов, прошедших через порт коммутатора числовое значение
power Счётчик потребления питания в Вт*ч на порту распределителя питания числовое значение
vlan_id VLAN установленный на порту коммутатора от 1 до 4095
vlan_name Псевдоним VLAN-а установленного на порту коммутатора строка без пробелов на латинице
mac MAC-адрес обнаруженный на порту коммутатора. Данных записей для каждого <port> может быть несколько. MAC-адрес в одном из форматов: 1A2B3C4D5E6F 1A:2B:3C:4D:5E:6F 1a2b.3c4d.5e6f 1A-2B-3C-4D-5E-6F
is_trunk Вкл./Выкл. режим trunk на порту с идентификатором identity  on - если нужно включить trunk, любая другая строка(мы обычно используем off) - если нужно отключить trunk.
trunk_members члены trunk на порту с идентификатором identity  строка с числами(VLAN ID) разделенными запятой без пробелов

Также обработчик может вернуть ошибку, пример:

<doc>
 <error>
  <type>connection</type>
  <text>Failed to open connection to 12.35.56.22</text>
 </error>
</doc>

Если в выходном XML есть блок <error> то всё остальное содержимое будет игнорироваться, операция завершится, кроме того будет создано уведомление об ошибке во время работы с устройством.

  • <type> - тип ошибки, может принимать значения "connection" и "unexpected_data"
    • connection - ошибка соединения с устройством
    • unexpected_data - ошибка во время обмена данными
  • <text>- текст ошибки, будет фигурировать в уведомлении.

Соответствие входных и выходных данных

Коммутаторы (<type>Switch</type>)

Функция Описание Входные данные Выходные данные
status должна вернуть полную информацию о состоянии всех портов коммутатора данные для доступа к устройству(<device_params>) описание портов 
statistics должна вернуть состояние счётчиков на портах устройства данные для доступа к устройству(<device_params>) счётчики портов устройства
port_on, port_off должна включить/выключить порт коммутатора данные для доступа к устройству(<device_params>) и идентификатор порта новое состояние порта
port_speed должа устнаовить скорость порта коммутатора данные для доступа к устройству(<device_params>), идентификатор и скорость порта новое состояние порта
port_duplex должа установить режим порта коммутатора данные для доступа к устройству(<device_params>), идентификатор и желаемый режим порта новое состояние порта
set_vlan должна устнаовить VLAN порта коммутатора данные для доступа к устройству(<device_params>), идентификатор порта, идетификатор VLAN и псевдоним VLAN, параметр is_trunk и trunk_members новое состояние порта

Распределители питания (<type>PDU</type>)

Функция Описание Входные данные Выходные данные
status должна вернуть полную информацию о состоянии всех портов PDU данные для доступа к устройству(<device_params>) описание портов 
statistics должна вернуть состояние счётчиков на портах устройства данные для доступа к устройству(<device_params>) счётчики портов устройства
port_on, port_off, port_reset должна включить/выключить/сбросить порт PDU данные для доступа к устройству(<device_params>) и идентификатор порта новое состояние порта

IPMI (<type>IPMI</type>)

Хоть у IPMI и нет портов т.к. он интегрирован в сервер, в DCImanager есть специально зарезервированный порт такого типа устройств. Его идентификатор должен быть "power" с учётом регистра. 

Функция Описание Входные данные Выходные данные
status должна вернуть полную информацию о состоянии  питания сервер данные для доступа к устройству(<device_params>) состояние порта "power"
port_on, port_off, port_reset должна включить/выключить/сбросить питание сервера данные для доступа к устройству(<device_params>) новое состояние порта  "power"

UPS (<type>UPS</type>)

Для UPS опрашивается только состояние устройства. Данные полученные от UPS отображаются в списке источников бесперебойного питания.

Функция Описание Входные данные Выходные данные
status должна вернуть полную информацию о состоянии  UPS данные для доступа к устройству(<device_params>) Нагрузка, вольтаж, на сколько хватит батареи

Примеры обработчиков

Обработчик коммутатора

Данный обработчик аналогичен поставляемому с DCImanager обработчику SNMP common. Управление коммутатором осуществляется посредством SNMPv2c с использованием библиотеки pysnmp 4ой версии.

#!/usr/bin/python2.7
#coding=utf8
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=utf8
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=utf8
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 IBMPortStatusToIFXStr( val ):
		return "on" if val == 1 else "off"

#Класс обработчика.
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 ):
		output = "<doc>"
		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)
			ports[ident].AdminStatus = IBMPortStatusToIFXStr( admin_status )
		#Читаем названия блейдов
		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=utf8
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()