From 61f2f075694b88f1f5dd8cc8f055d868640ab3d3 Mon Sep 17 00:00:00 2001 From: Mathieu Mitchell Date: Thu, 2 Jul 2015 17:59:39 -0400 Subject: [PATCH] Introduce support for APC MasterSwitchPlus and Rack PDU Provide 3 new snmp_driver values for different APC product families: - apc_masterswitch - apc_masterswitchplus - apc_rackpdu The "apc" snmp_driver is still supported and maps to the MasterSwitch driver to avoid breaking backwards compatibility. Different APC product families support different OIDs for power control. - APC MasterSwitch uses sPDUOutletCtl - APC MasterSwitchPlus uses sPDUOutletControlMSPOutletCommand - APC Rack PDU uses rPDUOutletControlOutletCommand Change-Id: I9d8724543d7da7b1c9cdc180c3396d131ed52615 Closes-Bug: #1471025 --- doc/source/deploy/drivers.rst | 56 +-------------- doc/source/drivers/snmp.rst | 91 +++++++++++++++++++++++++ ironic/drivers/modules/snmp.py | 37 ++++++++-- ironic/tests/drivers/test_snmp.py | 109 ++++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 57 deletions(-) create mode 100644 doc/source/drivers/snmp.rst diff --git a/doc/source/deploy/drivers.rst b/doc/source/deploy/drivers.rst index d80071a734..35da710f9f 100644 --- a/doc/source/deploy/drivers.rst +++ b/doc/source/deploy/drivers.rst @@ -42,60 +42,10 @@ AMT SNMP ---- -The SNMP power driver enables control of power distribution units of the type -frequently found in data centre racks. PDUs frequently have a management -ethernet interface and SNMP support enabling control of the power outlets. +.. toctree:: + :maxdepth: 1 -The SNMP power driver works with the PXE driver for network deployment and -network-configured boot. - -Supported PDUs -^^^^^^^^^^^^^^ - -- American Power Conversion (APC) -- CyberPower (implemented according to MIB spec but not tested on hardware) -- EatonPower (implemented according to MIB spec but not tested on hardware) -- Teltronix - -Software requirements -^^^^^^^^^^^^^^^^^^^^^ - -- The PySNMP package must be installed, variously referred to as ``pysnmp`` - or ``python-pysnmp`` - -Enabling the SNMP power driver -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -- Add ``pxe_snmp`` to the list of ``enabled_drivers`` in - ``/etc/ironic/ironic.conf`` -- Ironic Conductor must be restarted for the new driver to be loaded. - -Ironic node configuration -^^^^^^^^^^^^^^^^^^^^^^^^^ - -Nodes are configured for SNMP control by setting the ironic node object's -``driver`` property to be ``pxe_snmp``. Further configuration values are -added to ``driver_info``: - -- ``snmp_address``: the IPv4 address of the PDU controlling this node. -- ``snmp_port``: (optional) A non-standard UDP port to use for SNMP operations. - If not specified, the default port (161) is used. -- ``snmp_outlet``: The power outlet on the PDU (1-based indexing). -- ``snmp_protocol``: (optional) SNMP protocol version - (permitted values ``1``, ``2c`` or ``3``). If not specified, SNMPv1 - is chosen. -- ``snmp_community``: (Required for SNMPv1 and SNMPv2c) SNMP community - parameter for reads and writes to the PDU. -- ``snmp_security``: (Required for SNMPv3) SNMP security string. - -PDU configuration -^^^^^^^^^^^^^^^^^ - -This version of the SNMP power driver does not support handling -PDU authentication credentials. When using SNMPv3, the PDU must be -configured for ``NoAuthentication`` and ``NoEncryption``. The -security name is used analogously to the SNMP community in early -SNMP versions. + ../drivers/snmp iLO driver ---------- diff --git a/doc/source/drivers/snmp.rst b/doc/source/drivers/snmp.rst new file mode 100644 index 0000000000..99c37e0a41 --- /dev/null +++ b/doc/source/drivers/snmp.rst @@ -0,0 +1,91 @@ +=========== +SNMP driver +=========== + +The SNMP power driver enables control of power distribution units of the type +frequently found in data centre racks. PDUs frequently have a management +ethernet interface and SNMP support enabling control of the power outlets. + +The SNMP power driver works with the PXE driver for network deployment and +network-configured boot. + +List of supported devices +========================= + +This is a non-exhaustive list of supported devices. Any device not listed in +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 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 +============== ========== ========== ===================== + + +Software Requirements +===================== + +- The PySNMP package must be installed, variously referred to as ``pysnmp`` + or ``python-pysnmp`` + +Enabling the SNMP Power Driver +============================== + +- Add ``pxe_snmp`` to the list of ``enabled_drivers`` in + ``/etc/ironic/ironic.conf`` +- Ironic Conductor must be restarted for the new driver to be loaded. + +Ironic Node Configuration +========================= + +Nodes are configured for SNMP control by setting the Ironic node object's +``driver`` property to be ``pxe_snmp``. Further configuration values are +added to ``driver_info``: + +- ``snmp_driver``: PDU manufacturer driver +- ``snmp_address``: the IPv4 address of the PDU controlling this node. +- ``snmp_port``: (optional) A non-standard UDP port to use for SNMP operations. + If not specified, the default port (161) is used. +- ``snmp_outlet``: The power outlet on the PDU (1-based indexing). +- ``snmp_protocol``: (optional) SNMP protocol version + (permitted values ``1``, ``2c`` or ``3``). If not specified, SNMPv1 + is chosen. +- ``snmp_community``: (Required for SNMPv1 and SNMPv2c) SNMP community + parameter for reads and writes to the PDU. +- ``snmp_security``: (Required for SNMPv3) SNMP security string. + +PDU Configuration +================= + +This version of the SNMP power driver does not support handling +PDU authentication credentials. When using SNMPv3, the PDU must be +configured for ``NoAuthentication`` and ``NoEncryption``. The +security name is used analogously to the SNMP community in early +SNMP versions. diff --git a/ironic/drivers/modules/snmp.py b/ironic/drivers/modules/snmp.py index 1f62ff36c6..66259e75ce 100644 --- a/ironic/drivers/modules/snmp.py +++ b/ironic/drivers/modules/snmp.py @@ -398,10 +398,10 @@ class SNMPDriverAten(SNMPDriverSimple): return self.oid_enterprise + self.oid_device + (outlet, 0,) -class SNMPDriverAPC(SNMPDriverSimple): - """SNMP driver class for APC PDU devices. +class SNMPDriverAPCMasterSwitch(SNMPDriverSimple): + """SNMP driver class for APC MasterSwitch PDU devices. - SNMP objects for APC PDU: + SNMP objects for APC SNMPDriverAPCMasterSwitch PDU: 1.3.6.1.4.1.318.1.1.4.4.2.1.3 sPDUOutletCtl Values: 1=On, 2=Off, 3=PowerCycle, [...more options follow] """ @@ -411,6 +411,32 @@ class SNMPDriverAPC(SNMPDriverSimple): value_power_off = 2 +class SNMPDriverAPCMasterSwitchPlus(SNMPDriverSimple): + """SNMP driver class for APC MasterSwitchPlus PDU devices. + + SNMP objects for APC SNMPDriverAPCMasterSwitchPlus PDU: + 1.3.6.1.4.1.318.1.1.6.5.1.1.5 sPDUOutletControlMSPOutletCommand + Values: 1=On, 3=Off, [...more options follow] + """ + + oid_device = (318, 1, 1, 6, 5, 1, 1, 5) + value_power_on = 1 + value_power_off = 3 + + +class SNMPDriverAPCRackPDU(SNMPDriverSimple): + """SNMP driver class for APC RackPDU devices. + + SNMP objects for APC SNMPDriverAPCMasterSwitch PDU: + # 1.3.6.1.4.1.318.1.1.12.3.3.1.1.4 rPDUOutletControlOutletCommand + Values: 1=On, 2=Off, 3=PowerCycle, [...more options follow] + """ + + oid_device = (318, 1, 1, 12, 3, 3, 1, 1, 4) + value_power_on = 1 + value_power_off = 2 + + class SNMPDriverCyberPower(SNMPDriverSimple): """SNMP driver class for CyberPower PDU devices. @@ -522,7 +548,10 @@ class SNMPDriverEatonPower(SNMPDriverBase): # A dictionary of supported drivers keyed by snmp_driver attribute DRIVER_CLASSES = { - 'apc': SNMPDriverAPC, + 'apc': SNMPDriverAPCMasterSwitch, + 'apc_masterswitch': SNMPDriverAPCMasterSwitch, + 'apc_masterswitchplus': SNMPDriverAPCMasterSwitchPlus, + 'apc_rackpdu': SNMPDriverAPCRackPDU, 'aten': SNMPDriverAten, 'cyberpower': SNMPDriverCyberPower, 'eatonpower': SNMPDriverEatonPower, diff --git a/ironic/tests/drivers/test_snmp.py b/ironic/tests/drivers/test_snmp.py index a77211e35a..4865681d3c 100644 --- a/ironic/tests/drivers/test_snmp.py +++ b/ironic/tests/drivers/test_snmp.py @@ -189,6 +189,27 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase): info = snmp._parse_driver_info(node) self.assertEqual('apc', info.get('driver')) + def test__parse_driver_info_apc_masterswitch(self): + # Make sure the APC driver type is parsed. + info = db_utils.get_test_snmp_info(snmp_driver='apc_masterswitch') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('apc_masterswitch', info.get('driver')) + + def test__parse_driver_info_apc_masterswitchplus(self): + # Make sure the APC driver type is parsed. + info = db_utils.get_test_snmp_info(snmp_driver='apc_masterswitchplus') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('apc_masterswitchplus', info.get('driver')) + + def test__parse_driver_info_apc_rackpdu(self): + # Make sure the APC driver type is parsed. + info = db_utils.get_test_snmp_info(snmp_driver='apc_rackpdu') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('apc_rackpdu', info.get('driver')) + def test__parse_driver_info_aten(self): # Make sure the Aten driver type is parsed. info = db_utils.get_test_snmp_info(snmp_driver='aten') @@ -834,6 +855,94 @@ class SNMPDeviceDriverTestCase(db_base.DbTestCase): def test_apc_power_reset(self, mock_get_client): self._test_simple_device_power_reset('apc', mock_get_client) + def test_apc_masterswitch_snmp_objects(self, mock_get_client): + # Ensure the correct SNMP object OIDs and values are used by the APC + # masterswitch driver + self._update_driver_info(snmp_driver="apc_masterswitch", + snmp_outlet="6") + driver = snmp._get_driver(self.node) + oid = (1, 3, 6, 1, 4, 1, 318, 1, 1, 4, 4, 2, 1, 3, 6) + self.assertEqual(oid, driver._snmp_oid()) + self.assertEqual(1, driver.value_power_on) + self.assertEqual(2, driver.value_power_off) + + def test_apc_masterswitch_power_state_on(self, mock_get_client): + self._test_simple_device_power_state_on('apc_masterswitch', + mock_get_client) + + def test_apc_masterswitch_power_state_off(self, mock_get_client): + self._test_simple_device_power_state_off('apc_masterswitch', + mock_get_client) + + def test_apc_masterswitch_power_on(self, mock_get_client): + self._test_simple_device_power_on('apc_masterswitch', mock_get_client) + + def test_apc_masterswitch_power_off(self, mock_get_client): + self._test_simple_device_power_off('apc_masterswitch', mock_get_client) + + def test_apc_masterswitch_power_reset(self, mock_get_client): + self._test_simple_device_power_reset('apc_masterswitch', + mock_get_client) + + def test_apc_masterswitchplus_snmp_objects(self, mock_get_client): + # Ensure the correct SNMP object OIDs and values are used by the APC + # masterswitchplus driver + self._update_driver_info(snmp_driver="apc_masterswitchplus", + snmp_outlet="6") + driver = snmp._get_driver(self.node) + oid = (1, 3, 6, 1, 4, 1, 318, 1, 1, 6, 5, 1, 1, 5, 6) + self.assertEqual(oid, driver._snmp_oid()) + self.assertEqual(1, driver.value_power_on) + self.assertEqual(3, driver.value_power_off) + + def test_apc_masterswitchplus_power_state_on(self, mock_get_client): + self._test_simple_device_power_state_on('apc_masterswitchplus', + mock_get_client) + + def test_apc_masterswitchplus_power_state_off(self, mock_get_client): + self._test_simple_device_power_state_off('apc_masterswitchplus', + mock_get_client) + + def test_apc_masterswitchplus_power_on(self, mock_get_client): + self._test_simple_device_power_on('apc_masterswitchplus', + mock_get_client) + + def test_apc_masterswitchplus_power_off(self, mock_get_client): + self._test_simple_device_power_off('apc_masterswitchplus', + mock_get_client) + + def test_apc_masterswitchplus_power_reset(self, mock_get_client): + self._test_simple_device_power_reset('apc_masterswitchplus', + mock_get_client) + + def test_apc_rackpdu_snmp_objects(self, mock_get_client): + # Ensure the correct SNMP object OIDs and values are used by the APC + # rackpdu driver + self._update_driver_info(snmp_driver="apc_rackpdu", + snmp_outlet="6") + driver = snmp._get_driver(self.node) + oid = (1, 3, 6, 1, 4, 1, 318, 1, 1, 12, 3, 3, 1, 1, 4, 6) + + self.assertEqual(oid, driver._snmp_oid()) + self.assertEqual(1, driver.value_power_on) + self.assertEqual(2, driver.value_power_off) + + def test_apc_rackpdu_power_state_on(self, mock_get_client): + self._test_simple_device_power_state_on('apc_rackpdu', mock_get_client) + + def test_apc_rackpdu_power_state_off(self, mock_get_client): + self._test_simple_device_power_state_off('apc_rackpdu', + mock_get_client) + + def test_apc_rackpdu_power_on(self, mock_get_client): + self._test_simple_device_power_on('apc_rackpdu', mock_get_client) + + def test_apc_rackpdu_power_off(self, mock_get_client): + self._test_simple_device_power_off('apc_rackpdu', mock_get_client) + + def test_apc_rackpdu_power_reset(self, mock_get_client): + self._test_simple_device_power_reset('apc_rackpdu', mock_get_client) + def test_aten_snmp_objects(self, mock_get_client): # Ensure the correct SNMP object OIDs and values are used by the # Aten driver