DCImanager uses modules (service handlers) to manage equipment. A full list of handlers that the control panel supports can be found in the article Supported equipment. This article describes how to add a custom handler.

Overview 


An external device handler is an executable file: a script or binary file. Data exchange between DCImanager and a handler is through stdout and stdin. The data is transmitted in an XML-file.

When DCImanager starts, it launches the executable files from var / dcihandlers with the -info key. Each handler sends an XML to stdout. The XML describes what device this executable file processes and what functions it supports. DCImanager registers a new handler that will be available when adding new devices.

How it works


Registering a new handler 

DCImanager starts the handler with the -info option

testhandler -info
BASH

Nothing will be sent to the handler stdin, however, the handler must pass the XML that describes the device to its stdout. The XML example: 

<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> — a device type: Switch, PDU, IPMI, UPS.

<name> — a device name that will be displayed in DCImanager.

<requirements> — a list of input data for the handler. The list defines what data DCImanager will ask to enter to add a new device, and what input data will be sent to the handler while performing operations with the device:

  • <snmpv1/> — SNMPv1 will be available on the device creation form. SNMP version and Community will be sent to the handler;
  • <snmpv2c/> — SNMPv2c will be available on the device creation form. SNMP version and Community will be sent to the handler;
  • <snmpv3/> — SNMPv3 will be available on the device creation form. SNMP version, the username, auth phrase, priv phrase, and authentication level will be sent to the handler; 
  • <ssh/> — the form will contain the SSH tab. The username and password for SSH access will be sent to the handler; 
  • <telnet/> — the form will contain the Telnet tab. The username and password for Telnet access will be sent to the handler; 
  • <can_collect_power/> — must be specified only for PDU and UPS. If the control panel doesn't contain a PDU or UPS that support this option, power usage statistics information and columns in the list of servers and racks won't be displayed. 

You can use any combinations of the above parameters. 

Note

DCImanager doesn't implement protocols for external handlers (SNMP, SSH, TELNET). The parameters allow a user (who registers a device based on your handler in DCImanager) to see intelligible input fields that correspond to the described protocols.

<supported_funcs>

The list of device functions:

  • <status/> - the handler can read the information about the device (mandatory); 
  • <statistics/> - the handler can collect traffic statistics (for switches) or power consumption (for some PDU).
  • <port_on/> - the handler  can enable device port;
  • <port_off/> - the handler   can disable device port;
  • <port_reset/> - the handler  can reset the device port (mainly for power ports);
  • <port_speed/> - the handler  can set the device port speed (only for switches);
  • <port_duplex/> - the handler can set the device port mode (only for switches);
  • <set_vlan/> - the handler can set the device port VLAN (only for switches);
  • <mac_list/> - the handler can display the list of MAC-addresses of device ports (only for switches).

Device function call 

Input data

When a handler function is called (e.g., to disable a PDU port), the system calls the executable file without parameters and sends the XML with necessary data through its stdin. Example 1 - call of the status function, which should show information about all statuses of all device's ports.

The following is the example of the status function call. The function returns information about device port statuses: 

<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


The port_on function call. The function enables a certain port of the device:  

<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> — the name of the function to call.

<device_params> — device parameters, usually authentication information.

<port> — the port associated with the function.


Device parameters:

Parameter nameDescription

Possible values

ipDevice IP addressstring
snmp_verSNMP version that will be used

SNMP v1, SNMP v2c, SNMP v3

snmp_communitySNMP Community

string without spaces in the Latin alphabet letters

snmp_userSNMP user (only in case of using SNMP v3)string
snmp_auth_phraseA phrase for SNMP authentication (only in case of using SNMP v3)string
snmp_priv_phraseA private phrase for SNMP (only in case of using SNMP v3)string
snmp_auth_levelSNMP authentication level

noauth, authnopriv, authpriv

telnet_passPassword for Telnet accessstring
telnet_userUsername for Telnet accessstring
ssh_passPassword for SSH accessstring
ssh_userUsername for SSH accessstring
passIPMI passwordstring
userIPMI usernamestring


Port parameters:

Parameter nameDescription

Possible values

identityPort unique identifierstring
speedPort speed

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

duplexPort modeauto, half, full
vlan_idPort VLAN1 to 4095
vlan_namePort VLAN alias

string without spaces in the Latin alphabet letters

is_trunkTrunk on/off on the port

on — enable trunk.

Another string (e.g. off) — disable trunk.

trunk_membersTrunk members on the port

string with figures (VLAN ID) without spaces comma separated

vlan_pvlanPVLAN type of the primary VLAN on the port

0 — no PVLAN (common VLAN).

1 — a different type of PVLAN on different switches ).

2 — primary
3 — isolated

mapped_vlanMapped VLAN on the port

1 to 4095.

-1 — not set.


Output data

After the handler has received and processed the input data, it must return a result (also in XML). 

E.g. the following is the response from the switch handler with four ports to the status functionIt describes the ports and their states: 

<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

The responses to the port_off function for the port with the identifier "1".

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

<hostname> — only for switches, contains the switch hostname.

<port> — contains description of port parameters.


Port parameters:

Parameter nameDescription

Possible values

identityDevice port identifier.string
descriptionDevice port description.string
admin_statusPort status set by an administrator.on, off, unknown
oper_statusReal port state (e.g. switch port link).on, off, unknown
speedSwitch port speed.

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

duplexSwitch port mode.auto, half, full
rxbytesthrough the switch port 

numeric value

txbytesthrough the switch port 

numeric value

rxpacketsThe counter of incoming bytes passed through the switch port

numeric value

txpacketsThe counter of outgoing packets passed through the switch port

numeric value

powerThe counter of power consumption in Watts on the PDU port.

numeric value

vlan_idVLAN on the switch port.from 1 to 4095
vlan_nameVLAN alias on the switch port.

string without spaces in the Latin alphabet letters

macMAC-address on the switch port. There can be several records for every <port>

MAC-address in one of the following formats: 1A2B3C4D5E6F 1A:2B:3C:4D:5E:6F 1a2b.3c4d.5e6f 1A-2B-3C-4D-5E-6F

is_trunkTrunk on/off on the port

on — enable trunk.

Another string (e.g. off)

— disable trunk.

trunk_membersTrunk members on the port.

string with figures (VLAN ID), without spaces comma separated


The handler may return an error. E.g:

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

If the output XML contains the <error> section, all other content will be ignored and the operation will be terminated. The system will generate the error report.

<type> — error type. type of an error. Possible values: "connection" and "unexpected_data":

  • connection — device connection error
  • unexpected_data — data exchange error.

<text> — error text in the report.

Matching inputs and outputs


Switches (<type>Switch</type>)

FunctionDescriptionInput data

Output data

statusMust return the complete information about all switch portsDevice access data (<device_params>)

Port description

statisticsMust return the counter states on device portsDevice access data(<device_params>)

Device port counter

port_on, port_offMust enable/disable the switch portDevice access data (<device_params>) and port identifier

New state of the port

port_speedMust set the switch port speedDevice access data (<device_params>), port identifier and speed

New state of the port

port_duplexMust set the switch port modeDevice access data (<device_params>), port identifier and mode

New state of the port

set_vlanMust set the switch port VLANDevice access data (<device_params>), port identifier, VLAN identifier and alias, is_trunk and trunk_members parameter.

New state of the port


PDU (<type>PDU</type>)

FunctionDescriptionInput data

Output data

statusMust return the complete information about all PDU portsDevice access data (<device_params>)

Port description

statisticsMust return the counter states on device portsDevice access data (<device_params>)

Device port counter

port_on, port_off, port_resetMust enable / disable /reset PDU portDevice access data (<device_params>) and port identifier

New state of the port


IPMI (<type>IPMI</type>)

Although an IPMI doesn't have ports because it's integrated to the server, DCImanager has a reserved port for such devices. Its identifier must be "power" (case-sensitive).  

FunctionDescriptionInput data

Output data

statusMust return the complete information about server power supplyDevice access data (<device_params>)

"Power" port state

port_on, port_off, port_resetMust enable/disable /reset server power supplyDevice access data (<device_params>)

New state of the "power" port


UPS (<type>UPS</type>)

Only device status can be received for a UPS. The data from UPS are displayed in the UPS list.  

FunctionDescriptionInput data

Output data

statusMust return the complete information about UPS status.Device access data (<device_params>)

Load, voltage, battery life

Examples of handler 


Switch

This handler is similar to the SNMP common handler in DCImanager. Switch management is performed by SNMPv2c library pysnmp 4v. 

#!/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 ""

#Exceptions that can occur during the work with device.
class ConnectionProblem( Exception ):
	def __init__( self ):
		#Two types of possible issues.
		#connection — connection problem and...
		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 — data exchange problem.
		print "<doc><error><type>unexpected_data</type><text>" + msg + "</text></error></doc>"

#Different functions that convert values from the panel to the devices and back. 
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

#Handler class.
class SwitchSimpleHandler:
	def __init__( self, request_from_ifx ):
		#Initialization of the objects for the SNMP query.
		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))
		#Define the function that DCImanager called and call the corresponding method
		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 ):
		#Disable the port
		self.__SnmpSet( "1.3.6.1.2.1.2.2.1.7." + ident, rfc1902.Integer( 2 ) )
		
		#Inform the panel about a port sew status
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<admin_status>on</admin_status>"
		output += "</port></doc>"
		print output

	def __PortOn( self, ident ):
		#Enable the port
		self.__SnmpSet( "1.3.6.1.2.1.2.2.1.7." + ident, rfc1902.Integer( 1 ) )
		
		#Inform the panel about a port sew status
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<admin_status>on</admin_status>"
		output += "</port></doc>"
		print output

	def __PortSpeed( self, ident, val ):
		#Receive a list of indexes...
		indexes = self.__SnmpWalk( "1.3.6.1.4.1.9.5.1.4.1.1.11" )
		#To set new speed for the port.
		#Find the key by the value.
		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 ) ) )

		#Inform the panel about a port sew status.
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<speed>" + val + "</speed>"
		output += "</port></doc>"
		print output

	def __PortDuplex( self, ident, val ):
		#Receive a list of indexes...
		indexes = self.__SnmpWalk( "1.3.6.1.4.1.9.5.1.4.1.1.11" )
		#To set a new mode for the port.
		#Find the key by the value.
		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 ) ) )

		#Inform the panel about a port sew status.
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<duplex>" + val + "</duplex>"
		output += "</port></doc>"
		print output

	def __Status( self ):
		output = "<doc>"
		ports = {}#Add a dictionary of ports where the keys is the port identifier.
		#Define the port description.
		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
		#Define the port statuses set by the administrator.
		for ident, adm_status in self.__SnmpWalk( "1.3.6.1.2.1.2.2.1.7" ).iteritems():
			ports[ident].AdminStatus = CiscoPortStatusToIFXStr( adm_status ) 
		#Define the real port statuses. 
		for ident, oper_status in self.__SnmpWalk( "1.3.6.1.2.1.2.2.1.8" ).iteritems():
			ports[ident].OperStatus = CiscoPortStatusToIFXStr( oper_status )
		
		#Define the current speed and mode of the ports.
		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 )

		#Inform the panel about a full list of ports.
		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

	#Set the mib value. The value must correspond to 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

	#Traverse of the tree set  by 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>"
		#Device type
		output += "<type>Switch</type>" 
		output += "<name>SNMP Switch Handler</name>"
		#Use SNMP v2c
		output += "<requirements>"
		output += "<snmpv2c/>"
		output += "</requirements>"
                #DCImanager will call only 4 function from a switch handler: 
		output += "<supported_funcs>"
                #Retrieve a list of ports
		output += "<status/>"
                #Disable a port
		output += "<port_off/>"
                #Enable a port
		output += "<port_on/>"
                #Change port mode
		output += "<port_duplex/>"
                #Change port mode and speed
		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":
		#If there is the -info key, send the handler information
		SwitchSimpleHandler.Info()
	else:
		#In all other cases read the input thread and create the handler object
		#that will process the request from DCImanager.
		request_str = sys.stdin.read()
		SwitchSimpleHandler( request_str )

#Start the main function
main()
BASH

IPMI 


This handler allows managing devices with IPMI v1.5 with the ipmitool utility written on python 2.7.

Start the script by calling the function 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 )
		#Receive the IPMI IP address
		self.__IP = xpath_result( xmlRoot, "./device_params/ip" )
		#Receive the IPMI user who can manage the device.
		self.__User = xpath_result( xmlRoot, "./device_params/user" ).replace( "`", "\\`" )
		#For security reasons we will send a password in ipmitool through the environment variable.
		os.putenv( "IPMI_PASSWORD", xpath_result( xmlRoot, "./device_params/pass" ) )

		#Define the function that DCImanager called and call the corresponding method
		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 ):
		#Disable the server through ipmitool
		cmd_res, _ = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power off" )

		if cmd_res == 0:
			#If the command completed successfully, return a new status.
			#For IPMI the port identity must be power.
			output = "<doc><port>"
			output += "<identity>power</identity>"
			output += "<admin_status>off</admin_status>"
			output += "</port></doc>"
			print output
		else:
			#Should the command fail, inform about the connection problem.
			self.__ConnectionProblem( cmd_res )

	def __PortOn( self ):
		#Enable the server through ipmitool
		cmd_res, _ = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power on" )
		
		if cmd_res == 0:
			#If the command completed successfully, return a new status.
			output = "<doc><port>"
			output += "<identity>power</identity>"
			output += "<admin_status>on</admin_status>"
			output += "</port></doc>"
			print output
		else:
			##Should the command fail, inform about the connection problem.
			self.__ConnectionProblem( cmd_res )

	def __PortReset( self ):
                #Reboot the server through ipmitool
		cmd_res, _ = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power reset" )
		if cmd_res == 0:
			#If the command completed successfully, return a new status.
			output = "<doc><port>"
			output += "<identity>power</identity>"
			output += "<admin_status>on</admin_status>"
			output += "</port></doc>"
			print output
		else:
			##Should the command fail, inform about the connection problem.
			self.__ConnectionProblem( cmd_res )

	def __Status( self ):
		#Define the server status through ipmitool
		cmd_res, cmd_out = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power status" )

		if cmd_res == 0:
			#If the command completed successfully, return a new status.
			output = "<doc><port>"
			output += "<identity>power</identity>"
			#Description will be shown in the Device field of the Server connections list.
			#You may not specify this node. Power will be used as its description.
			output += "<description>Some IPMI</description>"
			#Define a port status.
			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:
				#If the port status cannot be defined by the ipmitool output, inform
				#about the data interchange error and terminate execution of the method.
				self.__UnexpectedDataProblem( cmd_out )
				return
			output += "</port></doc>"
			print output
		else:
			self.__ConnectionProblem( cmd_res )

	def __IPMIToolStart( self ):
		#Beginning of the ipmitool command. The -E key defines that
		#the password will be taken from the environment variables.
		return self.__IPMITool + " -H " + self.__IP + " -U " + self.__User + " -E "

	def __ConnectionProblem( self, ipmi_res ):
		#Return the error message. DCImanager will register the issue.
		output = "<doc><error>"
		#There can be two types of issues.
		#connection — connection problem and...
		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 — data exchange problem.
		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>"
		#Device type
		output += "<type>IPMI</type>" 
		#IPMI version displayed in the drop-down list.
		output += "<name>Custom IPMI v1.5 Handler</name>"
		#You don't need to specify the requirements node for IPMI.
		output += "<supported_funcs>"
		#DCImanager will call only 3 functions from the IPMI handler:
		#Device status
		output += "<status/>"
		#Disable the port
		output += "<port_off/>"
		#Enable
		output += "<port_on/>"
		#Reboot 
		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":
		#if there is the -info key, send the handler information
		IPMIhandler.Info()
	else:
		#In all other cases read the input thread and create the handler object
		#that will process the request from DCImanager.
		request_str = sys.stdin.read()
		IPMIhandler( request_str )

#Start the main function
main()
BASH

PDU 


A handler for IBM BladeServer that allows managing blade-server power (from one of our clients). 

#!/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 ""

#Exceptions that can occur during the work with device..
class ConnectionProblem( Exception ):
	def __init__( self ):
	#Two types of possible issues.
	#connection — connection problem and...
		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 — data exchange problem.
		print "<doc><error><type>unexpected_data</type><text>" + msg + "</text></error></doc>"

#Different functions that convert values from the panel to the devices and back. 
	def IBMPortStatusToIFXStr( val ):
		return "on" if val == 1 else "off"

#Handler calss.
class PowerSimpleHandler:
	def __init__( self, request_from_ifx ):
	#Initialization of the objects for the SNMP query.
		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))
	#Define the function that DCImanager called and call the corresponding method
		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 ):
	#Disable the port
		self.__SnmpSet( "1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.7." + ident, rfc1902.Integer( 0 ) )

	#Inform the panel about a port sew status
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<admin_status>off</admin_status>"
		output += "</port></doc>"
		print output

	def __PortOn( self, ident ):
	#Enable the port
		self.__SnmpSet( "1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.7." + ident, rfc1902.Integer( 1 ) )

	#Inform the panel about a port sew status
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<admin_status>on</admin_status>"
		output += "</port></doc>"
		print output

	def __PortReset( self, ident ):
	#Reboot the port
		self.__SnmpSet( "1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.8." + ident, rfc1902.Integer( 1 ) )

	#Inform the panel about a port sew status
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<admin_status>on</admin_status>"
		output += "</port></doc>"
		print output

	def __Status( self ):
		output = "<doc>"
		ports = {}#Add a port dictionary where the key is the port identifier..
		#Define the real port status.
		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 )
		#Read the names of blade-servers
		for ident, descr in self.__SnmpWalk( "1.3.6.1.4.1.2.3.51.2.22.1.5.1.1.6." ).iteritems():
		#If the blade-sever is missing, change No name into Not installed
			if descr == "(No name)":
				descr = "Not installed"
				ports[ident].Description = descr

#Inform the panel about a full list of ports.
		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

#Set the mib value. The value must correspond to 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

#Traverse of the tree set  by 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>"
		#Device type
		output += "<type>PDU</type>" 
		output += "<name>Blade Power</name>"
		#Use SNMP v1
		output += "<requirements>"
		output += "<snmpv1/>"
		output += "</requirements>"
		#DCImanager will call only 4 functions from a PDU:
		output += "<supported_funcs>"
		#Retrieve a list of ports
		output += "<status/>"
		#Disable the port
		output += "<port_off/>"
		#Enable the port
		output += "<port_on/>"
		#Reboot the port
		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":
#If there is the -info key, show the handler information
		PowerSimpleHandler.Info()
	else:
	#In all other cases read the input thread and create the handler object
	#that will process the request from DCImanager.
		request_str = sys.stdin.read()
		PowerSimpleHandler( request_str )

#Start the main function
main()
BASH

UPS


This handler imitates responses from a UPS.
To start the script, call the function 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 ""

#Handler class.
class UPSSimpleHandler:
	def __init__( self, request_from_ifx ):
		#Define a function that DCImanager called and call the corresponding method
			xmlRoot = xmlET.fromstring( request_from_ifx )
			func_name = xpath_result( xmlRoot, "./func" )
			if func_name == "status":
				self.__Status()
	def __Status( self ):
		output = "<doc>"
		#status — OK, Warning, Critical
		output += "  <eq_param name='device_status'>"
		output += "    <strvalue>OK</strvalue>"
		output += "  </eq_param>"
		#Input voltage. Less than 100 — show the warning
		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>"
		#Load (%)
		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>"
		#Output power (kW)
		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>"
		#Power consumption (kW)
		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>"
		#Battery charge (minutes)
		output += "  <eq_param name='battary_time_remains'>"
		output += "    <floatvalue>24</floatvalue>"
		output += "  </eq_param>"
		output += "</doc>"
		print output

	@staticmethod
	def Info():
		output = "<doc>\n"
		#Device type
		output += "  <type>UPS</type>\n"
		output += "  <name>custom UPS</name>\n"
		#Use SNMP v1
		output += "  <requirements>\n"
		output += "    <snmpv1/>\n"
		output += "  </requirements>\n"
		#DCImanager will call only one function from the UPS handler:
		output += "  <supported_funcs>\n"
		#status request
		output += "    <status/>\n"
		output += "  </supported_funcs>\n"
		output += "</doc>"

		print output

def main():
	if len(sys.argv) > 1 and sys.argv[1] == "-info":
		#If there is the -info key, show the handler information
		UPSSimpleHandler.Info()
	else:
		#In all other cases read the input thread and create the handler object
		#that will process the request from DCImanager.
		request_str = sys.stdin.read()
		UPSSimpleHandler( request_str )

#Start the main function
main()
BASH