Python Tutorial: Get Temperature and other environmental data from a Dell PowerEdge Server using pysnmp
This python tutorial will teach you how to query the Dell PowerEdge temperature sensors via SNMP. Once OpenManage software is installed on a Dell server, a ton of information is made available via SNMP including…
- Chassis Temperature
- Fan(s) Speed
- Chassis Intrusion Switch
- Hard Drive Health
- Power Supply Status, Voltages, and Consumption
Prerequisites
1.) Install the pysnmp package
C:\> pip install --upgrade pysnmp
Collecting pysnmp
...
Installing collected packages: pysmi, pysnmp
Successfully installed pysmi-0.0.6 pysnmp-4.3.1
2.) A Dell PowerEdge Server running Windows Server 2008 or newer with Dell Open Manage Installed.
3.) You will also need to install and configure SNMP support on the target Windows Server machine.
If you search for pysnmp example code you will find two type, Synchronous, and Asynchronous. The following code is an example of a synchronous SNMP Get found in the old pysnmp manual
http://pysnmp.sourceforge.net/examples/current/v3arch/oneliner/manager/cmdgen/get-v2c.html
from pysnmp.entity.rfc3413.oneliner import cmdgen import time SNMP_HOST = '192.168.1.60' SNMP_PORT = 161 SNMP_COMMUNITY = 'public' cmdGen = cmdgen.CommandGenerator() errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( cmdgen.CommunityData(SNMP_COMMUNITY), cmdgen.UdpTransportTarget((SNMP_HOST, SNMP_PORT)), '1.3.6.1.4.1.674.10892.1.300.10.1.8.1', '1.3.6.1.4.1.674.10892.1.300.10.1.9.1' ) # Check for errors and print out results if errorIndication: print(errorIndication) else: if errorStatus: print('%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex)-1] or '?' ) ) else: for name, val in varBinds: print('%s = %s' % (name.prettyPrint(), val.prettyPrint()))
SNMPv2-SMI::enterprises.674.10892.1.300.10.1.8.1 = b'Dell Inc.'
SNMPv2-SMI::enterprises.674.10892.1.300.10.1.9.1 = b'PowerEdge R720'
Throughout the rest of this tutorial, we will stick with the Synchronous type code. We are only fetching a few data points and there is no need to add the complexity of performing Asynchronous operations.
Next, we will create a snmpget() function that will encapsulate the above code and make it reusable.
def snmpget(oid): from pysnmp.entity.rfc3413.oneliner import cmdgen global SNMP_HOST global SNMP_PORT global SNMP_COMMUNITY cmdGen = cmdgen.CommandGenerator() errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( cmdgen.CommunityData(SNMP_COMMUNITY), cmdgen.UdpTransportTarget((SNMP_HOST, SNMP_PORT)), oid ) # Check for errors and print out results if errorIndication: print(errorIndication) else: if errorStatus: print('%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex)-1] or '?' ) ) else: for name, val in varBinds: #print('%s = %s' % (name.prettyPrint(), val.prettyPrint())) return val
With everything now contained inside the snmpget() function we can call the function for each OID we want to query.
answer = snmpget('1.3.6.1.4.1.674.10892.1.300.10.1.8.1') print(answer)
Dell Inc.
Now that we are getting the data, we will apply some formatting.
print( 'Make: ' + snmpget('1.3.6.1.4.1.674.10892.1.300.10.1.8.1') ) print( 'Model: ' + snmpget('1.3.6.1.4.1.674.10892.1.300.10.1.9.1') )
Make: Dell Inc.
Model: PowerEdge R720
Next, lets fetch some data about the power supply amperage and total watts consumed. You may get different results if your server does not have redundant(2) power supplies.
print( snmpget('1.3.6.1.4.1.674.10892.1.600.30.1.8.1.1') + ': ' + str(snmpget('1.3.6.1.4.1.674.10892.1.600.30.1.6.1.1')) + ' AMPS') print( snmpget('1.3.6.1.4.1.674.10892.1.600.30.1.8.1.2') + ': ' + str(snmpget('1.3.6.1.4.1.674.10892.1.600.30.1.6.1.2')) + ' AMPS') print( snmpget('1.3.6.1.4.1.674.10892.1.600.30.1.8.1.3') + ': ' + str(snmpget('1.3.6.1.4.1.674.10892.1.600.30.1.6.1.3')) + ' Watts')
PS1 Current 1: 16 AMPS
PS2 Current 2: 0 AMPS
System Board Pwr Consumption: 196 Watts
So far, we have called snmpget() individually to get data. Lets modify snmpget to accept a list of OIDs and return a list of return values.
We will progressively modify our snmpget() function to accomplish this. We add two additional arguments to the function oid2, oid3. While this is not ideal the getCmd() wants each addition oid as an additional command line argument.
Lets focus on the return values. Instead of iterating over varBinds we will extract the values and return a simple list or singular value if the list’s length is 1.
def snmpget(oid, oid2='', oid3=''): from pysnmp.entity.rfc3413.oneliner import cmdgen global SNMP_HOST global SNMP_PORT global SNMP_COMMUNITY cmdGen = cmdgen.CommandGenerator() errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( cmdgen.CommunityData(SNMP_COMMUNITY), cmdgen.UdpTransportTarget((SNMP_HOST, SNMP_PORT)), oid, oid2, oid3 ) # Predefine our results list results = [] # Check for errors and print out results if errorIndication: print(errorIndication) else: if errorStatus: print('%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex)-1] or '?' ) ) else: for name, val in varBinds: results.append( val ) if len(results) == 1: return results[0] else: return results results = snmpget( '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.1', \ '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.2', \ '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.3' ) for ans in results: print(ans)
PS1 Current 1
PS2 Current 2
System Board Pwr Consumption
The function now returns a list of values but our approach to accepting additional arguments is not very flexible. As you can see we get an error when we try to request anything other than 3 oids.
results = snmpget( '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.1', \ '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.3' )
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
in ()
----> 1 results = snmpget( '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.1', '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.3' )
in snmpget(oid, oid2, oid3)
14 oid,
15 oid2,
---> 16 oid3
17 )
...
As you see, this code raises an error.
We need to modify the function definition to accept a variable number of arguments.
The function getCmd() also uses this same technique. We can pass along these additional OIDs to the getCmd() function but we must prefix the more_oids variable with an asterisk *.
def snmpget(oid, *more_oids): from pysnmp.entity.rfc3413.oneliner import cmdgen global SNMP_HOST global SNMP_PORT global SNMP_COMMUNITY cmdGen = cmdgen.CommandGenerator() errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( cmdgen.CommunityData(SNMP_COMMUNITY), cmdgen.UdpTransportTarget((SNMP_HOST, SNMP_PORT)), oid, *more_oids ) # Predefine our results list results = [] # Check for errors and print out results if errorIndication: print(errorIndication) else: if errorStatus: print('%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex)-1] or '?' ) ) else: for name, val in varBinds: results.append( val ) if len(results) == 1: return results[0] else: return results
Now when we call snmpget() with any number of oids.
# get 6 oid values in one call. results = snmpget( '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.1', \ '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.2', \ '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.3', \ '1.3.6.1.4.1.674.10892.1.600.30.1.6.1.1', \ '1.3.6.1.4.1.674.10892.1.600.30.1.6.1.2', \ '1.3.6.1.4.1.674.10892.1.600.30.1.6.1.3', \ ) for ans in results: print(ans) print('----------------') # get 1 oid value result = snmpget( '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.1' ) print(result)
PS1 Current 1
PS2 Current 2
System Board Pwr Consumption
16
0
196
----------------
PS1 Current 1
Our snmpget() function is rather functional but we are still referencing the global variables for host,port,community defined back at the top of this tutorial.
We can make this code even more reusable by encapsulating everything into a Class.
class SNMPClient: # This is the SNMPClient constructor def __init__(self, host, port=161, community='public'): self.host = host self.port = port self.community = community def snmpget(self, oid, *more_oids): from pysnmp.entity.rfc3413.oneliner import cmdgen cmdGen = cmdgen.CommandGenerator() errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( cmdgen.CommunityData(self.community), cmdgen.UdpTransportTarget((self.host, self.port)), oid, *more_oids ) # Predefine our results list results = [] # Check for errors and print out results if errorIndication: print(errorIndication) else: if errorStatus: print('%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex)-1] or '?' ) ) else: for name, val in varBinds: results.append( val ) if len(results) == 1: return results[0] else: return results
We supply the IP, Port, and Community string to the class’s constructor.
myClient = SNMPClient('192.168.1.60', 161, 'public') results = myClient.snmpget('1.3.6.1.4.1.674.10892.1.300.10.1.8.1', \ '1.3.6.1.4.1.674.10892.1.300.10.1.9.1', \ '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.1', \ '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.2', \ '1.3.6.1.4.1.674.10892.1.600.30.1.8.1.3', \ '1.3.6.1.4.1.674.10892.1.600.30.1.6.1.1', \ '1.3.6.1.4.1.674.10892.1.600.30.1.6.1.2', \ '1.3.6.1.4.1.674.10892.1.600.30.1.6.1.3' ) for ans in results: print(ans)
Dell Inc.
PowerEdge R720
PS1 Current 1
PS2 Current 2
System Board Pwr Consumption
16
0
196
Now that we have a working SNMP Client that we can use to query the server lets look at some of the interesting data presented by the Dell OpenManage SNMP extensions.
# Cooling Device AKA Fan Location Name
# 1.3.6.1.4.1.674.10892.1.700.12.1.8.1.1
# Cooling Fan RPMs
# 1.3.6.1.4.1.674.10892.1.700.12.1.6.1.1
# Cooling Fan Status (3 is OK)
# 1.3.6.1.4.1.674.10892.1.700.12.1.5.1.1
# Cooling Unit Status (All Fans considers (3 is OK)
# 1.3.6.1.4.1.674.10892.1.700.10.1.8.1.1
# Temp Probe Location(s)
# 1.3.6.1.4.1.674.10892.1.700.20.1.8.1.1
# 1.3.6.1.4.1.674.10892.1.700.20.1.8.1.2
# Temp Reading (Value is Celius 1/10 Degree)
# 1.3.6.1.4.1.674.10892.1.700.20.1.6.1.1
# 1.3.6.1.4.1.674.10892.1.700.20.1.6.1.2
When we query the system’s temperature probes the value is returned in Celsius at a resolution of 1/10 of a degree.
So a value of 234 is 23.4 Celsius.
To get this value we simply divide by 10.
Additionally, I want the temperature reading in Fahrenheit so we use the formula to convert Celsius to Fahrenheit.
T(F) = T(C) x 9/5 + 32
myClient = SNMPClient('192.168.1.60', 161, 'public') results = myClient.snmpget('1.3.6.1.4.1.674.10892.1.700.20.1.8.1.1', \ '1.3.6.1.4.1.674.10892.1.700.20.1.6.1.1', \ '1.3.6.1.4.1.674.10892.1.700.20.1.8.1.2', \ '1.3.6.1.4.1.674.10892.1.700.20.1.6.1.2') temp1 = results[1] # Divide by 10 temp1_in_c = temp1 / 10 # Convert Celsius to Fahrenheit temp1_in_f = temp1_in_c * (9/5) + 32 # Print the results print(results[0] +": "+ str( temp1_in_f ) + 'F') temp2 = results[3] # Divide by 10 temp2_in_c = temp2 / 10 # Convert Celsius to Fahrenheit temp2_in_f = temp2_in_c * (9/5) + 32 # Print the results print(results[2] +": "+ str( temp2_in_f ) + 'F')
System Board Inlet Temp: 69F
System Board Exhaust Temp: 95F
I hope you find this python example useful and educational. You are free to use the above code how you see fit.
One Reply to “Python Tutorial: Get Temperature and other environmental data from a Dell PowerEdge Server using pysnmp”
Hello buddy, interesting article. I already know these stuff (the SNMP Get), however it is well written for someone who has never done it.
Can you give any advice for SNMP Set v2 with oid (no object names)? I have gotten sick googline, searching , testing and in the end doing nothing?