Add import system configuration method
set_virtual_boot_device is also using the import system configuration action, but its usage is very specific, with retries and rebooting. To keep it simple, this adds import_system_configuration, which is asynchronous and returns a TaskMonitor. Additionally, this changes the case of the header field names used by the asynchronous.http_call method to make them work with unit tests. While the requests package can handle real header field names case- insensitively, when they are mocked in unit tests, case needs to match. Story: 2003594 Task: 41576 Change-Id: I3b5e620e7b1939a029bd59b4578c0f52c8789598
This commit is contained in:
parent
ed70d136ac
commit
1020e805d3
@ -4,4 +4,4 @@
|
||||
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
python-dateutil>=2.7.0 # BSD
|
||||
sushy>=2.0.0 # Apache-2.0
|
||||
sushy>=3.7.0 # Apache-2.0
|
||||
|
@ -44,7 +44,7 @@ def http_call(conn, method, *args, **kwargs):
|
||||
|
||||
location = None
|
||||
while response.status_code == 202:
|
||||
location = response.headers.get('location', location)
|
||||
location = response.headers.get('Location', location)
|
||||
if not location:
|
||||
raise sushy.exceptions.ExtensionError(
|
||||
error='Response %d to HTTP %s with args %s, kwargs %s '
|
||||
@ -52,7 +52,7 @@ def http_call(conn, method, *args, **kwargs):
|
||||
'header' % (response.status_code, method.upper(),
|
||||
args, kwargs))
|
||||
|
||||
retry_after = response.headers.get('retry-after')
|
||||
retry_after = response.headers.get('Retry-After')
|
||||
if retry_after:
|
||||
retry_after = _to_datetime(retry_after)
|
||||
sleep_for = max(0, (retry_after - datetime.now()).total_seconds())
|
||||
|
@ -37,3 +37,24 @@ RESET_IDRAC_GRACEFUL_RESTART = 'graceful restart'
|
||||
|
||||
RESET_IDRAC_FORCE_RESTART = 'force restart'
|
||||
"""Perform an immediate (non-graceful) shutdown, followed by a restart"""
|
||||
|
||||
# ImportSystemConfiguration ShutdownType values
|
||||
IMPORT_SHUTDOWN_GRACEFUL = 'graceful shutdown'
|
||||
"""Graceful shutdown for Import System Configuration
|
||||
|
||||
Will wait for the host up to 5 minutes to shut down before timing out. The
|
||||
operating system can potentially deny or ignore the graceful shutdown request.
|
||||
"""
|
||||
|
||||
IMPORT_SHUTDOWN_FORCED = 'forced shutdown'
|
||||
"""Forced shutdown for Import System Configuration
|
||||
|
||||
The host server will be powered off immediately. Should be used when it is safe
|
||||
to power down the host.
|
||||
"""
|
||||
|
||||
IMPORT_SHUTDOWN_NO_REBOOT = 'no shutdown'
|
||||
"""No reboot for Import System Configuration
|
||||
|
||||
No shutdown performed. Explicit reboot is necessary to apply changes.
|
||||
"""
|
||||
|
@ -19,6 +19,7 @@ import sushy
|
||||
from sushy.resources import base
|
||||
from sushy.resources import common
|
||||
from sushy.resources.oem import base as oem_base
|
||||
from sushy.taskmonitor import TaskMonitor
|
||||
from sushy import utils as sushy_utils
|
||||
|
||||
from sushy_oem_idrac import asynchronous
|
||||
@ -48,8 +49,13 @@ class ExportActionField(common.ActionField):
|
||||
shared_parameters = SharedParameters('ShareParameters')
|
||||
|
||||
|
||||
class ImportActionField(common.ActionField):
|
||||
allowed_shutdown_type_values = base.Field(
|
||||
'ShutdownType@Redfish.AllowableValues', adapter=list)
|
||||
|
||||
|
||||
class DellManagerActionsField(base.CompositeField):
|
||||
import_system_configuration = common.ActionField(
|
||||
import_system_configuration = ImportActionField(
|
||||
lambda key, **kwargs: key.endswith(
|
||||
'#OemManager.ImportSystemConfiguration'))
|
||||
|
||||
@ -350,6 +356,52 @@ VFDD\
|
||||
LOG.error(error)
|
||||
raise sushy.exceptions.ExtensionError(error=error)
|
||||
|
||||
def get_allowed_import_shutdown_type_values(self):
|
||||
"""Get the allowed shutdown types of import system configuration.
|
||||
|
||||
:returns: A set of allowed shutdown type values.
|
||||
"""
|
||||
import_action = self._actions.import_system_configuration
|
||||
allowed_values = import_action.allowed_shutdown_type_values
|
||||
|
||||
if not allowed_values:
|
||||
LOG.warning('Could not figure out the allowed values for the '
|
||||
'shutdown type of import system configuration at %s',
|
||||
self.path)
|
||||
return set(mgr_maps.IMPORT_SHUTDOWN_VALUE_MAP_REV)
|
||||
|
||||
return set([mgr_maps.IMPORT_SHUTDOWN_VALUE_MAP[value] for value in
|
||||
set(mgr_maps.IMPORT_SHUTDOWN_VALUE_MAP).
|
||||
intersection(allowed_values)])
|
||||
|
||||
def import_system_configuration(self, import_buffer):
|
||||
"""Imports system configuration.
|
||||
|
||||
Caller needs to handle system reboot separately.
|
||||
|
||||
:param import_buffer: Configuration data to be imported.
|
||||
:returns: Task monitor instance to watch for task completion
|
||||
"""
|
||||
action_data = dict(self.ACTION_DATA, ImportBuffer=import_buffer)
|
||||
# Caller needs to handle system reboot separately to preserve
|
||||
# one-time boot settings.
|
||||
shutdown_type = mgr_cons.IMPORT_SHUTDOWN_NO_REBOOT
|
||||
|
||||
allowed_shutdown_types = self.get_allowed_import_shutdown_type_values()
|
||||
if shutdown_type not in allowed_shutdown_types:
|
||||
raise sushy.exceptions.InvalidParameterValueError(
|
||||
parameter='shutdown_type', value=shutdown_type,
|
||||
valid_values=allowed_shutdown_types)
|
||||
|
||||
action_data['ShutdownType'] =\
|
||||
mgr_maps.IMPORT_SHUTDOWN_VALUE_MAP_REV[shutdown_type]
|
||||
|
||||
response = self._conn.post(self.import_system_configuration_uri,
|
||||
data=action_data)
|
||||
|
||||
return TaskMonitor.from_response(
|
||||
self._conn, response, self.import_system_configuration_uri)
|
||||
|
||||
|
||||
def get_extension(*args, **kwargs):
|
||||
return DellManagerExtension
|
||||
|
@ -32,3 +32,12 @@ RESET_IDRAC_VALUE_MAP = {
|
||||
}
|
||||
|
||||
RESET_IDRAC_VALUE_MAP_REV = utils.revert_dictionary(RESET_IDRAC_VALUE_MAP)
|
||||
|
||||
IMPORT_SHUTDOWN_VALUE_MAP = {
|
||||
'Graceful': mgr_cons.IMPORT_SHUTDOWN_GRACEFUL,
|
||||
'Forced': mgr_cons.IMPORT_SHUTDOWN_FORCED,
|
||||
'NoReboot': mgr_cons.IMPORT_SHUTDOWN_NO_REBOOT
|
||||
}
|
||||
|
||||
IMPORT_SHUTDOWN_VALUE_MAP_REV =\
|
||||
utils.revert_dictionary(IMPORT_SHUTDOWN_VALUE_MAP)
|
||||
|
@ -20,12 +20,14 @@ from unittest import mock
|
||||
from oslotest.base import BaseTestCase
|
||||
import sushy
|
||||
from sushy.resources.manager import manager
|
||||
from sushy.taskmonitor import TaskMonitor
|
||||
|
||||
from sushy_oem_idrac.resources.manager import constants as mgr_cons
|
||||
from sushy_oem_idrac.resources.manager import idrac_card_service as idrac_card
|
||||
from sushy_oem_idrac.resources.manager import job_collection as jc
|
||||
from sushy_oem_idrac.resources.manager import job_service as job
|
||||
from sushy_oem_idrac.resources.manager import lifecycle_service as lifecycle
|
||||
from sushy_oem_idrac.resources.manager import manager as oem_manager
|
||||
|
||||
|
||||
class ManagerTestCase(BaseTestCase):
|
||||
@ -41,7 +43,9 @@ class ManagerTestCase(BaseTestCase):
|
||||
|
||||
mock_response = self.conn.post.return_value
|
||||
mock_response.status_code = 202
|
||||
mock_response.headers.get.return_value = '1'
|
||||
mock_response.headers = {
|
||||
'Location': '/redfish/v1/TaskService/Tasks/JID_905749031119'}
|
||||
mock_response.content = None
|
||||
|
||||
self.manager = manager.Manager(self.conn, '/redfish/v1/Managers/BMC',
|
||||
redfish_version='1.0.2')
|
||||
@ -215,3 +219,44 @@ class ManagerTestCase(BaseTestCase):
|
||||
job_collection.path)
|
||||
self.assertIsInstance(job_collection,
|
||||
jc.DellJobCollection)
|
||||
|
||||
def test_get_allowed_import_shutdown_type_values(self):
|
||||
oem = self.manager.get_oem_extension('Dell')
|
||||
expected_values = {mgr_cons.IMPORT_SHUTDOWN_GRACEFUL,
|
||||
mgr_cons.IMPORT_SHUTDOWN_FORCED,
|
||||
mgr_cons.IMPORT_SHUTDOWN_NO_REBOOT}
|
||||
allowed_values = oem.get_allowed_import_shutdown_type_values()
|
||||
self.assertIsInstance(allowed_values, set)
|
||||
self.assertEqual(expected_values, allowed_values)
|
||||
|
||||
@mock.patch.object(oem_manager, 'LOG', autospec=True)
|
||||
def test_get_allowed_import_shutdown_type_values_missing(self, mock_log):
|
||||
oem = self.manager.get_oem_extension('Dell')
|
||||
import_action = ('OemManager.v1_0_0'
|
||||
'#OemManager.ImportSystemConfiguration')
|
||||
oem.json['Actions']['Oem'][import_action].pop(
|
||||
'ShutdownType@Redfish.AllowableValues')
|
||||
oem.refresh()
|
||||
expected_values = {mgr_cons.IMPORT_SHUTDOWN_GRACEFUL,
|
||||
mgr_cons.IMPORT_SHUTDOWN_FORCED,
|
||||
mgr_cons.IMPORT_SHUTDOWN_NO_REBOOT}
|
||||
allowed_values = oem.get_allowed_import_shutdown_type_values()
|
||||
self.assertIsInstance(allowed_values, set)
|
||||
self.assertEqual(expected_values, allowed_values)
|
||||
mock_log.warning.assert_called_once()
|
||||
|
||||
def test_import_system_configuration(self):
|
||||
oem = self.manager.get_oem_extension('Dell')
|
||||
|
||||
result = oem.import_system_configuration('{"key": "value"}')
|
||||
|
||||
self.conn.post.assert_called_once_with(
|
||||
'/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/EID_674_Manager'
|
||||
'.ImportSystemConfiguration', data={'ShareParameters':
|
||||
{'Target': 'ALL'},
|
||||
'ImportBuffer':
|
||||
'{"key": "value"}',
|
||||
'ShutdownType': 'NoReboot'})
|
||||
self.assertIsInstance(result, TaskMonitor)
|
||||
self.assertEqual('/redfish/v1/TaskService/Tasks/JID_905749031119',
|
||||
result.task_monitor_uri)
|
||||
|
Loading…
Reference in New Issue
Block a user