diff --git a/doc/source/admin/drivers/snmp.rst b/doc/source/admin/drivers/snmp.rst index 1c402ab9b2..eed4ed794a 100644 --- a/doc/source/admin/drivers/snmp.rst +++ b/doc/source/admin/drivers/snmp.rst @@ -22,39 +22,47 @@ this table could possibly work using a similar driver. Please report any device status. -============== ========== ========== ===================== -Manufacturer Model Supported? Driver name -============== ========== ========== ===================== -APC AP7920 Yes apc_masterswitch -APC AP9606 Yes apc_masterswitch -APC AP9225 Yes apc_masterswitchplus -APC AP7155 Yes apc_rackpdu -APC AP7900 Yes apc_rackpdu -APC AP7901 Yes apc_rackpdu -APC AP7902 Yes apc_rackpdu -APC AP7911a Yes apc_rackpdu -APC AP7921 Yes apc_rackpdu -APC AP7922 Yes apc_rackpdu -APC AP7930 Yes apc_rackpdu -APC AP7931 Yes apc_rackpdu -APC AP7932 Yes apc_rackpdu -APC AP7940 Yes apc_rackpdu -APC AP7941 Yes apc_rackpdu -APC AP7951 Yes apc_rackpdu -APC AP7960 Yes apc_rackpdu -APC AP7990 Yes apc_rackpdu -APC AP7998 Yes apc_rackpdu -APC AP8941 Yes apc_rackpdu -APC AP8953 Yes apc_rackpdu -APC AP8959 Yes apc_rackpdu -APC AP8961 Yes apc_rackpdu -APC AP8965 Yes apc_rackpdu -Aten all? Yes aten -CyberPower all? Untested cyberpower -EatonPower all? Untested eatonpower -Teltronix all? Yes teltronix -BayTech MRP27 Yes baytech_mrp27 -============== ========== ========== ===================== +============== ============== ========== ===================== +Manufacturer Model Supported? Driver name +============== ============== ========== ===================== +APC AP7920 Yes apc_masterswitch +APC AP9606 Yes apc_masterswitch +APC AP9225 Yes apc_masterswitchplus +APC AP7155 Yes apc_rackpdu +APC AP7900 Yes apc_rackpdu +APC AP7901 Yes apc_rackpdu +APC AP7902 Yes apc_rackpdu +APC AP7911a Yes apc_rackpdu +APC AP7921 Yes apc_rackpdu +APC AP7922 Yes apc_rackpdu +APC AP7930 Yes apc_rackpdu +APC AP7931 Yes apc_rackpdu +APC AP7932 Yes apc_rackpdu +APC AP7940 Yes apc_rackpdu +APC AP7941 Yes apc_rackpdu +APC AP7951 Yes apc_rackpdu +APC AP7960 Yes apc_rackpdu +APC AP7990 Yes apc_rackpdu +APC AP7998 Yes apc_rackpdu +APC AP8941 Yes apc_rackpdu +APC AP8953 Yes apc_rackpdu +APC AP8959 Yes apc_rackpdu +APC AP8961 Yes apc_rackpdu +APC AP8965 Yes apc_rackpdu +Aten all? Yes aten +CyberPower all? Untested cyberpower +EatonPower all? Untested eatonpower +Teltronix all? Yes teltronix +BayTech MRP27 Yes baytech_mrp27 +Raritan PX3-5547V-V2 Yes raritan_pdu2 +Raritan PX3-5726V Yes raritan_pdu2 +Raritan PX3-5776U-N2 Yes raritan_pdu2 +Raritan PX3-5969U-V2 Yes raritan_pdu2 +Raritan PX3-5961I2U-V2 Yes raritan_pdu2 +Vertiv NU30212 Yes vertivgeist_pdu +ServerTech CW-16VE-P32M Yes servertech_sentry3 +ServerTech C2WG24SN Yes servertech_sentry4 +============== ============== ========== ===================== Software Requirements diff --git a/ironic/drivers/modules/snmp.py b/ironic/drivers/modules/snmp.py index 4e700c6f8f..d544d5687b 100644 --- a/ironic/drivers/modules/snmp.py +++ b/ironic/drivers/modules/snmp.py @@ -799,6 +799,341 @@ class SNMPDriverBaytechMRP27(SNMPDriverSimple): value_power_on = 1 +class SNMPDriverServerTechSentry3(SNMPDriverBase): + """SNMP driver class for Server Technology Sentry 3 PDUs. + + ftp://ftp.servertech.com/Pub/SNMP/sentry3/Sentry3.mib + + SNMP objects for Server Technology Power PDU. + 1.3.6.1.4.1.1718.3.2.3.1.5.1.1. outletStatus + Read 0=off, 1=on, 2=off wait, 3=on wait, [...more options follow] + 1.3.6.1.4.1.1718.3.2.3.1.11.1.1. outletControlAction + Write 0=no action, 1=on, 2=off, 3=reboot + """ + + oid_device = (1718, 3, 2, 3, 1) + oid_tower_infeed_idx = (1, 1, ) + oid_power_status = (5,) + oid_power_action = (11,) + + status_off = 0 + status_on = 1 + status_off_wait = 2 + status_on_wait = 3 + + value_power_on = 1 + value_power_off = 2 + + def __init__(self, *args, **kwargs): + super(SNMPDriverServerTechSentry3, self).__init__(*args, **kwargs) + # Due to its use of different OIDs for different actions, we only form + # an OID that holds the common substring of the OIDs for power + # operations. + self.oid_base = self.oid_enterprise + self.oid_device + + def _snmp_oid(self, oid): + """Return the OID for one of the outlet control objects. + + :param oid: The action-dependent portion of the OID, as a tuple of + integers. + :returns: The full OID as a tuple of integers. + """ + + outlet = self.snmp_info['outlet'] + full_oid = self.oid_base + oid + self.oid_tower_infeed_idx + (outlet,) + return full_oid + + def _snmp_power_state(self): + oid = self._snmp_oid(self.oid_power_status) + state = self.client.get(oid) + + # Translate the state to an Ironic power state. + if state in (self.status_on, self.status_off_wait): + power_state = states.POWER_ON + elif state in (self.status_off, self.status_on_wait): + power_state = states.POWER_OFF + else: + LOG.warning("SeverTech Sentry3 PDU %(addr)s oid %(oid) outlet " + "%(outlet)s: unrecognised power state %(state)s.", + {'addr': self.snmp_info['address'], + 'oid': oid, + 'outlet': self.snmp_info['outlet'], + 'state': state}) + power_state = states.ERROR + + return power_state + + def _snmp_power_on(self): + oid = self._snmp_oid(self.oid_power_action) + value = snmp.Integer(self.value_power_on) + self.client.set(oid, value) + + def _snmp_power_off(self): + oid = self._snmp_oid(self.oid_power_action) + value = snmp.Integer(self.value_power_off) + self.client.set(oid, value) + + +class SNMPDriverServerTechSentry4(SNMPDriverBase): + """SNMP driver class for Server Technology Sentry 4 PDUs. + + https://www.servertech.com/support/sentry-mib-oid-tree-downloads + + SNMP objects for Server Technology Power PDU. + 1.3.6.1.4.1.1718.4.1.8.5.1.1 outletStatus + notSet (0) fixedOn (1) idleOff (2) idleOn (3) [...more options follow] + pendOn (8) pendOff (9) off (10) on (11) [...more options follow] + eventOff (16) eventOn (17) eventReboot (18) eventShutdown (19) + 1.3.6.1.4.1.1718.4.1.8.5.1.2. outletControlAction + Write 0=no action, 1=on, 2=off, 3=reboot + """ + + oid_device = (1718, 4, 1, 8, 5, 1) + oid_tower_infeed_idx = (1, 1, ) + oid_power_status = (1,) + oid_power_action = (2,) + + notSet = 0 + fixedOn = 1 + idleOff = 2 + idleOn = 3 + wakeOff = 4 + wakeOn = 5 + ocpOff = 6 + ocpOn = 7 + status_pendOn = 8 + status_pendOff = 9 + status_off = 10 + status_on = 11 + reboot = 12 + shutdown = 13 + lockedOff = 14 + lockedOn = 15 + + value_power_on = 1 + value_power_off = 2 + + def __init__(self, *args, **kwargs): + super(SNMPDriverServerTechSentry4, self).__init__(*args, **kwargs) + # Due to its use of different OIDs for different actions, we only form + # an OID that holds the common substring of the OIDs for power + # operations. + self.oid_base = self.oid_enterprise + self.oid_device + + def _snmp_oid(self, oid): + """Return the OID for one of the outlet control objects. + + :param oid: The action-dependent portion of the OID, as a tuple of + integers. + :returns: The full OID as a tuple of integers. + """ + + outlet = self.snmp_info['outlet'] + full_oid = self.oid_base + oid + self.oid_tower_infeed_idx + (outlet,) + return full_oid + + def _snmp_power_state(self): + oid = self._snmp_oid(self.oid_power_status) + state = self.client.get(oid) + + # Translate the state to an Ironic power state. + if state in (self.status_on, self.status_pendOn, self.idleOn): + power_state = states.POWER_ON + elif state in (self.status_off, self.status_pendOff): + power_state = states.POWER_OFF + else: + LOG.warning("ServerTech Sentry4 PDU %(addr)s oid %(oid)s outlet " + "%(outlet)s: unrecognised power state %(state)s.", + {'addr': self.snmp_info['address'], + 'oid': oid, + 'outlet': self.snmp_info['outlet'], + 'state': state}) + power_state = states.ERROR + + return power_state + + def _snmp_power_on(self): + oid = self._snmp_oid(self.oid_power_action) + value = snmp.Integer(self.value_power_on) + self.client.set(oid, value) + + def _snmp_power_off(self): + oid = self._snmp_oid(self.oid_power_action) + value = snmp.Integer(self.value_power_off) + self.client.set(oid, value) + + +class SNMPDriverRaritanPDU2(SNMPDriverBase): + """SNMP driver class for Raritan PDU2 PDUs. + + http://support.raritan.com/px2/version-2.4.1/mibs/pdu2-mib-020400-39592.txt + http://cdn.raritan.com/download/PX/v1.5.20/PDU-MIB.txt + + Command: + snmpset -v2c -c private -m+PDU2-MIB \ + PDU2-MIB::switchingOperation.1.4 = cycle + snmpset -v2c -c private \ + .1.3.6.1.4.1.13742.6.4.1.2.1.2.1.4 i 2 + Output: + PDU2-MIB::switchingOperation.1.4 = INTEGER: cycle(2) + """ + + oid_device = (13742, 6, 4, 1, 2, 1) + oid_power_action = (2, ) + oid_power_status = (3, ) + oid_tower_infeed_idx = (1, ) + + unavailable = -1 + status_open = 0 + status_closed = 1 + belowLowerCritical = 2 + belowLowerWarning = 3 + status_normal = 4 + aboveUpperWarning = 5 + aboveUpperCritical = 6 + status_on = 7 + status_off = 8 + detected = 9 + notDetected = 10 + alarmed = 11 + ok = 12 + marginal = 13 + fail = 14 + yes = 15 + no = 16 + standby = 17 + one = 18 + two = 19 + inSync = 20 + outOfSync = 21 + + value_power_on = 1 + value_power_off = 0 + + def __init__(self, *args, **kwargs): + super(SNMPDriverRaritanPDU2, self).__init__(*args, **kwargs) + # Due to its use of different OIDs for different actions, we only form + # an OID that holds the common substring of the OIDs for power + # operations. + self.oid_base = self.oid_enterprise + self.oid_device + + def _snmp_oid(self, oid): + """Return the OID for one of the outlet control objects. + + :param oid: The action-dependent portion of the OID, as a tuple of + integers. + :returns: The full OID as a tuple of integers. + """ + + outlet = self.snmp_info['outlet'] + full_oid = self.oid_base + oid + self.oid_tower_infeed_idx + (outlet,) + return full_oid + + def _snmp_power_state(self): + oid = self._snmp_oid(self.oid_power_status) + state = self.client.get(oid) + + # Translate the state to an Ironic power state. + if state == self.status_on: + power_state = states.POWER_ON + elif state == self.status_off: + power_state = states.POWER_OFF + else: + LOG.warning("Raritan PDU2 PDU %(addr)s oid %(oid)s outlet " + "%(outlet)s: unrecognised power state %(state)s.", + {'addr': self.snmp_info['address'], + 'oid': oid, + 'outlet': self.snmp_info['outlet'], + 'state': state}) + power_state = states.ERROR + + return power_state + + def _snmp_power_on(self): + oid = self._snmp_oid(self.oid_power_action) + value = snmp.Integer(self.value_power_on) + self.client.set(oid, value) + + def _snmp_power_off(self): + oid = self._snmp_oid(self.oid_power_action) + value = snmp.Integer(self.value_power_off) + self.client.set(oid, value) + + +class SNMPDriverVertivGeistPDU(SNMPDriverBase): + """SNMP driver class for VertivGeist NU30017L/NU30019L PDU. + + https://mibs.observium.org/mib/GEIST-V5-MIB/ + + """ + + oid_device = (21239, 5, 2, 3, 5, 1) + oid_power_action = (6, ) + oid_power_status = (4, ) + oid_tower_infeed_idx = (1, ) + + on = 1 + off = 2 + on2off = 3 + off2on = 4 + rebootOn = 5 + rebootOff = 5 + unavailable = 7 + + value_power_on = 2 + value_power_off = 4 + + def __init__(self, *args, **kwargs): + super(SNMPDriverVertivGeistPDU, self).__init__(*args, **kwargs) + # Due to its use of different OIDs for different actions, we only form + # an OID that holds the common substring of the OIDs for power + # operations. + self.oid_base = self.oid_enterprise + self.oid_device + + def _snmp_oid(self, oid): + """Return the OID for one of the outlet control objects. + + :param oid: The action-dependent portion of the OID, as a tuple of + integers. + + :returns: The full OID as a tuple of integers. + """ + + outlet = self.snmp_info['outlet'] + full_oid = self.oid_base + oid + (outlet,) + return full_oid + + def _snmp_power_state(self): + oid = self._snmp_oid(self.oid_power_status) + state = self.client.get(oid) + + # Translate the state to an Ironic power state. + if state in (self.on, self.on2off): + power_state = states.POWER_ON + elif state in (self.off, self.off2on): + power_state = states.POWER_OFF + else: + LOG.warning("Vertiv Geist PDU %(addr)s oid %(oid)s outlet " + "%(outlet)s: unrecognised power state %(state)s.", + {'addr': self.snmp_info['address'], + 'oid': oid, + 'outlet': self.snmp_info['outlet'], + 'state': state}) + power_state = states.ERROR + + return power_state + + def _snmp_power_on(self): + oid = self._snmp_oid(self.oid_power_action) + value = snmp.Integer(self.value_power_on) + self.client.set(oid, value) + + def _snmp_power_off(self): + oid = self._snmp_oid(self.oid_power_action) + value = snmp.Integer(self.value_power_off) + self.client.set(oid, value) + + class SNMPDriverAuto(SNMPDriverBase): SYS_OBJ_OID = (1, 3, 6, 1, 2, 1, 1, 2) @@ -878,6 +1213,10 @@ DRIVER_CLASSES = { 'eatonpower': SNMPDriverEatonPower, 'teltronix': SNMPDriverTeltronix, 'baytech_mrp27': SNMPDriverBaytechMRP27, + 'servertech_sentry3': SNMPDriverServerTechSentry3, + 'servertech_sentry4': SNMPDriverServerTechSentry4, + 'raritan_pdu2': SNMPDriverRaritanPDU2, + 'vertivgeist_pdu': SNMPDriverVertivGeistPDU, 'auto': SNMPDriverAuto, } diff --git a/ironic/tests/unit/drivers/modules/test_snmp.py b/ironic/tests/unit/drivers/modules/test_snmp.py index 6bdd2da5a2..00799dc4d2 100644 --- a/ironic/tests/unit/drivers/modules/test_snmp.py +++ b/ironic/tests/unit/drivers/modules/test_snmp.py @@ -327,6 +327,34 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase): info = snmp._parse_driver_info(node) self.assertEqual('teltronix', info['driver']) + def test__parse_driver_info_servertech_sentry3(self): + # Make sure the servertech_sentry3 driver type is parsed. + info = db_utils.get_test_snmp_info(snmp_driver='servertech_sentry3') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('servertech_sentry3', info['driver']) + + def test__parse_driver_info_servertech_sentry4(self): + # Make sure the servertech_sentry4 driver type is parsed. + info = db_utils.get_test_snmp_info(snmp_driver='servertech_sentry4') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('servertech_sentry4', info['driver']) + + def test__parse_driver_info_raritan_pdu2(self): + # Make sure the raritan_pdu2 driver type is parsed. + info = db_utils.get_test_snmp_info(snmp_driver='raritan_pdu2') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('raritan_pdu2', info['driver']) + + def test__parse_driver_info_vertivgeist_pdu(self): + # Make sure the vertivgeist_pdu driver type is parsed. + info = db_utils.get_test_snmp_info(snmp_driver='vertivgeist_pdu') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('vertivgeist_pdu', info['driver']) + def test__parse_driver_info_snmp_v1(self): # Make sure SNMPv1 is parsed with a community string. info = db_utils.get_test_snmp_info(snmp_version='1', @@ -1260,6 +1288,58 @@ class SNMPDeviceDriverTestCase(db_base.DbTestCase): def test_apc_rackpdu_power_reset(self, mock_get_client): self._test_simple_device_power_reset('apc_rackpdu', mock_get_client) + def test_raritan_pdu2_snmp_objects(self, mock_get_client): + # Ensure the correct SNMP object OIDs and values are used by the + # Raritan PDU2 driver + self._update_driver_info(snmp_driver="raritan_pdu2", + snmp_outlet="6") + driver = snmp._get_driver(self.node) + oid = (1, 3, 6, 1, 4, 1, 13742, 6, 4, 1, 2, 1, 2, 1, 6) + action = (2,) + + self.assertEqual(oid, driver._snmp_oid(action)) + self.assertEqual(1, driver.value_power_on) + self.assertEqual(0, driver.value_power_off) + + def test_servertech_sentry3_snmp_objects(self, mock_get_client): + # Ensure the correct SNMP object OIDs and values are used by the + # ServerTech Sentry3 driver + self._update_driver_info(snmp_driver="servertech_sentry3", + snmp_outlet="6") + driver = snmp._get_driver(self.node) + oid = (1, 3, 6, 1, 4, 1, 1718, 3, 2, 3, 1, 5, 1, 1, 6) + action = (5,) + + self.assertEqual(oid, driver._snmp_oid(action)) + self.assertEqual(1, driver.value_power_on) + self.assertEqual(2, driver.value_power_off) + + def test_servertech_sentry4_snmp_objects(self, mock_get_client): + # Ensure the correct SNMP object OIDs and values are used by the + # ServerTech Sentry4 driver + self._update_driver_info(snmp_driver="servertech_sentry4", + snmp_outlet="6") + driver = snmp._get_driver(self.node) + oid = (1, 3, 6, 1, 4, 1, 1718, 4, 1, 8, 5, 1, 2, 1, 1, 6) + action = (2,) + + self.assertEqual(oid, driver._snmp_oid(action)) + self.assertEqual(1, driver.value_power_on) + self.assertEqual(2, driver.value_power_off) + + def test_vertivgeist_pdu_snmp_objects(self, mock_get_client): + # Ensure the correct SNMP object OIDs and values are used by the + # Vertiv Geist PDU driver + self._update_driver_info(snmp_driver="vertivgeist_pdu", + snmp_outlet="6") + driver = snmp._get_driver(self.node) + oid = (1, 3, 6, 1, 4, 1, 21239, 5, 2, 3, 5, 1, 4, 6) + action = (4,) + + self.assertEqual(oid, driver._snmp_oid(action)) + self.assertEqual(2, driver.value_power_on) + self.assertEqual(4, driver.value_power_off) + def test_aten_snmp_objects(self, mock_get_client): # Ensure the correct SNMP object OIDs and values are used by the # Aten driver diff --git a/releasenotes/notes/additonal-snmp-drivers-ae1174e6bd6ee3a6.yaml b/releasenotes/notes/additonal-snmp-drivers-ae1174e6bd6ee3a6.yaml new file mode 100644 index 0000000000..f98f2e6070 --- /dev/null +++ b/releasenotes/notes/additonal-snmp-drivers-ae1174e6bd6ee3a6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds ``raritan_pdu2``, ``servertech_sentry3``, ``servertech_sentry4``, + and ``vertivgest_pdu`` snmp drivers to support additional PDU models.