From fc568056cd9f08a6bf0e4eb49d0554b6e04b62ac Mon Sep 17 00:00:00 2001 From: Shivanand Tendulker Date: Mon, 7 Mar 2016 21:37:29 -0800 Subject: [PATCH] Add RIS support for power operations This commit adds RIS support for power operations for the servers where RIS support is available. Added RIS support for following IloClient methods: hold_pwr_btn() press_pwr_btn() reset_server() set_host_power() Change-Id: Iab55d0d1501514ea5052b9c7b13d01b6c64548b9 Closes-Bug:1553869 --- proliantutils/ilo/client.py | 4 ++ proliantutils/ilo/ris.py | 87 ++++++++++++++++++++++++++ proliantutils/tests/ilo/test_client.py | 48 ++++++++++++++ proliantutils/tests/ilo/test_ris.py | 81 ++++++++++++++++++++++++ 4 files changed, 220 insertions(+) diff --git a/proliantutils/ilo/client.py b/proliantutils/ilo/client.py index 8c83059a..dac3be1b 100644 --- a/proliantutils/ilo/client.py +++ b/proliantutils/ilo/client.py @@ -32,10 +32,14 @@ SUPPORTED_RIS_METHODS = [ 'get_product_name', 'get_secure_boot_mode', 'get_vm_status', + 'hold_pwr_btn', 'insert_virtual_media', + 'press_pwr_btn', 'reset_bios_to_default', 'reset_ilo_credential', 'reset_secure_boot_keys', + 'reset_server', + 'set_host_power', 'set_http_boot_url', 'set_one_time_boot', 'set_pending_boot_mode', diff --git a/proliantutils/ilo/ris.py b/proliantutils/ilo/ris.py index 7f7899a2..8c2ea15d 100755 --- a/proliantutils/ilo/ris.py +++ b/proliantutils/ilo/ris.py @@ -43,6 +43,11 @@ DEVICE_COMMON_TO_RIS = {'NETWORK': 'Pxe', DEVICE_RIS_TO_COMMON = dict( (v, k) for (k, v) in DEVICE_COMMON_TO_RIS.items()) +POWER_STATE = { + 'ON': 'On', + 'OFF': 'ForceOff', +} + LOG = log.get_logger(__name__) @@ -710,6 +715,88 @@ class RISOperations(operations.IloOperations): data = self._get_host_details() return data['Power'].upper() + def _perform_power_op(self, oper): + """Perform requested power operation. + + :param oper: Type of power button press to simulate. + Supported values: 'ON', 'ForceOff' and 'ForceRestart' + :raises: IloError, on an error from iLO. + """ + + power_settings = {"Action": "Reset", + "ResetType": oper} + systems_uri = "/rest/v1/Systems/1" + + status, headers, response = self._rest_post(systems_uri, None, + power_settings) + if status >= 300: + msg = self._get_extended_error(response) + raise exception.IloError(msg) + + def reset_server(self): + """Resets the server. + + :raises: IloError, on an error from iLO. + """ + + self._perform_power_op("ForceRestart") + + def _press_pwr_btn(self, pushType="Press"): + """Simulates a physical press of the server power button. + + :param pushType: Type of power button press to simulate + Supported values are: 'Press' and 'PressAndHold' + :raises: IloError, on an error from iLO. + """ + power_settings = {"Action": "PowerButton", + "Target": "/Oem/Hp", + "PushType": pushType} + + systems_uri = "/rest/v1/Systems/1" + + status, headers, response = self._rest_post(systems_uri, None, + power_settings) + if status >= 300: + msg = self._get_extended_error(response) + raise exception.IloError(msg) + + def press_pwr_btn(self): + """Simulates a physical press of the server power button. + + :raises: IloError, on an error from iLO. + """ + self._press_pwr_btn() + + def hold_pwr_btn(self): + """Simulate a physical press and hold of the server power button. + + :raises: IloError, on an error from iLO. + """ + self._press_pwr_btn(pushType="PressAndHold") + + def set_host_power(self, power): + """Toggle the power button of server. + + :param power: 'ON' or 'OFF' + :raises: IloError, on an error from iLO. + """ + power = power.upper() + if (power is not None) and (power not in POWER_STATE): + msg = ("Invalid input '%(pow)s'. " + "The expected input is ON or OFF." % + {'pow': power}) + raise exception.IloInvalidInputError(msg) + + # Check current power status, do not act if it's in requested state. + cur_status = self.get_host_power_status() + + if cur_status == power: + LOG.debug(self._("Node is already in '%(power)s' power state."), + {'power': power}) + return + + self._perform_power_op(POWER_STATE[power]) + def get_http_boot_url(self): """Request the http boot url from system in uefi boot mode. diff --git a/proliantutils/tests/ilo/test_client.py b/proliantutils/tests/ilo/test_client.py index 33a446c7..33916168 100644 --- a/proliantutils/tests/ilo/test_client.py +++ b/proliantutils/tests/ilo/test_client.py @@ -513,3 +513,51 @@ class IloClientTestCase(testtools.TestCase): _call_method_mock.assert_called_once_with('update_firmware', some_url, some_component_type) + + @mock.patch.object(ris.RISOperations, 'hold_pwr_btn') + def test_hold_pwr_btn_gen9(self, hold_pwr_btn_mock): + self.client.model = 'Gen9' + self.client.hold_pwr_btn() + self.assertTrue(hold_pwr_btn_mock.called) + + @mock.patch.object(ribcl.RIBCLOperations, 'hold_pwr_btn') + def test_hold_pwr_btn_gen8(self, hold_pwr_btn_mock): + self.client.model = 'Gen8' + self.client.hold_pwr_btn() + self.assertTrue(hold_pwr_btn_mock.called) + + @mock.patch.object(ris.RISOperations, 'set_host_power') + def test_set_host_power_gen9(self, set_host_power_mock): + self.client.model = 'Gen9' + self.client.set_host_power('ON') + set_host_power_mock.assert_called_once_with('ON') + + @mock.patch.object(ribcl.RIBCLOperations, 'set_host_power') + def test_set_host_power_gen8(self, set_host_power_mock): + self.client.model = 'Gen8' + self.client.set_host_power('ON') + set_host_power_mock.assert_called_once_with('ON') + + @mock.patch.object(ris.RISOperations, 'press_pwr_btn') + def test_press_pwr_btn_gen9(self, press_pwr_btn_mock): + self.client.model = 'Gen9' + self.client.press_pwr_btn() + self.assertTrue(press_pwr_btn_mock.called) + + @mock.patch.object(ribcl.RIBCLOperations, 'press_pwr_btn') + def test_press_pwr_btn_gen8(self, press_pwr_btn_mock): + self.client.model = 'Gen8' + self.client.press_pwr_btn() + self.assertTrue(press_pwr_btn_mock.called) + + @mock.patch.object(ris.RISOperations, 'reset_server') + def test_reset_server_gen9(self, reset_server_mock): + self.client.model = 'Gen9' + self.client.reset_server() + self.assertTrue(reset_server_mock.called) + + @mock.patch.object(ribcl.RIBCLOperations, 'reset_server') + def test_reset_server_gen8(self, reset_server_mock): + self.client.model = 'Gen8' + self.client.reset_server() + self.assertTrue(reset_server_mock.called) diff --git a/proliantutils/tests/ilo/test_ris.py b/proliantutils/tests/ilo/test_ris.py index e1787ca5..dd395ce6 100755 --- a/proliantutils/tests/ilo/test_ris.py +++ b/proliantutils/tests/ilo/test_ris.py @@ -948,6 +948,41 @@ class IloRisTestCase(testtools.TestCase): self.assertRaises(exception.IloError, self.client.get_firmware_update_progress) + @mock.patch.object(ris.RISOperations, 'get_host_power_status') + def test_set_host_power_no_change(self, host_power_status_mock): + host_power_status_mock.return_value = 'ON' + self.client.set_host_power('on') + self.assertTrue(host_power_status_mock.called) + + @mock.patch.object(ris.RISOperations, 'get_host_power_status') + def test_set_host_power_exc(self, host_power_status_mock): + self.assertRaises(exception.IloInvalidInputError, + self.client.set_host_power, 'invalid') + + @mock.patch.object(ris.RISOperations, '_perform_power_op') + @mock.patch.object(ris.RISOperations, 'get_host_power_status') + def test_set_host_power_change(self, host_power_status_mock, + perform_power_op_mock): + host_power_status_mock.return_value = 'ON' + self.client.set_host_power('off') + host_power_status_mock.assert_called_once_with() + perform_power_op_mock.assert_called_once_with('ForceOff') + + @mock.patch.object(ris.RISOperations, '_perform_power_op') + def test_reset_server(self, mock_perform_power): + self.client.reset_server() + mock_perform_power.assert_called_once_with("ForceRestart") + + @mock.patch.object(ris.RISOperations, '_press_pwr_btn') + def test_hold_pwr_btn(self, press_pwr_btn_mock): + self.client.hold_pwr_btn() + press_pwr_btn_mock.assert_called_once_with(pushType="PressAndHold") + + @mock.patch.object(ris.RISOperations, '_press_pwr_btn') + def test_press_pwr_btn(self, press_pwr_btn_mock): + self.client.hold_pwr_btn() + press_pwr_btn_mock.assert_called_once_with(pushType="PressAndHold") + class TestRISOperationsPrivateMethods(testtools.TestCase): @@ -1673,3 +1708,49 @@ class TestRISOperationsPrivateMethods(testtools.TestCase): # | WHEN | & | THEN | self.assertRaises(exception.IloCommandNotSupportedError, self.client._get_firmware_update_service_resource) + + @mock.patch.object(ris.RISOperations, '_rest_post') + def test_press_pwr_btn(self, rest_post_mock): + systems_uri = "/rest/v1/Systems/1" + new_pow_settings = {"Action": "PowerButton", + "Target": "/Oem/Hp", + "PushType": "Press"} + rest_post_mock.return_value = (200, ris_outputs.GET_HEADERS, + ris_outputs.REST_POST_RESPONSE) + self.client._press_pwr_btn() + rest_post_mock.assert_called_once_with(systems_uri, None, + new_pow_settings) + + @mock.patch.object(ris.RISOperations, '_rest_post') + def test_press_pwr_btn_patch_fail(self, rest_post_mock): + systems_uri = "/rest/v1/Systems/1" + new_pow_settings = {"Action": "PowerButton", + "Target": "/Oem/Hp", + "PushType": "Press"} + rest_post_mock.return_value = (301, ris_outputs.GET_HEADERS, + ris_outputs.REST_FAILURE_OUTPUT) + self.assertRaises(exception.IloError, + self.client._press_pwr_btn, 'Press') + rest_post_mock.assert_called_once_with(systems_uri, None, + new_pow_settings) + + @mock.patch.object(ris.RISOperations, '_rest_post') + def test_perform_power_op(self, rest_post_mock): + systems_uri = "/rest/v1/Systems/1" + new_pow_settings = {"Action": "Reset", "ResetType": "ForceRestart"} + rest_post_mock.return_value = (200, ris_outputs.GET_HEADERS, + ris_outputs.REST_POST_RESPONSE) + self.client.reset_server() + rest_post_mock.assert_called_once_with(systems_uri, None, + new_pow_settings) + + @mock.patch.object(ris.RISOperations, '_rest_post') + def test_perform_power_op_fail(self, rest_post_mock): + systems_uri = "/rest/v1/Systems/1" + new_pow_settings = {"Action": "Reset", "ResetType": "ForceRestart"} + rest_post_mock.return_value = (301, ris_outputs.GET_HEADERS, + ris_outputs.REST_FAILURE_OUTPUT) + self.assertRaises(exception.IloError, + self.client.reset_server) + rest_post_mock.assert_called_once_with(systems_uri, None, + new_pow_settings)