How to add a new handler

From ISPWiki
Jump to: navigation, search

Operating principles

External device handler (hereafter a handler) is an executable file, it doesn’t matter if it is a script or binary file. Data exchange between DCImanager and handler occurs through stdout and stdin. Data is transmitted in XML-file.
When DCImanager starts, it launches in turns executable files from var / dcihandlers with -info option. Each handler send XML to stdout, which describes what device this executable file processes and what functions it supports. After registration in DCImanager a new handler will be available while creating new devices.

Interface protocol

Handler registration

DCImanager launches your handler with -info option:

tresthandler -info


stdin of handler won't get anything but the handler should transfer the XML, which describes the device, through its stdout.


 <doc>
 <type>Switch</type>                     - Type of device
 <name>First External Handler</name>     - Name which will be displayed in DCImanager
 <requirements>                          - List of input data, required by handler 
 <snmpv1/>
 <snmpv2c/>
 </requirements>
 <supported_funcs>                       - List of functions, supported by device 
 <status/>
 <port_on/>
 <port_off/>
 </supported_funcs>
 </doc>

<type>

Type of device. It can be Switch, PDU, IPMI.

<name>

Name which will be displayed in DCImanager. <requirements> List of input data, required by handler. This list is responsible for data which DCImanager will offer to enter during creating a device, based on this handler. It also will specify input data, which will be received by handler during different operations with device. Can include following values:

<snmpv1/> - SNMPv1 will be available on the device creation form. Handler will get information about SNMP version and community.
<snmpv2c/> - SNMPv2c will be available on the device creation form. Handler will get information about SNMP version and community.
<snmpv3/> - SNMPv3 will be available on the device creation form. Handler will get information about SNMP version, user, password (auth phrase), private key (priv phrase) и and authentication level.
<ssh/> - On the form SSH tab will be available, handler will get user and password info for SSH connection.
<telnet/> - Telnet tab will be available on the form, handler will get user and password info for Telnet connection.

You can use any combinations of these parameters.

WARNING: DCImanager doesn't implement any protocols for external handlers (SNMP, SSH, TELNET). Parameters mentioned above just allow the user (the person who will register the device based on your handler in DCImanager) to see intelligible input fields, corresponding to the described protocols.


<supported_funcs>

List of functions supported by the device . May include the following values:

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


Call of handler functions

Input data

If any handler function is called (for example if PDU's port is disabled ), executable files without parameters will be called and the xml with all necessary data will be sent to stdin. Example 1 - call of status function, which should show information about all statuses of all device's ports.

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

Example 2 - Call of function port_on, which should enable certain port.

<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> - name of function to be called.
<device_params> - Device parameters,usually authentication information. Can include:

Parameter name Description Acceptable values
ip IP-address of device string
snmp_ver SNMP version, which will be used SNMP v1, SNMP v2c, SNMP v3
snmp_community SNMP Community string without spaces, Latin letters
snmp_user SNMP user (only in case of using SNMP v3) string
snmp_auth_phrase Phrase for SNMP authentication (only in case of using SNMP v3) string
snmp_priv_phrase Private phrase for SNMP (only in case of using SNMP v3) string
snmp_auth_level SNMP authentication level noauth, authnopriv, authpriv
telnet_pass Password for Telnet connection string
telnet_user Username for Telnet connection string
ssh_pass Password for SSH connection string
ssh_user Username for SSH connection string
pass Password for IPMI string
user IPMI user name string

<port> - port, for which the function is executed, can include:

Parameter name Description Acceptable value
identity ID of the port, unique inside the device string
speed The speed, which should be set on the port with ID identity  auto, auto10100, 10mbps, 100mbps, 1gbps, 10gbps
duplex Mode, t which should be set on the port with ID identity  auto, half, full
vlan_id VLAN which should be set on the port with ID identity  from 1 to 4095
vlan_name Nickname of VLAN which should be set on the port with ID identity  string without spaces, Latin letters

Output data

When handler got and processed input data, it should return a result (also in XML). Example 1 - response to function status of handler of small switch for 4 ports. Describes all ports and their state.

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

Example 2 - response to function port_off for port with ID "1".

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

<hostname> - only for switches, contents switch's hostname.

<port> - contains description of port’s various parameters. Can include:

Parameter name Descripton Acceptable value
identity ID of the port, unique inside the device string
description Description of device port string
admin_status Port's status, made by administrator. on, off, unknown
oper_status Actual port's state (e.x switch port link) on, off, unknown
speed Switch port speed
auto, auto10100, 10mbps, 100mbps, 1gbps, 10gbps
duplex Switch port mode auto, half, full
rxbytes Counter of incoming bytes, passed through the switch port numeric value
txbytes Counter of outgoing bytes, passed through the switch port numeric value
rxpackets Counter of incoming packets, passed through the switch port numeric value
txpackets Counter of outgoing packets, passed through the switch port numeric value
power Counter of power consumption in W-hr pn PDU' port numeric value
vlan_id VLAN set on the switch port from 1 to 4095
vlan_name VLAN alias on the switch port string without spaces, Latin letters
mac MAC-address, found on the switch port. There may be several entries for each port. MAC-address in one of following formats: 1A2B3C4D5E6F 1A:2B:3C:4D:5E:6F 1a2b.3c4d.5e6f 1A-2B-3C-4D-5E-6F

Also a handler can return an error, for example:

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

If in output XML there is a block <error> all other content will be ignored and the operation will be terminated, also error report about an error during the work with device will be generated.

  • <type> - type of an error. Can be "connection" and "unexpected_data"
    • connection - device connection error
    • unexpected_data - data exchange error
  • <text>- text of an error, will be displayed in report.

Matching of input and output data

Switches (<type>Switch</type>)

Function Description Input data Output data
status should return the complete information about all the switch ports data to access device (<device_params>) ports descriprion  
statistics should return the state of counters of device ports data to access device (<device_params>) ports counters
port_on, port_off should enable / disable switch port data to access device(<device_params>) and port's ID new port state
port_speed should set the speed of the switch port data to access device(<device_params>), ID and port' speed new port state
port_duplex should set the switch port mode data to access device, ID and wanted port's mode new port state
set_vlan should set the VLAN for switch port data to access device, port ID and VLAN ID and VLAN nickname new port state

Power distributors

Function Description Input data Output data
status should return the complete information about all ports of PDU data to access device (<device_params>) ports description  
statistics should return the state of all counters of device ports data to access device(<device_params>) ports counters
port_on, port_off, port_reset should turn on / off / reset port of PDU data to access device(<device_params>)and port ID new port status

IPMI

Although IPMI doesn't have ports because it's integrated to the server, in DCImanager there is a special reserved port for device of this type. Its ID should be "power" (case-sensitive). 

Function Description Input data Output data
status should return the full information about the power status of the server data to access device(<device_params>) port's status "power"
port_on, port_off, port_reset should turn on / off / reset the server's power data to access device(<device_params>) new port's state   "power"

Examples of handler

Switch handler

This event handler is similar to the handler SNMP common which is included in DCImanager. Switch management is carried out by using SNMPv2c library pysnmp 4v.

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

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

#Different functions converting values from panel to 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 all objects, required for 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 a function called by DCImanager and call 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 port
		self.__SnmpSet( "1.3.6.1.2.1.2.2.1.7." + ident, rfc1902.Integer( 2 ) )
		
		#Send to panel new port's status 
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<admin_status>on</admin_status>"
		output += "</port></doc>"
		print output

	def __PortOn( self, ident ):
		#Enable port
		self.__SnmpSet( "1.3.6.1.2.1.2.2.1.7." + ident, rfc1902.Integer( 1 ) )
		
		#Send to panel new port's status 
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<admin_status>on</admin_status>"
		output += "</port></doc>"
		print output

	def __PortSpeed( self, ident, val ):
		#Get a list of indexes...
		indexes = self.__SnmpWalk( "1.3.6.1.4.1.9.5.1.4.1.1.11" )
		#And with this list set a new speed at  desirable port
		#Key will be searched by 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 ) ) )

		#Send to panel new port's speed 
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<speed>" + val + "</speed>"
		output += "</port></doc>"
		print output

	def __PortDuplex( self, ident, val ):
		#Get a list of indexes...
		indexes = self.__SnmpWalk( "1.3.6.1.4.1.9.5.1.4.1.1.11" )
		#And with this list set a new mode at  desirable port
		#Key will be searched by 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 ) ) )

		#Send to panel new port's mode .
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<duplex>" + val + "</duplex>"
		output += "</port></doc>"
		print output

	def __Status( self ):
		output = "<doc>"
		ports = {}#Create port dictionary where key is ID of port
		#Create ports 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 ports status made by 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 real ports 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 )
		
		#Define actual ports speed and mode. 
		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 )

		#Send a full list of ports to panel. 
		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 up of mib value. Value should be converted to type according 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

	#Tree-traverse (traverse of tree, made 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>"
                #There are only 4 functions which DCImanager will call from switch handler: 
		output += "<supported_funcs>"
                #get a list of ports 
		output += "<status/>"
                #disable port
		output += "<port_off/>"
                #enable port
		output += "<port_on/>"
                #change port's mode
		output += "<port_duplex/>"
                #change port's 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 a  -info key - show information about handler.
		SwitchSimpleHandler.Info()
	else:
		#In all other cases read input stream and make handler's object,
		#for processing DCImanager request
		request_str = sys.stdin.read()
		SwitchSimpleHandler( request_str )

#Launch of main function 
main()

IPMI handler

This handler manages devices with IPMI v1.5 by ipmitool utility (written on python 2.7) Start the script by calling the main function

#!/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 )
		#Get IP-address of IPMI.
		self.__IP = xpath_result( xmlRoot, "./device_params/ip" )
		#Get IPMI user, who should have rights  to power management. 
		self.__User = xpath_result( xmlRoot, "./device_params/user" ).replace( "`", "\\`" )
		#Password of IPMI in  ipmitool will be sent by environment variable, for security reasons  
		os.putenv( "IPMI_PASSWORD", xpath_result( xmlRoot, "./device_params/pass" ) )

		#Defying the function, called by DCImanager and calling 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 ):
		#Power-off by ipmitool
		cmd_res, _ = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power off" )

		if cmd_res == 0:
			#If a command is successful, return a new status by output
			#For IPMI connection port's identity should always has a value "'''power'''".
			output = "<doc><port>"
			output += "<identity>power</identity>"
			output += "<admin_status>off</admin_status>"
			output += "</port></doc>"
			print output
		else:
			#If connection failed, report about the connection problem. 
			self.__ConnectionProblem( cmd_res )

	def __PortOn( self ):
		#Power-on by ipmitool
		cmd_res, _ = commands.getstatusoutput( self.__IPMIToolStart() + "chassis power on" )
		
		if cmd_res == 0:
			#If a command is successful, return a new status by output
			output = "<doc><port>"
			output += "<identity>power</identity>"
			output += "<admin_status>on</admin_status>"
			output += "</port></doc>"
			print output
		else:
			#If command failed, report about the connection problem. 
			self.__ConnectionProblem( cmd_res )

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

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

		if cmd_res == 0:
			#If a command is successful, return a new status by output
			output = "<doc><port>"
			output += "<identity>power</identity>"
			#Description will be shown in "Device" field in server's connections list. 
			#This node's description is not necessary. As a description the word "Power" will be used.
			output += "<description>Some IPMI</description>"
			#Check ports 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  ipmitool output doesn't show port status - report about an error in data exchange and finish method executing. 
				self.__UnexpectedDataProblem( cmd_out )
				return
			output += "</port></doc>"
			print output
		else:
			self.__ConnectionProblem( cmd_res )

	def __IPMIToolStart( self ):
		#Start of ipmitool. If there is -E key - password
		#will be get from environment variable
		return self.__IPMITool + " -H " + self.__IP + " -U " + self.__User + " -E "

	def __ConnectionProblem( self, ipmi_res ):
		#Return an error. Problem will be registered in DCImanager
		output = "<doc><error>"
		#Two types of problem can happen.
		#connection - problem with connection 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 - problem of data exchange with device.
		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's type
		output += "<type>IPMI</type>" 
		#IPMI version which will be shown in drop-down menu during creation of new connection. 
		output += "<name>Custom IPMI v1.5 Handler</name>"
		#Node "requirements" in case of IPMI is not necessary.
		output += "<supported_funcs>"
		#Thewre are only 3 functions DCImanager will call from IPMI handler:
		#device status
		output += "<status/>"
		#port disable
		output += "<port_off/>"
		#port enable
		output += "<port_on/>"
		#port reset
		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 '''-info''' key -  show handler's info
		IPMIhandler.Info()
	else:
		#In all other cases read input stream and create handler' object 
		#for processing DCImanager's request.
		request_str = sys.stdin.read()
		IPMIhandler( request_str )

#Launch of main function 
main()

PDU handler

Handler for BM blades power management (IBM BladeServer) (provided by one of the clients).

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

#Exeptions. which can happen during works with device
class ConnectionProblem( Exception ):
	def __init__( self ):
	#Two types f problems can occurs
	#connection - problem with connection 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 - problem of data exchange between device and panel
		print "<doc><error><type>unexpected_data</type><text>" + msg + "</text></error></doc>"

#Different functions of converting of values from panel to devices and vice versa. 
	def IBMPortStatusToIFXStr( val ):
		return "on" if val == 1 else "off"

#Handler type
class PowerSimpleHandler:
	def __init__( self, request_from_ifx ):
	#Initialization of all objects required for SNMP request
		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 function called by DCImanager and call 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 port
		self.__SnmpSet( "1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.7." + ident, rfc1902.Integer( 0 ) )

	#Send to panel new port's status
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<admin_status>off</admin_status>"
		output += "</port></doc>"
		print output

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

	#Send to panel new port's status
		output = "<doc><port>"
		output += "<identity>" + ident + "</identity>"
		output += "<admin_status>on</admin_status>"
		output += "</port></doc>"
		print output

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

	#Send to panel new port's 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 = {}#Make a vocabulary of ports, where key is ID of port
		#Define 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 blades names
		for ident, descr in self.__SnmpWalk( "1.3.6.1.4.1.2.3.51.2.22.1.5.1.1.6." ).iteritems():
		#If there is no blade in server - change (No name) Not installed
			if descr == "(No name)":
				descr = "Not installed"
				ports[ident].Description = descr

#Send to panel 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 up of mib value. Value should be adjusted to type in accordance with 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

#Tree-traverse (traverse of tree, made 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>"
		#Type of device
		output += "<type>PDU</type>" 
		output += "<name>Blade Power</name>"
		#Use SNMP v1
		output += "<requirements>"
		output += "<snmpv1/>"
		output += "</requirements>"
		#DCImanager will call from PDU handler only 4 functions:
		output += "<supported_funcs>"
	#list of ports
		output += "<status/>"
		#disable port
		output += "<port_off/>"
		#enable port
		output += "<port_on/>"
		#port reset
		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 '''-info''' key - show handler info 
		PowerSimpleHandler.Info()
	else:
	#In all other cases read input stream and create handler' object 
	#which execute request from DCImanager
		request_str = sys.stdin.read()
		PowerSimpleHandler( request_str )

#Launch of main fucntion
main()