diff --git a/proliantutils/redfish/redfish.py b/proliantutils/redfish/redfish.py index 5b61477e..4a71304c 100644 --- a/proliantutils/redfish/redfish.py +++ b/proliantutils/redfish/redfish.py @@ -23,6 +23,7 @@ import tempfile from OpenSSL.crypto import FILETYPE_ASN1 from OpenSSL.crypto import load_certificate +import retrying from six.moves.urllib import parse import sushy from sushy import utils @@ -46,11 +47,14 @@ from proliantutils import utils as common_utils Class specific for Redfish APIs. """ +MAX_RETRY_ATTEMPTS = 3 # Maximum number of attempts to be retried +MAX_TIME_BEFORE_RETRY = 7 * 1000 # wait time in milliseconds before retry + GET_POWER_STATE_MAP = { sushy.SYSTEM_POWER_STATE_ON: 'ON', - sushy.SYSTEM_POWER_STATE_POWERING_ON: 'ON', + sushy.SYSTEM_POWER_STATE_POWERING_ON: 'PoweringOn', sushy.SYSTEM_POWER_STATE_OFF: 'OFF', - sushy.SYSTEM_POWER_STATE_POWERING_OFF: 'OFF' + sushy.SYSTEM_POWER_STATE_POWERING_OFF: 'PoweringOff' } POWER_RESET_MAP = { @@ -264,6 +268,46 @@ class RedfishOperations(operations.IloOperations): sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) return GET_POWER_STATE_MAP.get(sushy_system.power_state) + def _perform_power_op(self, power): + """This method performs power operation. + + :param: power : target power state + """ + sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) + try: + sushy_system.reset_system(POWER_RESET_MAP[power]) + except sushy.exceptions.SushyError as e: + msg = (self._('The Redfish controller failed to set power state ' + 'of server to %(target_value)s. Error %(error)s') % + {'target_value': power, 'error': str(e)}) + LOG.debug(msg) + raise exception.IloError(msg) + + @retrying.retry( + stop_max_attempt_number=MAX_RETRY_ATTEMPTS, + retry_on_result=lambda state: state != 'ON', + wait_fixed=MAX_TIME_BEFORE_RETRY + ) + def _retry_until_powered_on(self, power): + """This method retries power on operation. + + :param: power : target power state + """ + # If the system is in the same power state as + # requested by the user, it gives the error + # InvalidOperationForSystemState. To avoid this error + # the power state is checked before power on + # operation is performed. + status = self.get_host_power_status() + allowed_states = ['ON', 'PoweringOn'] + if power == "OFF": + allowed_states = ['OFF', 'PoweringOff'] + if (status != power or status not in allowed_states or status is None): + self._perform_power_op(power) + return self.get_host_power_status() + else: + return status + def reset_server(self): """Resets the server. @@ -302,9 +346,8 @@ class RedfishOperations(operations.IloOperations): "state."), {'target_value': target_value}) return - sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) try: - sushy_system.reset_system(POWER_RESET_MAP[target_value]) + self._retry_until_powered_on(target_value) except sushy.exceptions.SushyError as e: msg = (self._('The Redfish controller failed to set power state ' 'of server to %(target_value)s. Error %(error)s') % diff --git a/proliantutils/tests/redfish/test_redfish.py b/proliantutils/tests/redfish/test_redfish.py index 588b83db..9b081c71 100644 --- a/proliantutils/tests/redfish/test_redfish.py +++ b/proliantutils/tests/redfish/test_redfish.py @@ -139,16 +139,55 @@ class RedfishOperationsTestCase(testtools.TestCase): 'The Redfish controller failed to set power state of server to ON', self.rf_client.set_host_power, 'ON') - def test_set_host_power_invalid_input(self): + @mock.patch.object(redfish.RedfishOperations, 'get_host_power_status') + def test_set_host_power_invalid_input(self, host_power_status_mock): self.assertRaisesRegex( exception.InvalidInputError, 'The parameter "target_value" value "Off" is invalid.', self.rf_client.set_host_power, 'Off') @mock.patch.object(redfish.RedfishOperations, 'get_host_power_status') - def test_set_host_power_change(self, get_host_power_status_mock): - get_host_power_status_mock.return_value = 'OFF' + def test_set_host_power_exc(self, host_power_status_mock): + self.assertRaises(exception.InvalidInputError, + self.rf_client.set_host_power, 'invalid') + + @mock.patch.object(redfish.RedfishOperations, 'get_host_power_status') + @mock.patch.object(redfish.RedfishOperations, '_retry_until_powered_on') + def test_set_host_power_off(self, retry_mock, host_power_status_mock): + host_power_status_mock.return_value = 'ON' + self.rf_client.set_host_power('OFF') + host_power_status_mock.assert_called_once_with() + self.assertTrue(retry_mock.called) + + @mock.patch.object(redfish.RedfishOperations, '_perform_power_op') + @mock.patch.object(redfish.RedfishOperations, 'get_host_power_status') + @mock.patch.object(redfish.RedfishOperations, '_retry_until_powered_on') + def test_set_host_power_on(self, retry_mock, host_power_status_mock, + perform_power_op_mock): + host_power_status_mock.return_value = 'OFF' self.rf_client.set_host_power('ON') + host_power_status_mock.assert_called_once_with() + self.assertFalse(perform_power_op_mock.called) + self.assertTrue(retry_mock.called) + + @mock.patch.object(redfish.RedfishOperations, '_perform_power_op') + @mock.patch.object(redfish.RedfishOperations, 'get_host_power_status') + def test_retry_until_powered_on_3times(self, host_power_status_mock, + perform_power_mock): + host_power_status_mock.side_effect = ['OFF', 'OFF', 'ON'] + self.rf_client._retry_until_powered_on('ON') + self.assertEqual(3, host_power_status_mock.call_count) + + @mock.patch.object(redfish.RedfishOperations, '_perform_power_op') + @mock.patch.object(redfish.RedfishOperations, 'get_host_power_status') + def test_retry_until_powered_on(self, host_power_status_mock, + perform_power_mock): + host_power_status_mock.return_value = 'ON' + self.rf_client._retry_until_powered_on('ON') + self.assertEqual(1, host_power_status_mock.call_count) + + def test_perform_power_op(self): + self.rf_client._perform_power_op("ON") self.sushy.get_system().reset_system.assert_called_once_with( sushy.RESET_ON) diff --git a/requirements.txt b/requirements.txt index e72d59df..998cd247 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,5 +11,5 @@ pyasn1-lextudio>=1.1.0 # BSD pyasn1-modules-lextudio>=0.2.0 # BSD # Redfish communication uses the Sushy library -sushy>=4.4.0 +sushy>=4.5.0 pyOpenSSL>=19.1.0