diff --git a/tests/common/plugins/psu_controller/snmp_psu_controllers.py b/tests/common/plugins/psu_controller/snmp_psu_controllers.py index 30b7d427bd..fc926d380d 100644 --- a/tests/common/plugins/psu_controller/snmp_psu_controllers.py +++ b/tests/common/plugins/psu_controller/snmp_psu_controllers.py @@ -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): """ @@ -51,17 +91,22 @@ 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__) @@ -69,6 +114,9 @@ def __init__(self, hostname, controller): 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__) @@ -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): """ @@ -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): """ @@ -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): @@ -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)