Adds a retry workaround for power ON failure

Closes: 2021995
Change-Id: Id98474c334840ecc86371aa6b6395f7b4d6b1714
This commit is contained in:
Nisha Agarwal 2023-09-06 08:38:04 +00:00
parent 56c507344d
commit 5839129389
3 changed files with 90 additions and 8 deletions

View File

@ -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') %

View File

@ -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)

View File

@ -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