Skip to content

Commit

Permalink
Add the APC PSU support for SNMP PSU controller (#1461)
Browse files Browse the repository at this point in the history
Add APC SNMP MIB for PDU control

PORT_NAME_BASE_OID = ".1.3.6.1.4.1.318.1.1.4.4.2.1.4"
PORT_STATUS_BASE_OID = ".1.3.6.1.4.1.318.1.1.12.3.5.1.1.4"
PORT_CONTROL_BASE_OID = ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4"

How did you verify/test it?
{username}@{sonic-mgmt-docker}:/var/sonic-mgmt-int/tests/common/plugins/psu_controller$ python
Python 2.7.12 (default, Oct 8 2019, 14:14:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.

from snmp_psu_controllers import *
psucntl = get_psu_controller('<controller_ip>',"<device_name>")
psucntl.get_psu_status()
[{'psu_id': 0, 'psu_on': True}]
psucntl.turn_off_psu('0')
True
psucntl.get_psu_status()
[{'psu_id': 0, 'psu_on': False}]
psucntl.turn_on_psu('0')
True
psucntl.get_psu_status()
[{'psu_id': 0, 'psu_on': True}]
print psucntl.psuType
APC
  • Loading branch information
sujinmkang authored Apr 7, 2020
1 parent 00914a7 commit f8fde6d
Showing 1 changed file with 130 additions and 83 deletions.
213 changes: 130 additions & 83 deletions tests/common/plugins/psu_controller/snmp_psu_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,83 @@
import logging

from controller_base import PsuControllerBase
from controller_base import run_local_cmd

from pysnmp.proto import rfc1902
from pysnmp.entity.rfc3413.oneliner import cmdgen

def get_psu_controller_type(psu_controller_host):
class snmpPsuController(PsuControllerBase):
"""
@summary: Use SNMP to get the type of PSU controller host
@param psu_controller_host: IP address of PSU controller host
@return: Returns type string of the specified PSU controller host
"""
result = None
cmd = "snmpget -v 1 -c public -Ofenqv %s .1.3.6.1.2.1.1.1.0" % psu_controller_host
try:
stdout = run_local_cmd(cmd)

lines = stdout.splitlines()
if len(lines) > 0:
result = lines[0].strip()
result = result.replace('"', '')
except Exception as e:
logging.debug("Failed to get psu controller type, exception: " + repr(e))

return result

PSU Controller class for SNMP conrolled PSUs - 'Sentry Switched CDU' and 'APC Web/SNMP Management Card'
class SentrySwitchedCDU(PsuControllerBase):
This class implements the interface defined in PsuControllerBase class for SNMP conrtolled PDU type
'Sentry Switched CDU' and 'APC Web/SNMP Management Card'
"""
PSU Controller class for 'Sentry Switched CDU'

This class implements the interface defined in PsuControllerBase class for PDU type 'Sentry Switched CDU'
"""
PORT_NAME_BASE_OID = ".1.3.6.1.4.1.1718.3.2.3.1.3.1"
PORT_STATUS_BASE_OID = ".1.3.6.1.4.1.1718.3.2.3.1.5.1"
PORT_CONTROL_BASE_OID = ".1.3.6.1.4.1.1718.3.2.3.1.11.1"
STATUS_ON = "1"
STATUS_OFF = "0"
CONTROL_ON = "1"
CONTROL_OFF = "2"
def get_psu_controller_type(self):
"""
@summary: Use SNMP to get the type of PSU controller host
@param psu_controller_host: IP address of PSU controller host
@return: Returns type string of the specified PSU controller host
"""
pSYSDESCR = ".1.3.6.1.2.1.1.1.0"
SYSDESCR = "1.3.6.1.2.1.1.1.0"
psu = None
cmdGen = cmdgen.CommandGenerator()
snmp_auth = cmdgen.CommunityData('public')
errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd(
snmp_auth,
cmdgen.UdpTransportTarget((self.controller, 161), timeout=5.0),
cmdgen.MibVariable(pSYSDESCR,),
)
if errorIndication:
logging.info("Failed to get psu controller type, exception: " + str(errorIndication))
for oid, val in varBinds:
current_oid = oid.prettyPrint()
current_val = val.prettyPrint()
if current_oid == SYSDESCR:
psu = current_val
if psu is None:
self.psuType = None
return
if 'Sentry Switched CDU' in psu:
self.psuType = "SENTRY"
if 'APC Web/SNMP Management Card' in psu:
self.psuType = "APC"
return

def psuCntrlOid(self):
"""
Define Oids based on the PSU Type
"""
# MIB OIDs for 'APC Web/SNMP Management PSU'
APC_PORT_NAME_BASE_OID = "1.3.6.1.4.1.318.1.1.4.4.2.1.4"
APC_PORT_STATUS_BASE_OID = "1.3.6.1.4.1.318.1.1.12.3.5.1.1.4"
APC_PORT_CONTROL_BASE_OID = "1.3.6.1.4.1.318.1.1.12.3.3.1.1.4"
# MIB OID for 'Sentry Switched CDU'
SENTRY_PORT_NAME_BASE_OID = "1.3.6.1.4.1.1718.3.2.3.1.3.1"
SENTRY_PORT_STATUS_BASE_OID = "1.3.6.1.4.1.1718.3.2.3.1.5.1"
SENTRY_PORT_CONTROL_BASE_OID = "1.3.6.1.4.1.1718.3.2.3.1.11.1"
self.STATUS_ON = "1"
self.STATUS_OFF = "0"
self.CONTROL_ON = "1"
self.CONTROL_OFF = "2"
if self.psuType == "APC":
self.pPORT_NAME_BASE_OID = '.'+APC_PORT_NAME_BASE_OID
self.pPORT_STATUS_BASE_OID = '.'+APC_PORT_STATUS_BASE_OID
self.pPORT_CONTROL_BASE_OID = '.'+APC_PORT_CONTROL_BASE_OID
self.PORT_NAME_BASE_OID = APC_PORT_NAME_BASE_OID
self.PORT_STATUS_BASE_OID = APC_PORT_STATUS_BASE_OID
self.PORT_CONTROL_BASE_OID = APC_PORT_CONTROL_BASE_OID
elif self.psuType == "SENTRY":
self.pPORT_NAME_BASE_OID = '.'+SENTRY_PORT_NAME_BASE_OID
self.pPORT_STATUS_BASE_OID = '.'+SENTRY_PORT_STATUS_BASE_OID
self.pPORT_CONTROL_BASE_OID = '.'+SENTRY_PORT_CONTROL_BASE_OID
self.PORT_NAME_BASE_OID = SENTRY_PORT_NAME_BASE_OID
self.PORT_STATUS_BASE_OID = SENTRY_PORT_STATUS_BASE_OID
self.PORT_CONTROL_BASE_OID = SENTRY_PORT_CONTROL_BASE_OID
else:
pass


def _get_pdu_ports(self):
"""
Expand All @@ -51,24 +91,32 @@ def _get_pdu_ports(self):
The PDU ports connected to DUT must have hostname of DUT configured in port name/description.
This method depends on this configuration to find out the PDU ports connected to PSUs of specific DUT.
"""
try:
cmd = "snmpwalk -v 1 -c public -Ofenq %s %s " % (self.controller, self.PORT_NAME_BASE_OID)
stdout = run_local_cmd(cmd)
for line in stdout.splitlines():
if self.hostname in line: # PDU port name/description should have DUT hostname
fields = line.split()
if len(fields) == 2:
# Remove the preceding PORT_NAME_BASE_OID, remaining string is the PDU port ID
self.pdu_ports.append(fields[0].replace(self.PORT_NAME_BASE_OID, ''))
except Exception as e:
logging.debug("Failed to get ports controlling PSUs of DUT, exception: " + repr(e))
cmdGen = cmdgen.CommandGenerator()
snmp_auth = cmdgen.CommunityData('public')
errorIndication, errorStatus, errorIndex, varTable = cmdGen.nextCmd(
snmp_auth,
cmdgen.UdpTransportTarget((self.controller, 161)),
cmdgen.MibVariable(self.pPORT_NAME_BASE_OID,),
)
if errorIndication:
logging.debug("Failed to get ports controlling PSUs of DUT, exception: " + str(errorIndication))
for varBinds in varTable:
for oid, val in varBinds:
current_oid = oid.prettyPrint()
current_val = val.prettyPrint()
if self.hostname.lower() in current_val.lower():
# Remove the preceding PORT_NAME_BASE_OID, remaining string is the PDU port ID
self.pdu_ports.append(current_oid.replace(self.PORT_NAME_BASE_OID, ''))

def __init__(self, hostname, controller):
logging.info("Initializing " + self.__class__.__name__)
PsuControllerBase.__init__(self)
self.hostname = hostname
self.controller = controller
self.pdu_ports = []
self.psuType = None
self.get_psu_controller_type()
self.psuCntrlOid()
self._get_pdu_ports()
logging.info("Initialized " + self.__class__.__name__)

Expand All @@ -89,16 +137,17 @@ def turn_on_psu(self, psu_id):
@param psu_id: ID of the PSU on SONiC DUT
@return: Return true if successfully execute the command for turning on power. Otherwise return False.
"""
try:
idx = int(psu_id) % len(self.pdu_ports)
port_oid = self.PORT_CONTROL_BASE_OID + self.pdu_ports[idx]
cmd = "snmpset -v1 -C q -c private %s %s i %s" % (self.controller, port_oid, self.CONTROL_ON)
run_local_cmd(cmd)
logging.info("Turned on PSU %s" % str(psu_id))
return True
except Exception as e:
logging.debug("Failed to turn on PSU %s, exception: %s" % (str(psu_id), repr(e)))
port_oid = self.pPORT_CONTROL_BASE_OID + self.pdu_ports[rfc1902.Integer(psu_id)]
errorIndication, errorStatus, _, _ = \
cmdgen.CommandGenerator().setCmd(
cmdgen.CommunityData('private'),
cmdgen.UdpTransportTarget((self.controller, 161)),
(port_oid, rfc1902.Integer(self.CONTROL_ON)),
)
if errorIndication or errorStatus != 0:
logging.debug("Failed to turn on PSU %s, exception: %s" % (str(psu_id), str(errorStatus)))
return False
return True

def turn_off_psu(self, psu_id):
"""
Expand All @@ -117,16 +166,17 @@ def turn_off_psu(self, psu_id):
@param psu_id: ID of the PSU on SONiC DUT
@return: Return true if successfully execute the command for turning off power. Otherwise return False.
"""
try:
idx = int(psu_id) % len(self.pdu_ports)
port_oid = self.PORT_CONTROL_BASE_OID + self.pdu_ports[idx]
cmd = "snmpset -v1 -C q -c private %s %s i %s" % (self.controller, port_oid, self.CONTROL_OFF)
run_local_cmd(cmd)
logging.info("Turned off PSU %s" % str(psu_id))
return True
except Exception as e:
logging.debug("Failed to turn off PSU %s, exception: %s" % (str(psu_id), repr(e)))
port_oid = self.pPORT_CONTROL_BASE_OID + self.pdu_ports[rfc1902.Integer(psu_id)]
errorIndication, errorStatus, _, _ = \
cmdgen.CommandGenerator().setCmd(
cmdgen.CommunityData('private'),
cmdgen.UdpTransportTarget((self.controller, 161)),
(port_oid, rfc1902.Integer(self.CONTROL_OFF)),
)
if errorIndication or errorStatus != 0:
logging.debug("Failed to turn on PSU %s, exception: %s" % (str(psu_id), str(errorStatus)))
return False
return True

def get_psu_status(self, psu_id=None):
"""
Expand All @@ -149,22 +199,28 @@ def get_psu_status(self, psu_id=None):
The psu_id in returned result is integer starts from 0.
"""
results = []
try:
cmd = "snmpwalk -v 1 -c public -Ofenq %s %s " % (self.controller, self.PORT_STATUS_BASE_OID)
stdout = run_local_cmd(cmd)
for line in stdout.splitlines():
cmdGen = cmdgen.CommandGenerator()
snmp_auth = cmdgen.CommunityData('public')
errorIndication, errorStatus, errorIndex, varTable = cmdGen.nextCmd(
snmp_auth,
cmdgen.UdpTransportTarget((self.controller, 161)),
cmdgen.MibVariable(self.pPORT_STATUS_BASE_OID,),
)
if errorIndication:
logging.debug("Failed to get ports controlling PSUs of DUT, exception: " + str(errorIndication))
for varBinds in varTable:
for oid, val in varBinds:
current_oid = oid.prettyPrint()
current_val = val.prettyPrint()
for idx, port in enumerate(self.pdu_ports):
port_oid = self.PORT_STATUS_BASE_OID + port
fields = line.strip().split()
if len(fields) == 2 and fields[0] == port_oid:
status = {"psu_id": idx, "psu_on": True if fields[1] == self.STATUS_ON else False}
if current_oid == port_oid:
status = {"psu_id": idx, "psu_on": True if current_val == self.STATUS_ON else False}
results.append(status)
if psu_id is not None:
idx = int(psu_id) % len(self.pdu_ports)
results = results[idx:idx+1]
logging.info("Got PSU status: %s" % str(results))
except Exception as e:
logging.debug("Failed to get psu status, exception: " + repr(e))
if psu_id is not None:
idx = int(psu_id) % len(self.pdu_ports)
results = results[idx:idx+1]
logging.info("Got PSU status: %s" % str(results))
return results

def close(self):
Expand All @@ -176,13 +232,4 @@ def get_psu_controller(controller_ip, dut_hostname):
@summary: Factory function to create the actual PSU controller object.
@return: The actual PSU controller object. Returns None if something went wrong.
"""

psu_controller_type = get_psu_controller_type(controller_ip)
if not psu_controller_type:
return None

if "Sentry Switched CDU" in psu_controller_type:
logging.info("Initializing PSU controller")
return SentrySwitchedCDU(dut_hostname, controller_ip)

return None
return snmpPsuController(dut_hostname, controller_ip)

0 comments on commit f8fde6d

Please sign in to comment.