diff --git a/proliantutils/ilo/client.py b/proliantutils/ilo/client.py index be3f6c27..99e15eef 100644 --- a/proliantutils/ilo/client.py +++ b/proliantutils/ilo/client.py @@ -39,6 +39,7 @@ SUPPORTED_RIS_METHODS = [ 'get_supported_boot_mode', 'get_vm_status', 'hold_pwr_btn', + 'inject_nmi', 'insert_virtual_media', 'press_pwr_btn', 'reset_bios_to_default', @@ -73,6 +74,7 @@ SUPPORTED_REDFISH_METHODS = [ 'get_current_boot_mode', 'activate_license', 'eject_virtual_media', + 'inject_nmi', 'insert_virtual_media', 'set_vm_status', 'update_firmware', @@ -641,3 +643,15 @@ class IloClient(operations.IloOperations): """ return self._call_method( 'update_firmware', firmware_url, component_type) + + def inject_nmi(self): + """Inject NMI, Non Maskable Interrupt. + + Inject NMI (Non Maskable Interrupt) for a node immediately. + + :raises: IloError, on an error from iLO + :raises: IloConnectionError, if not able to reach iLO. + :raises: IloCommandNotSupportedError, if the command is + not supported on the server + """ + return self._call_method('inject_nmi') diff --git a/proliantutils/ilo/operations.py b/proliantutils/ilo/operations.py index be8244b3..9298dcee 100644 --- a/proliantutils/ilo/operations.py +++ b/proliantutils/ilo/operations.py @@ -360,3 +360,15 @@ class IloOperations(object): not supported on the server """ raise exception.IloCommandNotSupportedError(ERRMSG) + + def inject_nmi(self): + """Inject NMI, Non Maskable Interrupt. + + Inject NMI (Non Maskable Interrupt) for a node immediately. + + :raises: IloError, on an error from iLO + :raises: IloConnectionError, if not able to reach iLO. + :raises: IloCommandNotSupportedError, if the command is + not supported on the server + """ + raise exception.IloCommandNotSupportedError(ERRMSG) diff --git a/proliantutils/ilo/ribcl.py b/proliantutils/ilo/ribcl.py index 1f0aa547..c220038c 100644 --- a/proliantutils/ilo/ribcl.py +++ b/proliantutils/ilo/ribcl.py @@ -1183,6 +1183,18 @@ class RIBCLOperations(operations.IloOperations): if location == 'Embedded': nic_dict[port] = mac + def inject_nmi(self): + """Inject NMI, Non Maskable Interrupt. + + Inject NMI (Non Maskable Interrupt) for a node immediately. + + :raises: IloError, on an error from iLO + :raises: IloCommandNotSupportedError + """ + platform = self.get_product_name() + msg = ("`inject_nmi` is not supported on %s" % platform) + raise exception.IloCommandNotSupportedError(msg) + # The below block of code is there only for backward-compatibility # reasons (before commit 47608b6 for ris-support). diff --git a/proliantutils/ilo/ris.py b/proliantutils/ilo/ris.py index 94d8b480..9dc51cea 100755 --- a/proliantutils/ilo/ris.py +++ b/proliantutils/ilo/ris.py @@ -789,7 +789,8 @@ class RISOperations(rest.RestConnectorBase, operations.IloOperations): """Perform requested power operation. :param oper: Type of power button press to simulate. - Supported values: 'ON', 'ForceOff' and 'ForceRestart' + Supported values: 'ON', 'ForceOff', 'ForceRestart' and + 'Nmi' :raises: IloError, on an error from iLO. """ @@ -1820,3 +1821,16 @@ class RISOperations(rest.RestConnectorBase, operations.IloOperations): except exception.IloCommandNotSupportedError: nvn_status = False return nvn_status + + def inject_nmi(self): + """Inject NMI, Non Maskable Interrupt. + + Inject NMI (Non Maskable Interrupt) for a node immediately. + + :raises: IloError, on an error from iLO + """ + cur_status = self.get_host_power_status() + if cur_status != 'ON': + raise exception.IloError("Server is not in powered on state.") + + self._perform_power_op("Nmi") diff --git a/proliantutils/redfish/redfish.py b/proliantutils/redfish/redfish.py index f356eee0..7f6b2dcf 100644 --- a/proliantutils/redfish/redfish.py +++ b/proliantutils/redfish/redfish.py @@ -1006,3 +1006,22 @@ class RedfishOperations(operations.IloOperations): else: msg = 'iSCSI initiator cannot be retrieved in BIOS boot mode' raise exception.IloCommandNotSupportedInBiosError(msg) + + def inject_nmi(self): + """Inject NMI, Non Maskable Interrupt. + + Inject NMI (Non Maskable Interrupt) for a node immediately. + + :raises: IloError, on an error from iLO + """ + sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) + if sushy_system.power_state != sushy.SYSTEM_POWER_STATE_ON: + raise exception.IloError("Server is not in powered on state.") + + try: + sushy_system.reset_system(sushy.RESET_NMI) + except sushy.exceptions.SushyError as e: + msg = (self._('The Redfish controller failed to inject nmi to ' + 'server. Error %(error)s') % {'error': str(e)}) + LOG.debug(msg) + raise exception.IloError(msg) diff --git a/proliantutils/tests/ilo/test_client.py b/proliantutils/tests/ilo/test_client.py index 53e6a974..3feda013 100644 --- a/proliantutils/tests/ilo/test_client.py +++ b/proliantutils/tests/ilo/test_client.py @@ -1011,6 +1011,24 @@ class IloClientTestCase(testtools.TestCase): call_mock.assert_called_once_with('get_essential_properties') self.assertFalse(snmp_mock.called) + @mock.patch.object(client.IloClient, '_call_method') + def test_inject_nmi(self, call_mock): + self.client.inject_nmi() + call_mock.assert_called_once_with('inject_nmi') + + @mock.patch.object(ris.RISOperations, 'inject_nmi') + def test_inject_nmi_gen9(self, inject_nmi_mock): + self.client.model = 'Gen9' + self.client.inject_nmi() + inject_nmi_mock.assert_called_once_with() + + @mock.patch.object(ribcl.RIBCLOperations, 'get_product_name') + def test_inject_nmi_gen8(self, product_mock): + self.client.model = 'Gen8' + self.assertRaisesRegexp(exception.IloCommandNotSupportedError, + 'not supported', + self.client.inject_nmi) + class IloRedfishClientTestCase(testtools.TestCase): diff --git a/proliantutils/tests/ilo/test_ribcl.py b/proliantutils/tests/ilo/test_ribcl.py index 7d484552..2d348282 100644 --- a/proliantutils/tests/ilo/test_ribcl.py +++ b/proliantutils/tests/ilo/test_ribcl.py @@ -1041,6 +1041,13 @@ class IloRibclTestCaseBeforeRisSupport(unittest.TestCase): self.ilo.set_vm_status('cdrom', 'boot_once', 'yes') self.assertTrue(request_ilo_mock.called) + @mock.patch.object(ribcl.RIBCLOperations, 'get_product_name') + def test_inject_nmi(self, product_name_mock): + product_name_mock.return_value = constants.GET_PRODUCT_NAME + self.assertRaisesRegexp(exception.IloCommandNotSupportedError, + 'ProLiant DL380 G7', + self.ilo.inject_nmi) + if __name__ == '__main__': unittest.main() diff --git a/proliantutils/tests/ilo/test_ris.py b/proliantutils/tests/ilo/test_ris.py index 005ba9a8..a65290b1 100755 --- a/proliantutils/tests/ilo/test_ris.py +++ b/proliantutils/tests/ilo/test_ris.py @@ -1239,6 +1239,25 @@ class IloRisTestCase(testtools.TestCase): self.client.hold_pwr_btn() press_pwr_btn_mock.assert_called_once_with(pushType="PressAndHold") + @mock.patch.object(ris.RISOperations, '_perform_power_op') + @mock.patch.object(ris.RISOperations, 'get_host_power_status') + def test_inject_nmi(self, get_power_status_mock, + perform_power_op_mock): + get_power_status_mock.return_value = 'ON' + self.client.inject_nmi() + get_power_status_mock.assert_called_once_with() + perform_power_op_mock.assert_called_once_with('Nmi') + + @mock.patch.object(ris.RISOperations, '_perform_power_op') + @mock.patch.object(ris.RISOperations, 'get_host_power_status') + def test_inject_nmi_exc(self, get_power_status_mock, + perform_power_op_mock): + get_power_status_mock.return_value = 'OFF' + self.assertRaises(exception.IloError, + self.client.inject_nmi) + get_power_status_mock.assert_called_once_with() + self.assertFalse(perform_power_op_mock.called) + class TestRISOperationsPrivateMethods(testtools.TestCase): diff --git a/proliantutils/tests/redfish/test_redfish.py b/proliantutils/tests/redfish/test_redfish.py index d8a97583..e19412dc 100644 --- a/proliantutils/tests/redfish/test_redfish.py +++ b/proliantutils/tests/redfish/test_redfish.py @@ -1419,3 +1419,26 @@ class RedfishOperationsTestCase(testtools.TestCase): 'iSCSI initiator cannot be retrieved in ' 'BIOS boot mode', self.rf_client.get_iscsi_initiator_info) + + def test_inject_nmi(self): + self.sushy.get_system().power_state = sushy.SYSTEM_POWER_STATE_ON + self.rf_client.inject_nmi() + self.sushy.get_system().reset_system.assert_called_once_with( + sushy.RESET_NMI) + + def test_inject_nmi_power_off(self): + self.sushy.get_system().power_state = sushy.SYSTEM_POWER_STATE_OFF + self.assertRaisesRegex( + exception.IloError, + 'Server is not in powered on state.', + self.rf_client.inject_nmi) + self.assertFalse(self.sushy.get_system().reset_system.called) + + def test_inject_nmi_sushy_exc(self): + self.sushy.get_system().power_state = sushy.SYSTEM_POWER_STATE_ON + self.sushy.get_system().reset_system.side_effect = ( + sushy.exceptions.SushyError) + self.assertRaisesRegex( + exception.IloError, + 'The Redfish controller failed to inject nmi', + self.rf_client.inject_nmi)