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

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


Внешний обработчик устройств — исполняемый файл: скрипт или бинарный файл. Обмен данными между DCImanager и обработчиком осуществляется через stdout и stdin. Данные передаются в виде XML.

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

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


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

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

testhandler -info
BASH

На 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>
BASH

<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). Параметры позволят пользователю при регистрации в панели управления устройства, которое использует этот обработчик, увидеть понятные поля ввода, соответствующие описанным протоколам.

<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>
BASH


Например, вызов функции 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>
BASH



<func> — имя запрашиваемой функции.

<device_params> — параметры устройства, чаще всего данные для доступа.

<port> — порт, для которого выполняется функция.


Список параметров устройств:

Имя параметраОписание

Допустимые значения

ipIP-адрес устройствастрока
snmp_verВерсия SNMP, которая будет использоваться

SNMP v1, SNMP v2c, SNMP v3

snmp_communitySNMP 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строка


Список параметров портов:

Имя параметраОписание

Допустимые значения

identityИдентификатор порта, уникальный внутри устройствастрока
speedСкорость, которую необходимо установить на порту с идентификатором identity

auto, auto10100, 10mbps, 100mbps, 1gbps, 10gbps

duplexРежим, который необходимо установить на порту с идентификатором identityauto, half, full
vlan_idVLAN, который необходимо установить на порту с идентификатором 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_vlanMapped VLAN, который необходимо установить на порту с идентификатором identity

от 1 до 4095.

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


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

Получив и обработав входные данные, обработчик должен вернуть результат, также в виде 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>
BASH

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

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

<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_idVLAN, установленный на порту коммутатора.от 1 до 4095
vlan_nameПсевдоним VLAN, установленного на порту коммутатора.

строка без пробелов на латинице

macMAC-адрес, обнаруженный на порту коммутатора. Данных записей для каждого <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>
BASH

Если в выходном 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=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()
BASH

Обработчик 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()
BASH

Обработчик распределителя питания


Обработчик, реализующий управление питанием блейд-серверов 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()
BASH


Обработчик 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()
BASH