Merge "Add import, export configuration to idrac-redfish"
This commit is contained in:
commit
ff4c370d4b
@ -34,7 +34,12 @@ opts = [
|
||||
min=1,
|
||||
help=_('Maximum number of retries for '
|
||||
'the configuration job to complete '
|
||||
'successfully.'))
|
||||
'successfully.')),
|
||||
cfg.IntOpt('query_import_config_job_status_interval',
|
||||
min=0,
|
||||
default=60,
|
||||
help=_('Number of seconds to wait between checking for '
|
||||
'completed import configuration task'))
|
||||
]
|
||||
|
||||
|
||||
|
@ -20,8 +20,10 @@
|
||||
DRAC management interface
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
from futurist import periodics
|
||||
from ironic_lib import metrics_utils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
@ -29,15 +31,21 @@ from oslo_utils import importutils
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import molds
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules.drac import common as drac_common
|
||||
from ironic.drivers.modules.drac import job as drac_job
|
||||
from ironic.drivers.modules.redfish import management as redfish_management
|
||||
from ironic.drivers.modules.redfish import utils as redfish_utils
|
||||
|
||||
|
||||
drac_exceptions = importutils.try_import('dracclient.exceptions')
|
||||
sushy = importutils.try_import('sushy')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -301,14 +309,333 @@ def set_boot_device(node, device, persistent=False):
|
||||
|
||||
|
||||
class DracRedfishManagement(redfish_management.RedfishManagement):
|
||||
"""iDRAC Redfish interface for management-related actions.
|
||||
"""iDRAC Redfish interface for management-related actions."""
|
||||
|
||||
Presently, this class entirely defers to its base class, a generic,
|
||||
vendor-independent Redfish interface. Future resolution of Dell EMC-
|
||||
specific incompatibilities and introduction of vendor value added
|
||||
should be implemented by this class.
|
||||
"""
|
||||
pass
|
||||
EXPORT_CONFIGURATION_ARGSINFO = {
|
||||
"export_configuration_location": {
|
||||
"description": "URL of location to save the configuration to.",
|
||||
"required": True,
|
||||
}
|
||||
}
|
||||
|
||||
IMPORT_CONFIGURATION_ARGSINFO = {
|
||||
"import_configuration_location": {
|
||||
"description": "URL of location to fetch desired configuration "
|
||||
"from.",
|
||||
"required": True,
|
||||
}
|
||||
}
|
||||
|
||||
IMPORT_EXPORT_CONFIGURATION_ARGSINFO = {**EXPORT_CONFIGURATION_ARGSINFO,
|
||||
**IMPORT_CONFIGURATION_ARGSINFO}
|
||||
|
||||
@base.deploy_step(priority=0, argsinfo=EXPORT_CONFIGURATION_ARGSINFO)
|
||||
@base.clean_step(priority=0, argsinfo=EXPORT_CONFIGURATION_ARGSINFO)
|
||||
def export_configuration(self, task, export_configuration_location):
|
||||
"""Export the configuration of the server.
|
||||
|
||||
Exports the configuration of the server against which the step is run
|
||||
and stores it in specific format in indicated location.
|
||||
|
||||
Uses Dell's Server Configuration Profile (SCP) from `sushy-oem-idrac`
|
||||
library to get ALL configuration for cloning.
|
||||
|
||||
:param task: A task from TaskManager.
|
||||
:param export_configuration_location: URL of location to save the
|
||||
configuration to.
|
||||
|
||||
:raises: MissingParameterValue if missing configuration name of a file
|
||||
to save the configuration to
|
||||
:raises: DracOperatationError when no managagers for Redfish system
|
||||
found or configuration export from SCP failed
|
||||
:raises: RedfishError when loading OEM extension failed
|
||||
"""
|
||||
if not export_configuration_location:
|
||||
raise exception.MissingParameterValue(
|
||||
_('export_configuration_location missing'))
|
||||
|
||||
system = redfish_utils.get_system(task.node)
|
||||
configuration = None
|
||||
|
||||
if not system.managers:
|
||||
raise exception.DracOperationError(
|
||||
error=(_("No managers found for %(node)s"),
|
||||
{'node': task.node.uuid}))
|
||||
|
||||
for manager in system.managers:
|
||||
|
||||
try:
|
||||
manager_oem = manager.get_oem_extension('Dell')
|
||||
except sushy.exceptions.OEMExtensionNotFoundError as e:
|
||||
error_msg = (_("Search for Sushy OEM extension Python package "
|
||||
"'sushy-oem-idrac' failed for node %(node)s. "
|
||||
"Ensure it is installed. Error: %(error)s") %
|
||||
{'node': task.node.uuid, 'error': e})
|
||||
LOG.error(error_msg)
|
||||
raise exception.RedfishError(error=error_msg)
|
||||
|
||||
try:
|
||||
configuration = manager_oem.export_system_configuration()
|
||||
LOG.info("Exported %(node)s configuration via OEM",
|
||||
{'node': task.node.uuid})
|
||||
except sushy.exceptions.SushyError as e:
|
||||
LOG.debug("Sushy OEM extension Python package "
|
||||
"'sushy-oem-idrac' failed to export system "
|
||||
" configuration for node %(node)s. Will try next "
|
||||
"manager, if available. Error: %(error)s",
|
||||
{'system': system.uuid if system.uuid else
|
||||
system.identity,
|
||||
'manager': manager.uuid if manager.uuid else
|
||||
manager.identity,
|
||||
'node': task.node.uuid,
|
||||
'error': e})
|
||||
continue
|
||||
break
|
||||
|
||||
if configuration and configuration.status_code == 200:
|
||||
configuration = {"oem": {"interface": "idrac-redfish",
|
||||
"data": configuration.json()}}
|
||||
molds.save_configuration(task,
|
||||
export_configuration_location,
|
||||
configuration)
|
||||
else:
|
||||
raise exception.DracOperationError(
|
||||
error=(_("No configuration exported for node %(node)s"),
|
||||
{'node': task.node.uuid}))
|
||||
|
||||
@base.deploy_step(priority=0, argsinfo=IMPORT_CONFIGURATION_ARGSINFO)
|
||||
@base.clean_step(priority=0, argsinfo=IMPORT_CONFIGURATION_ARGSINFO)
|
||||
def import_configuration(self, task, import_configuration_location):
|
||||
"""Import and apply the configuration to the server.
|
||||
|
||||
Gets pre-created configuration from storage by given location and
|
||||
imports that into given server. Uses Dell's Server Configuration
|
||||
Profile (SCP).
|
||||
|
||||
:param task: A task from TaskManager.
|
||||
:param import_configuration_location: URL of location to fetch desired
|
||||
configuration from.
|
||||
|
||||
:raises: MissingParameterValue if missing configuration name of a file
|
||||
to fetch the configuration from
|
||||
"""
|
||||
if not import_configuration_location:
|
||||
raise exception.MissingParameterValue(
|
||||
_('import_configuration_location missing'))
|
||||
|
||||
configuration = molds.get_configuration(task,
|
||||
import_configuration_location)
|
||||
if not configuration:
|
||||
raise exception.DracOperationError(
|
||||
error=(_("No configuration found for node %(node)s by name "
|
||||
"%(configuration_name)s"),
|
||||
{'node': task.node.uuid,
|
||||
'configuration_name': import_configuration_location}))
|
||||
|
||||
interface = configuration["oem"]["interface"]
|
||||
if interface != "idrac-redfish":
|
||||
raise exception.DracOperationError(
|
||||
error=(_("Invalid configuration for node %(node)s "
|
||||
"in %(configuration_name)s. Supports only "
|
||||
"idrac-redfish, but found %(interface)s"),
|
||||
{'node': task.node.uuid,
|
||||
'configuration_name': import_configuration_location,
|
||||
'interface': interface}))
|
||||
|
||||
system = redfish_utils.get_system(task.node)
|
||||
|
||||
if not system.managers:
|
||||
raise exception.DracOperationError(
|
||||
error=(_("No managers found for %(node)s"),
|
||||
{'node': task.node.uuid}))
|
||||
|
||||
for manager in system.managers:
|
||||
try:
|
||||
manager_oem = manager.get_oem_extension('Dell')
|
||||
except sushy.exceptions.OEMExtensionNotFoundError as e:
|
||||
error_msg = (_("Search for Sushy OEM extension Python package "
|
||||
"'sushy-oem-idrac' failed for node %(node)s. "
|
||||
"Ensure it is installed. Error: %(error)s") %
|
||||
{'node': task.node.uuid, 'error': e})
|
||||
LOG.error(error_msg)
|
||||
raise exception.RedfishError(error=error_msg)
|
||||
|
||||
try:
|
||||
task_monitor = manager_oem.import_system_configuration(
|
||||
json.dumps(configuration["oem"]["data"]))
|
||||
|
||||
info = task.node.driver_internal_info
|
||||
info['import_task_monitor_url'] = task_monitor.task_monitor_uri
|
||||
task.node.driver_internal_info = info
|
||||
|
||||
deploy_utils.set_async_step_flags(
|
||||
task.node,
|
||||
reboot=True,
|
||||
skip_current_step=True,
|
||||
polling=True)
|
||||
deploy_opts = deploy_utils.build_agent_options(task.node)
|
||||
task.driver.boot.prepare_ramdisk(task, deploy_opts)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
|
||||
return deploy_utils.get_async_step_return_state(task.node)
|
||||
except sushy.exceptions.SushyError as e:
|
||||
LOG.debug("Sushy OEM extension Python package "
|
||||
"'sushy-oem-idrac' failed to import system "
|
||||
" configuration for node %(node)s. Will try next "
|
||||
"manager, if available. Error: %(error)s",
|
||||
{'system': system.uuid if system.uuid else
|
||||
system.identity,
|
||||
'manager': manager.uuid if manager.uuid else
|
||||
manager.identity,
|
||||
'node': task.node.uuid,
|
||||
'error': e})
|
||||
continue
|
||||
|
||||
raise exception.DracOperationError(
|
||||
error=(_("Failed to import configuration for node %(node)s"),
|
||||
{'node': task.node.uuid}))
|
||||
|
||||
@base.clean_step(priority=0,
|
||||
argsinfo=IMPORT_EXPORT_CONFIGURATION_ARGSINFO)
|
||||
@base.deploy_step(priority=0,
|
||||
argsinfo=IMPORT_EXPORT_CONFIGURATION_ARGSINFO)
|
||||
def import_export_configuration(self, task, import_configuration_location,
|
||||
export_configuration_location):
|
||||
"""Import and export configuration in one go.
|
||||
|
||||
Gets pre-created configuration from storage by given name and
|
||||
imports that into given server. After that exports the configuration of
|
||||
the server against which the step is run and stores it in specific
|
||||
format in indicated storage as configured by Ironic.
|
||||
|
||||
:param import_configuration_location: URL of location to fetch desired
|
||||
configuration from.
|
||||
:param export_configuration_location: URL of location to save the
|
||||
configuration to.
|
||||
"""
|
||||
# Import is async operation, setting sub-step to store export config
|
||||
# and indicate that it's being executed as part of composite step
|
||||
info = task.node.driver_internal_info
|
||||
info['export_configuration_location'] = export_configuration_location
|
||||
task.node.driver_internal_info = info
|
||||
task.node.save()
|
||||
|
||||
return self.import_configuration(task, import_configuration_location)
|
||||
# Export executed as part of Import async periodic task status check
|
||||
|
||||
@METRICS.timer('DracRedfishManagement._query_import_configuration_status')
|
||||
@periodics.periodic(
|
||||
spacing=CONF.drac.query_import_config_job_status_interval,
|
||||
enabled=CONF.drac.query_import_config_job_status_interval > 0)
|
||||
def _query_import_configuration_status(self, manager, context):
|
||||
"""Period job to check import configuration task."""
|
||||
|
||||
filters = {'reserved': False, 'maintenance': False}
|
||||
fields = ['driver_internal_info']
|
||||
node_list = manager.iter_nodes(fields=fields, filters=filters)
|
||||
for (node_uuid, driver, conductor_group,
|
||||
driver_internal_info) in node_list:
|
||||
try:
|
||||
lock_purpose = 'checking async import configuration task'
|
||||
with task_manager.acquire(context, node_uuid,
|
||||
purpose=lock_purpose,
|
||||
shared=True) as task:
|
||||
if not isinstance(task.driver.management,
|
||||
DracRedfishManagement):
|
||||
continue
|
||||
task_monitor_url = driver_internal_info.get(
|
||||
'import_task_monitor_url')
|
||||
if not task_monitor_url:
|
||||
continue
|
||||
self._check_import_configuration_task(
|
||||
task, task_monitor_url)
|
||||
except exception.NodeNotFound:
|
||||
LOG.info('During _query_import_configuration_status, node '
|
||||
'%(node)s was not found and presumed deleted by '
|
||||
'another process.', {'node': node_uuid})
|
||||
except exception.NodeLocked:
|
||||
LOG.info('During _query_import_configuration_status, node '
|
||||
'%(node)s was already locked by another process. '
|
||||
'Skip.', {'node': node_uuid})
|
||||
|
||||
def _check_import_configuration_task(self, task, task_monitor_url):
|
||||
"""Checks progress of running import configuration task"""
|
||||
|
||||
node = task.node
|
||||
task_monitor = redfish_utils.get_task_monitor(node, task_monitor_url)
|
||||
|
||||
if not task_monitor.is_processing:
|
||||
import_task = task_monitor.get_task()
|
||||
|
||||
task.upgrade_lock()
|
||||
info = node.driver_internal_info
|
||||
info.pop('import_task_monitor_url', None)
|
||||
node.driver_internal_info = info
|
||||
|
||||
if (import_task.task_state == sushy.TASK_STATE_COMPLETED
|
||||
and import_task.task_status in
|
||||
[sushy.HEALTH_OK, sushy.HEALTH_WARNING]):
|
||||
LOG.info('Configuration import %(task_monitor_url)s '
|
||||
'successful for node %(node)s',
|
||||
{'node': node.uuid,
|
||||
'task_monitor_url': task_monitor_url})
|
||||
|
||||
# If import executed as part of import_export_configuration
|
||||
export_configuration_location =\
|
||||
info.get('export_configuration_location')
|
||||
if export_configuration_location:
|
||||
# then do sync export configuration before finishing
|
||||
self._cleanup_export_substep(node)
|
||||
try:
|
||||
self.export_configuration(
|
||||
task, export_configuration_location)
|
||||
except (sushy.exceptions.SushyError,
|
||||
exception.IronicException) as e:
|
||||
error_msg = (_("Failed export configuration. %(exc)s" %
|
||||
{'exc': e}))
|
||||
log_msg = ("Export configuration failed for node "
|
||||
"%(node)s. %(error)s" %
|
||||
{'node': task.node.uuid,
|
||||
'error': error_msg})
|
||||
self._set_failed(task, log_msg, error_msg)
|
||||
return
|
||||
self._set_success(task)
|
||||
else:
|
||||
# Select all messages, skipping OEM messages that don't have
|
||||
# `message` field populated.
|
||||
messages = [m.message for m in import_task.messages
|
||||
if m.message is not None]
|
||||
error_msg = (_("Failed import configuration task: "
|
||||
"%(task_monitor_url)s. Message: '%(message)s'.")
|
||||
% {'task_monitor_url': task_monitor_url,
|
||||
'message': ', '.join(messages)})
|
||||
log_msg = ("Import configuration task failed for node "
|
||||
"%(node)s. %(error)s" % {'node': task.node.uuid,
|
||||
'error': error_msg})
|
||||
self._set_failed(task, log_msg, error_msg)
|
||||
node.save()
|
||||
else:
|
||||
LOG.debug('Import configuration %(task_monitor_url)s in progress '
|
||||
'for node %(node)s',
|
||||
{'node': node.uuid,
|
||||
'task_monitor_url': task_monitor_url})
|
||||
|
||||
def _set_success(self, task):
|
||||
if task.node.clean_step:
|
||||
manager_utils.notify_conductor_resume_clean(task)
|
||||
else:
|
||||
manager_utils.notify_conductor_resume_deploy(task)
|
||||
|
||||
def _set_failed(self, task, log_msg, error_msg):
|
||||
if task.node.clean_step:
|
||||
manager_utils.cleaning_error_handler(task, log_msg, error_msg)
|
||||
else:
|
||||
manager_utils.deploying_error_handler(task, log_msg, error_msg)
|
||||
|
||||
def _cleanup_export_substep(self, node):
|
||||
driver_internal_info = node.driver_internal_info
|
||||
driver_internal_info.pop('export_configuration_location', None)
|
||||
node.driver_internal_info = driver_internal_info
|
||||
|
||||
|
||||
class DracWSManManagement(base.ManagementInterface):
|
||||
|
@ -20,20 +20,26 @@
|
||||
Test class for DRAC management interface
|
||||
"""
|
||||
|
||||
import json
|
||||
from unittest import mock
|
||||
|
||||
from oslo_utils import importutils
|
||||
|
||||
import ironic.common.boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common import molds
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules.drac import common as drac_common
|
||||
from ironic.drivers.modules.drac import job as drac_job
|
||||
from ironic.drivers.modules.drac import management as drac_mgmt
|
||||
from ironic.drivers.modules.redfish import utils as redfish_utils
|
||||
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
dracclient_exceptions = importutils.try_import('dracclient.exceptions')
|
||||
sushy = importutils.try_import('sushy')
|
||||
|
||||
INFO_DICT = test_utils.INFO_DICT
|
||||
|
||||
@ -822,3 +828,598 @@ class DracManagementTestCase(test_utils.BaseDracTest):
|
||||
job_ids=['JID_CLEARALL_FORCE'])
|
||||
|
||||
self.assertIsNone(return_value)
|
||||
|
||||
|
||||
class DracRedfishManagementTestCase(test_utils.BaseDracTest):
|
||||
|
||||
def setUp(self):
|
||||
super(DracRedfishManagementTestCase, self).setUp()
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='idrac',
|
||||
driver_info=INFO_DICT)
|
||||
self.management = drac_mgmt.DracRedfishManagement()
|
||||
|
||||
def test_export_configuration_name_missing(self):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
self.management.export_configuration, task, None)
|
||||
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_export_configuration_no_managers(self, mock_get_system):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_system = mock.Mock(managers=[])
|
||||
mock_get_system.return_value = fake_system
|
||||
|
||||
self.assertRaises(exception.DracOperationError,
|
||||
self.management.export_configuration, task, 'edge')
|
||||
|
||||
@mock.patch.object(drac_mgmt, 'LOG', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_export_configuration_oem_not_found(self, mock_get_system,
|
||||
mock_log):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_manager1 = mock.Mock()
|
||||
fake_manager1.get_oem_extension.side_effect = (
|
||||
sushy.exceptions.OEMExtensionNotFoundError)
|
||||
fake_system = mock.Mock(managers=[fake_manager1])
|
||||
mock_get_system.return_value = fake_system
|
||||
|
||||
self.assertRaises(exception.RedfishError,
|
||||
self.management.export_configuration, task, 'edge')
|
||||
self.assertEqual(mock_log.error.call_count, 1)
|
||||
|
||||
@mock.patch.object(drac_mgmt, 'LOG', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_export_configuration_all_managers_fail(self, mock_get_system,
|
||||
mock_log):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_manager_oem1 = mock.Mock()
|
||||
fake_manager_oem1.export_system_configuration.side_effect = (
|
||||
sushy.exceptions.SushyError)
|
||||
fake_manager1 = mock.Mock()
|
||||
fake_manager1.get_oem_extension.return_value = fake_manager_oem1
|
||||
fake_manager_oem2 = mock.Mock()
|
||||
fake_manager_oem2.export_system_configuration.side_effect = (
|
||||
sushy.exceptions.SushyError)
|
||||
fake_manager2 = mock.Mock()
|
||||
fake_manager2.get_oem_extension.return_value = fake_manager_oem2
|
||||
fake_system = mock.Mock(managers=[fake_manager1, fake_manager2])
|
||||
mock_get_system.return_value = fake_system
|
||||
|
||||
self.assertRaises(exception.DracOperationError,
|
||||
self.management.export_configuration,
|
||||
task, 'edge')
|
||||
self.assertEqual(mock_log.debug.call_count, 2)
|
||||
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_export_configuration_export_failed(self, mock_get_system):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_manager_oem1 = mock.Mock()
|
||||
fake_manager_oem1.export_system_configuration = mock.Mock()
|
||||
fake_manager_oem1.export_system_configuration.status_code = 500
|
||||
fake_manager1 = mock.Mock()
|
||||
fake_manager1.get_oem_extension.return_value = fake_manager_oem1
|
||||
fake_system = mock.Mock(managers=[fake_manager1])
|
||||
mock_get_system.return_value = fake_system
|
||||
|
||||
self.assertRaises(exception.DracOperationError,
|
||||
self.management.export_configuration, task, 'edge')
|
||||
|
||||
@mock.patch.object(drac_mgmt, 'LOG', autospec=True)
|
||||
@mock.patch.object(molds, 'save_configuration', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_export_configuration_success(self, mock_get_system,
|
||||
mock_save_configuration,
|
||||
mock_log):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_manager_oem1 = mock.Mock()
|
||||
fake_manager_oem1.export_system_configuration.side_effect = (
|
||||
sushy.exceptions.SushyError)
|
||||
fake_manager1 = mock.Mock()
|
||||
fake_manager1.get_oem_extension.return_value = fake_manager_oem1
|
||||
|
||||
configuration = mock.Mock(status_code=200)
|
||||
configuration.json.return_value = (
|
||||
json.loads('{"prop1":"value1", "prop2":2}'))
|
||||
fake_manager_oem2 = mock.Mock()
|
||||
fake_manager_oem2.export_system_configuration.return_value = (
|
||||
configuration)
|
||||
fake_manager2 = mock.Mock()
|
||||
fake_manager2.get_oem_extension.return_value = fake_manager_oem2
|
||||
fake_system = mock.Mock(managers=[fake_manager1, fake_manager2])
|
||||
mock_get_system.return_value = fake_system
|
||||
self.management.export_configuration(task, 'edge')
|
||||
|
||||
mock_save_configuration.assert_called_once_with(
|
||||
task,
|
||||
'edge',
|
||||
{"oem": {"interface": "idrac-redfish",
|
||||
"data": {"prop1": "value1", "prop2": 2}}})
|
||||
self.assertEqual(mock_log.debug.call_count, 1)
|
||||
self.assertEqual(mock_log.info.call_count, 1)
|
||||
|
||||
def test_import_configuration_name_missing(self):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
self.management.import_configuration, task, None)
|
||||
|
||||
@mock.patch.object(molds, 'get_configuration', autospec=True)
|
||||
def test_import_configuration_file_not_found(self, mock_get_configuration):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
mock_get_configuration.return_value = None
|
||||
|
||||
self.assertRaises(exception.DracOperationError,
|
||||
self.management.import_configuration, task, 'edge')
|
||||
|
||||
@mock.patch.object(molds, 'get_configuration', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_import_configuration_no_managers(self, mock_get_system,
|
||||
mock_get_configuration):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_system = mock.Mock(managers=[])
|
||||
mock_get_configuration.return_value = json.loads(
|
||||
'{"oem": {"interface": "idrac-redfish", '
|
||||
'"data": {"prop1": "value1", "prop2": 2}}}')
|
||||
mock_get_system.return_value = fake_system
|
||||
|
||||
self.assertRaises(exception.DracOperationError,
|
||||
self.management.import_configuration, task, 'edge')
|
||||
|
||||
@mock.patch.object(drac_mgmt, 'LOG', autospec=True)
|
||||
@mock.patch.object(molds, 'get_configuration', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_import_configuration_oem_not_found(self, mock_get_system,
|
||||
mock_get_configuration,
|
||||
mock_log):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_manager1 = mock.Mock()
|
||||
fake_manager1.get_oem_extension.side_effect = (
|
||||
sushy.exceptions.OEMExtensionNotFoundError)
|
||||
fake_system = mock.Mock(managers=[fake_manager1])
|
||||
mock_get_system.return_value = fake_system
|
||||
mock_get_configuration.return_value = json.loads(
|
||||
'{"oem": {"interface": "idrac-redfish", '
|
||||
'"data": {"prop1": "value1", "prop2": 2}}}')
|
||||
|
||||
self.assertRaises(exception.RedfishError,
|
||||
self.management.import_configuration, task, 'edge')
|
||||
self.assertEqual(mock_log.error.call_count, 1)
|
||||
|
||||
@mock.patch.object(drac_mgmt, 'LOG', autospec=True)
|
||||
@mock.patch.object(molds, 'get_configuration', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_import_configuration_all_managers_fail(self, mock_get_system,
|
||||
mock_get_configuration,
|
||||
mock_log):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_manager_oem1 = mock.Mock()
|
||||
fake_manager_oem1.import_system_configuration.side_effect = (
|
||||
sushy.exceptions.SushyError)
|
||||
fake_manager1 = mock.Mock()
|
||||
fake_manager1.get_oem_extension.return_value = fake_manager_oem1
|
||||
fake_manager_oem2 = mock.Mock()
|
||||
fake_manager_oem2.import_system_configuration.side_effect = (
|
||||
sushy.exceptions.SushyError)
|
||||
fake_manager2 = mock.Mock()
|
||||
fake_manager2.get_oem_extension.return_value = fake_manager_oem2
|
||||
fake_system = mock.Mock(managers=[fake_manager1, fake_manager2])
|
||||
mock_get_system.return_value = fake_system
|
||||
mock_get_configuration.return_value = json.loads(
|
||||
'{"oem": {"interface": "idrac-redfish", '
|
||||
'"data": {"prop1": "value1", "prop2": 2}}}')
|
||||
|
||||
self.assertRaises(exception.DracOperationError,
|
||||
self.management.import_configuration, task, 'edge')
|
||||
self.assertEqual(mock_log.debug.call_count, 2)
|
||||
|
||||
@mock.patch.object(molds, 'get_configuration', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_import_configuration_incorrect_interface(self, mock_get_system,
|
||||
mock_get_configuration):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_manager_oem1 = mock.Mock()
|
||||
fake_manager1 = mock.Mock()
|
||||
fake_manager1.get_oem_extension.return_value = fake_manager_oem1
|
||||
fake_system = mock.Mock(managers=[fake_manager1])
|
||||
mock_get_system.return_value = fake_system
|
||||
mock_get_configuration.return_value = json.loads(
|
||||
'{"oem": {"interface": "idrac-wsman", '
|
||||
'"data": {"prop1": "value1", "prop2": 2}}}')
|
||||
|
||||
self.assertRaises(exception.DracOperationError,
|
||||
self.management.import_configuration, task, 'edge')
|
||||
|
||||
@mock.patch.object(deploy_utils, 'get_async_step_return_state',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
|
||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||
@mock.patch.object(drac_mgmt, 'LOG', autospec=True)
|
||||
@mock.patch.object(molds, 'get_configuration', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_import_configuration_success(
|
||||
self, mock_get_system, mock_get_configuration, mock_log,
|
||||
mock_power, mock_build_agent_options,
|
||||
mock_set_async_step_flags, mock_get_async_step_return_state):
|
||||
deploy_opts = mock.Mock()
|
||||
mock_build_agent_options.return_value = deploy_opts
|
||||
step_result = mock.Mock()
|
||||
mock_get_async_step_return_state.return_value = step_result
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_manager_oem1 = mock.Mock()
|
||||
fake_manager_oem1.import_system_configuration.side_effect = (
|
||||
sushy.exceptions.SushyError)
|
||||
fake_manager1 = mock.Mock()
|
||||
fake_manager1.get_oem_extension.return_value = fake_manager_oem1
|
||||
fake_manager_oem2 = mock.Mock()
|
||||
fake_manager2 = mock.Mock()
|
||||
fake_manager2.get_oem_extension.return_value = fake_manager_oem2
|
||||
fake_system = mock.Mock(managers=[fake_manager1, fake_manager2])
|
||||
mock_get_system.return_value = fake_system
|
||||
mock_get_configuration.return_value = json.loads(
|
||||
'{"oem": {"interface": "idrac-redfish", '
|
||||
'"data": {"prop1": "value1", "prop2": 2}}}')
|
||||
|
||||
result = self.management.import_configuration(task, 'edge')
|
||||
|
||||
fake_manager_oem2.import_system_configuration.assert_called_once_with(
|
||||
'{"prop1": "value1", "prop2": 2}')
|
||||
self.assertEqual(mock_log.debug.call_count, 1)
|
||||
|
||||
mock_set_async_step_flags.assert_called_once_with(
|
||||
task.node, reboot=True, skip_current_step=True, polling=True)
|
||||
mock_build_agent_options.assert_called_once_with(task.node)
|
||||
task.driver.boot.prepare_ramdisk.assert_called_once_with(
|
||||
task, deploy_opts)
|
||||
mock_get_async_step_return_state.assert_called_once_with(task.node)
|
||||
self.assertEqual(step_result, result)
|
||||
|
||||
@mock.patch.object(drac_mgmt.DracRedfishManagement,
|
||||
'import_configuration', autospec=True)
|
||||
def test_import_export_configuration_success(self, mock_import):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
|
||||
self.management.import_export_configuration(
|
||||
task, 'https://server/edge_import', 'https://server/edge_export')
|
||||
|
||||
mock_import.assert_called_once_with(self.management, task,
|
||||
'https://server/edge_import')
|
||||
self.assertEqual(
|
||||
'https://server/edge_export',
|
||||
self.node.driver_internal_info.get(
|
||||
'export_configuration_location'))
|
||||
|
||||
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
||||
def test__query_import_configuration_not_drac(self, mock_acquire):
|
||||
driver_internal_info = {'import_task_monitor_url': '/TaskService/123'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
mock_manager = mock.Mock()
|
||||
node_list = [(self.node.uuid, 'not-idrac', '', driver_internal_info)]
|
||||
mock_manager.iter_nodes.return_value = node_list
|
||||
task = mock.Mock(node=self.node,
|
||||
driver=mock.Mock(management=mock.Mock()))
|
||||
mock_acquire.return_value = mock.MagicMock(
|
||||
__enter__=mock.MagicMock(return_value=task))
|
||||
self.management._check_import_configuration_task = mock.Mock()
|
||||
|
||||
self.management._query_import_configuration_status(mock_manager,
|
||||
self.context)
|
||||
|
||||
self.management._check_import_configuration_task.assert_not_called()
|
||||
|
||||
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
||||
def test__query_import_configuration_status_no_task_monitor_url(
|
||||
self, mock_acquire):
|
||||
driver_internal_info = {'something': 'else'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
mock_manager = mock.Mock()
|
||||
node_list = [(self.node.uuid, 'idrac', '', driver_internal_info)]
|
||||
mock_manager.iter_nodes.return_value = node_list
|
||||
task = mock.Mock(node=self.node,
|
||||
driver=mock.Mock(management=self.management))
|
||||
mock_acquire.return_value = mock.MagicMock(
|
||||
__enter__=mock.MagicMock(return_value=task))
|
||||
self.management._check_import_configuration_task = mock.Mock()
|
||||
|
||||
self.management._query_import_configuration_status(mock_manager,
|
||||
self.context)
|
||||
|
||||
self.management._check_import_configuration_task.assert_not_called()
|
||||
|
||||
@mock.patch.object(drac_mgmt.LOG, 'info', autospec=True)
|
||||
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
||||
def test__query_import_configuration_status_node_notfound(
|
||||
self, mock_acquire, mock_log):
|
||||
driver_internal_info = {'import_task_monitor_url': '/TaskService/123'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
mock_manager = mock.Mock()
|
||||
node_list = [(self.node.uuid, 'idrac', '', driver_internal_info)]
|
||||
mock_manager.iter_nodes.return_value = node_list
|
||||
mock_acquire.side_effect = exception.NodeNotFound
|
||||
task = mock.Mock(node=self.node,
|
||||
driver=mock.Mock(management=self.management))
|
||||
mock_acquire.return_value = mock.MagicMock(
|
||||
__enter__=mock.MagicMock(return_value=task))
|
||||
self.management._check_import_configuration_task = mock.Mock()
|
||||
|
||||
self.management._query_import_configuration_status(mock_manager,
|
||||
self.context)
|
||||
|
||||
self.management._check_import_configuration_task.assert_not_called()
|
||||
self.assertTrue(mock_log.called)
|
||||
|
||||
@mock.patch.object(drac_mgmt.LOG, 'info', autospec=True)
|
||||
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
||||
def test__query_import_configuration_status_node_locked(
|
||||
self, mock_acquire, mock_log):
|
||||
driver_internal_info = {'import_task_monitor_url': '/TaskService/123'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
mock_manager = mock.Mock()
|
||||
node_list = [(self.node.uuid, 'idrac', '', driver_internal_info)]
|
||||
mock_manager.iter_nodes.return_value = node_list
|
||||
mock_acquire.side_effect = exception.NodeLocked
|
||||
task = mock.Mock(node=self.node,
|
||||
driver=mock.Mock(management=self.management))
|
||||
mock_acquire.return_value = mock.MagicMock(
|
||||
__enter__=mock.MagicMock(return_value=task))
|
||||
self.management._check_import_configuration_task = mock.Mock()
|
||||
|
||||
self.management._query_import_configuration_status(mock_manager,
|
||||
self.context)
|
||||
|
||||
self.management._check_import_configuration_task.assert_not_called()
|
||||
self.assertTrue(mock_log.called)
|
||||
|
||||
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
||||
def test__query_import_configuration_status(self, mock_acquire):
|
||||
driver_internal_info = {'import_task_monitor_url': '/TaskService/123'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
mock_manager = mock.Mock()
|
||||
node_list = [(self.node.uuid, 'idrac', '', driver_internal_info)]
|
||||
mock_manager.iter_nodes.return_value = node_list
|
||||
task = mock.Mock(node=self.node,
|
||||
driver=mock.Mock(management=self.management))
|
||||
mock_acquire.return_value = mock.MagicMock(
|
||||
__enter__=mock.MagicMock(return_value=task))
|
||||
self.management._check_import_configuration_task = mock.Mock()
|
||||
|
||||
self.management._query_import_configuration_status(mock_manager,
|
||||
self.context)
|
||||
|
||||
(self.management
|
||||
._check_import_configuration_task
|
||||
.assert_called_once_with(task, '/TaskService/123'))
|
||||
|
||||
@mock.patch.object(drac_mgmt.LOG, 'debug', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
|
||||
def test__check_import_configuration_task_still_processing(
|
||||
self, mock_get_task_monitor, mock_log):
|
||||
driver_internal_info = {'import_task_monitor_url': '/TaskService/123'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
|
||||
mock_task_monitor = mock.Mock()
|
||||
mock_task_monitor.is_processing = True
|
||||
mock_get_task_monitor.return_value = mock_task_monitor
|
||||
|
||||
self.management._set_success = mock.Mock()
|
||||
self.management._set_failed = mock.Mock()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.management._check_import_configuration_task(
|
||||
task, '/TaskService/123')
|
||||
|
||||
self.management._set_success.assert_not_called()
|
||||
self.management._set_failed.assert_not_called()
|
||||
self.assertTrue(mock_log.called)
|
||||
self.assertEqual(
|
||||
'/TaskService/123',
|
||||
task.node.driver_internal_info.get('import_task_monitor_url'))
|
||||
|
||||
@mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
|
||||
def test__check_import_configuration_task_failed(
|
||||
self, mock_get_task_monitor):
|
||||
driver_internal_info = {'import_task_monitor_url': '/TaskService/123'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
|
||||
mock_message = mock.Mock()
|
||||
mock_message.message = 'Firmware upgrade failed'
|
||||
mock_import_task = mock.Mock()
|
||||
mock_import_task.task_state = sushy.TASK_STATE_COMPLETED
|
||||
mock_import_task.task_status = 'Failed'
|
||||
mock_import_task.messages = [mock_message]
|
||||
mock_task_monitor = mock.Mock()
|
||||
mock_task_monitor.is_processing = False
|
||||
mock_task_monitor.get_task.return_value = mock_import_task
|
||||
mock_get_task_monitor.return_value = mock_task_monitor
|
||||
|
||||
self.management._set_success = mock.Mock()
|
||||
self.management._set_failed = mock.Mock()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.management._check_import_configuration_task(
|
||||
task, '/TaskService/123')
|
||||
|
||||
self.management._set_failed.assert_called_once_with(
|
||||
task, mock.ANY,
|
||||
"Failed import configuration task: /TaskService/123. Message: "
|
||||
"'Firmware upgrade failed'.")
|
||||
self.management._set_success.assert_not_called()
|
||||
self.assertIsNone(
|
||||
task.node.driver_internal_info.get('import_task_monitor_url'))
|
||||
|
||||
@mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
|
||||
def test__check_import_configuration_task(self, mock_get_task_monitor):
|
||||
driver_internal_info = {'import_task_monitor_url': '/TaskService/123'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
|
||||
mock_message = mock.Mock()
|
||||
mock_message.message = 'Configuration import done'
|
||||
mock_import_task = mock.Mock()
|
||||
mock_import_task.task_state = sushy.TASK_STATE_COMPLETED
|
||||
mock_import_task.task_status = sushy.HEALTH_OK
|
||||
mock_import_task.messages = [mock_message]
|
||||
mock_task_monitor = mock.Mock()
|
||||
mock_task_monitor.is_processing = False
|
||||
mock_task_monitor.get_task.return_value = mock_import_task
|
||||
mock_get_task_monitor.return_value = mock_task_monitor
|
||||
|
||||
self.management._set_success = mock.Mock()
|
||||
self.management._set_failed = mock.Mock()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.management._check_import_configuration_task(
|
||||
task, '/TaskService/123')
|
||||
|
||||
self.management._set_success.assert_called_once_with(task)
|
||||
self.management._set_failed.assert_not_called()
|
||||
self.assertIsNone(
|
||||
task.node.driver_internal_info.get('import_task_monitor_url'))
|
||||
|
||||
@mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
|
||||
def test__check_import_configuration_task_with_export_failed(
|
||||
self, mock_get_task_monitor):
|
||||
driver_internal_info = {
|
||||
'import_task_monitor_url': '/TaskService/123',
|
||||
'export_configuration_location': 'https://server/export1'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
|
||||
mock_message = mock.Mock()
|
||||
mock_message.message = 'Configuration import done'
|
||||
mock_import_task = mock.Mock()
|
||||
mock_import_task.task_state = sushy.TASK_STATE_COMPLETED
|
||||
mock_import_task.task_status = sushy.HEALTH_OK
|
||||
mock_import_task.messages = [mock_message]
|
||||
mock_task_monitor = mock.Mock()
|
||||
mock_task_monitor.is_processing = False
|
||||
mock_task_monitor.get_task.return_value = mock_import_task
|
||||
mock_get_task_monitor.return_value = mock_task_monitor
|
||||
|
||||
self.management._set_success = mock.Mock()
|
||||
self.management._set_failed = mock.Mock()
|
||||
mock_export = mock.Mock()
|
||||
mock_export.side_effect = exception.IronicException
|
||||
self.management.export_configuration = mock_export
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.management._check_import_configuration_task(
|
||||
task, '/TaskService/123')
|
||||
|
||||
self.management.export_configuration.assert_called_once_with(
|
||||
task, 'https://server/export1')
|
||||
self.management._set_success.assert_not_called()
|
||||
self.assertIsNone(
|
||||
task.node.driver_internal_info.get('import_task_monitor_url'))
|
||||
self.assertIsNone(
|
||||
task.node.driver_internal_info.get(
|
||||
'export_configuration_location'))
|
||||
self.management._set_failed.assert_called_with(
|
||||
task, mock.ANY,
|
||||
'Failed export configuration. An unknown exception occurred.')
|
||||
|
||||
@mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
|
||||
def test__check_import_configuration_task_with_export(
|
||||
self, mock_get_task_monitor):
|
||||
driver_internal_info = {
|
||||
'import_task_monitor_url': '/TaskService/123',
|
||||
'export_configuration_location': 'https://server/export1'}
|
||||
self.node.driver_internal_info = driver_internal_info
|
||||
self.node.save()
|
||||
|
||||
mock_message = mock.Mock()
|
||||
mock_message.message = 'Configuration import done'
|
||||
mock_import_task = mock.Mock()
|
||||
mock_import_task.task_state = sushy.TASK_STATE_COMPLETED
|
||||
mock_import_task.task_status = sushy.HEALTH_OK
|
||||
mock_import_task.messages = [mock_message]
|
||||
mock_task_monitor = mock.Mock()
|
||||
mock_task_monitor.is_processing = False
|
||||
mock_task_monitor.get_task.return_value = mock_import_task
|
||||
mock_get_task_monitor.return_value = mock_task_monitor
|
||||
|
||||
self.management._set_success = mock.Mock()
|
||||
self.management._set_failed = mock.Mock()
|
||||
self.management.export_configuration = mock.Mock()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.management._check_import_configuration_task(
|
||||
task, '/TaskService/123')
|
||||
|
||||
self.management.export_configuration.assert_called_once_with(
|
||||
task, 'https://server/export1')
|
||||
self.management._set_success.assert_called_once_with(task)
|
||||
self.assertIsNone(
|
||||
task.node.driver_internal_info.get('import_task_monitor_url'))
|
||||
self.assertIsNone(
|
||||
task.node.driver_internal_info.get(
|
||||
'export_configuration_location'))
|
||||
self.management._set_failed.assert_not_called()
|
||||
|
||||
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
|
||||
autospec=True)
|
||||
@mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
|
||||
autospec=True)
|
||||
def test__set_success_clean(self, mock_notify_clean, mock_notify_deploy):
|
||||
self.node.clean_step = {'test': 'value'}
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.management._set_success(task)
|
||||
|
||||
mock_notify_clean.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
|
||||
autospec=True)
|
||||
@mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
|
||||
autospec=True)
|
||||
def test__set_success_deploy(self, mock_notify_clean, mock_notify_deploy):
|
||||
self.node.clean_step = None
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.management._set_success(task)
|
||||
|
||||
mock_notify_deploy.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(manager_utils, 'deploying_error_handler',
|
||||
autospec=True)
|
||||
@mock.patch.object(manager_utils, 'cleaning_error_handler',
|
||||
autospec=True)
|
||||
def test__set_failed_clean(self, mock_clean_handler, mock_deploy_handler):
|
||||
self.node.clean_step = {'test': 'value'}
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.management._set_failed(task, 'error', 'log message')
|
||||
|
||||
mock_clean_handler.assert_called_once_with(
|
||||
task, 'error', 'log message')
|
||||
|
||||
@mock.patch.object(manager_utils, 'deploying_error_handler',
|
||||
autospec=True)
|
||||
@mock.patch.object(manager_utils, 'cleaning_error_handler',
|
||||
autospec=True)
|
||||
def test__set_failed_deploy(self, mock_clean_handler, mock_deploy_handler):
|
||||
self.node.clean_step = None
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.management._set_failed(task, 'error', 'log message')
|
||||
|
||||
mock_deploy_handler.assert_called_once_with(
|
||||
task, 'error', 'log message')
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds ``import_configuration``, ``export_configuration`` and
|
||||
``import_export_configuration`` steps to ``idrac-redfish`` management
|
||||
interface. These steps allow to use configuration from another system as
|
||||
template and replicate that configuration to other, similarly capable,
|
||||
systems. Currently, this feature is experimental.
|
Loading…
Reference in New Issue
Block a user