diff --git a/ironic/drivers/modules/drac/raid.py b/ironic/drivers/modules/drac/raid.py index f879560485..42d86d3845 100644 --- a/ironic/drivers/modules/drac/raid.py +++ b/ironic/drivers/modules/drac/raid.py @@ -35,6 +35,7 @@ from ironic.drivers.modules.drac import common as drac_common from ironic.drivers.modules.drac import job as drac_job drac_exceptions = importutils.try_import('dracclient.exceptions') +drac_constants = importutils.try_import('dracclient.constants') LOG = logging.getLogger(__name__) @@ -134,6 +135,34 @@ def list_physical_disks(node): raise exception.DracOperationError(error=exc) +def _is_raid_controller(node, raid_controller_fqdd, raid_controllers=None): + """Find out if object's fqdd is for a raid controller or not + + :param node: an ironic node object + :param raid_controller_fqdd: The object's fqdd we are testing to see + if it is a raid controller or not. + :param raid_controllers: A list of RAIDControllers used to check for + the presence of BOSS cards. If None, the + iDRAC will be queried for the list of + controllers. + :returns: boolean, True if the device is a RAID controller, + False if not. + """ + client = drac_common.get_drac_client(node) + + try: + return client.is_raid_controller(raid_controller_fqdd, + raid_controllers) + except drac_exceptions.BaseClientException as exc: + LOG.error('Unable to determine if controller %(raid_controller_fqdd)s ' + 'on node %(node_uuid)s is a RAID controller. ' + 'Reason: %(error)s. ', + {'raid_controller_fqdd': raid_controller_fqdd, + 'node_uuid': node.uuid, 'error': exc}) + + raise exception.DracOperationError(error=exc) + + def create_virtual_disk(node, raid_controller, physical_disks, raid_level, size_mb, disk_name=None, span_length=None, span_depth=None): @@ -202,6 +231,69 @@ def delete_virtual_disk(node, virtual_disk): raise exception.DracOperationError(error=exc) +def _reset_raid_config(node, raid_controller): + """Delete all virtual disk and unassign all hotspares physical disk + + :param node: an ironic node object. + :param raid_controller: id of the RAID controller. + :returns: a dictionary containing + - The is_commit_required needed key with a + boolean value indicating whether a config job must be created + for the values to be applied. + - The is_reboot_required key with a RebootRequired enumerated + value indicating whether the server must be rebooted to + reset configuration. + :raises: DracOperationError on an error from python-dracclient. + """ + try: + + drac_job.validate_job_queue(node) + + client = drac_common.get_drac_client(node) + return client.reset_raid_config(raid_controller) + except drac_exceptions.BaseClientException as exc: + LOG.error('DRAC driver failed to delete all virtual disk ' + 'and unassign all hotspares ' + 'on %(raid_controller_fqdd)s ' + 'for node %(node_uuid)s. ' + 'Reason: %(error)s.', + {'raid_controller_fqdd': raid_controller, + 'node_uuid': node.uuid, + 'error': exc}) + raise exception.DracOperationError(error=exc) + + +def clear_foreign_config(node, raid_controller): + """Free up the foreign drives. + + :param node: an ironic node object. + :param raid_controller: id of the RAID controller. + :returns: a dictionary containing + - The is_commit_required needed key with a + boolean value indicating whether a config job must be created + for the values to be applied. + - The is_reboot_required key with a RebootRequired enumerated + value indicating whether the server must be rebooted to + clear foreign configuration. + :raises: DracOperationError on an error from python-dracclient. + """ + try: + + drac_job.validate_job_queue(node) + + client = drac_common.get_drac_client(node) + return client.clear_foreign_config(raid_controller) + except drac_exceptions.BaseClientException as exc: + LOG.error('DRAC driver failed to free foreign driver ' + 'on %(raid_controller_fqdd)s ' + 'for node %(node_uuid)s. ' + 'Reason: %(error)s.', + {'raid_controller_fqdd': raid_controller, + 'node_uuid': node.uuid, + 'error': exc}) + raise exception.DracOperationError(error=exc) + + def commit_config(node, raid_controller, reboot=False, realtime=False): """Apply all pending changes on a RAID controller. @@ -635,19 +727,41 @@ def _filter_logical_disks(logical_disks, include_root_volume, return filtered_disks -def _commit_to_controllers(node, controllers): - """Commit changes to RAID controllers on the node.""" +def _commit_to_controllers(node, controllers, substep="completed"): + """Commit changes to RAID controllers on the node. + :param node: an ironic node object + :param controllers: a list of dictionary containing + - The raid_controller key with raid controller + fqdd value indicating on which raid configuration + job needs to be perform. + - The is_commit_required needed key with a + boolean value indicating whether a config job must + be created. + - The is_reboot_required key with a RebootRequired + enumerated value indicating whether the server must + be rebooted only if raid controller does not support + realtime. + :param substep: contain sub cleaning step which executes any raid + configuration job if set after cleaning step. + (default to completed) + :returns: states.CLEANWAIT if deletion is in progress asynchronously + or None if it is completed. + """ if not controllers: LOG.debug('No changes on any of the controllers on node %s', node.uuid) return driver_internal_info = node.driver_internal_info + driver_internal_info['raid_config_substep'] = substep + driver_internal_info['raid_config_parameters'] = [] + if 'raid_config_job_ids' not in driver_internal_info: driver_internal_info['raid_config_job_ids'] = [] all_realtime = True + optional = drac_constants.RebootRequired.optional for controller in controllers: raid_controller = controller['raid_controller'] @@ -656,7 +770,8 @@ def _commit_to_controllers(node, controllers): # controller without real time support. In that case the reboot # is triggered when the configuration is committed to the last # controller. - realtime = controller['is_reboot_required'] == 'optional' + + realtime = controller['is_reboot_required'] == optional all_realtime = all_realtime and realtime if controller == controllers[-1]: job_id = commit_config(node, raid_controller=raid_controller, @@ -675,6 +790,11 @@ def _commit_to_controllers(node, controllers): driver_internal_info['raid_config_job_ids'].append(job_id) + if raid_controller not in driver_internal_info[ + 'raid_config_parameters']: + driver_internal_info['raid_config_parameters'].append( + raid_controller) + node.driver_internal_info = driver_internal_info node.save() @@ -728,7 +848,7 @@ class DracRAID(base.RAIDInterface): logical_disks = node.target_raid_config['logical_disks'] for disk in logical_disks: - if (disk['size_gb'] == 'MAX' and 'physical_disks' not in disk): + if disk['size_gb'] == 'MAX' and 'physical_disks' not in disk: raise exception.InvalidParameterValue( _("create_configuration called with invalid " "target_raid_configuration for node %(node_id)s. " @@ -782,15 +902,18 @@ class DracRAID(base.RAIDInterface): node = task.node controllers = list() - for disk in list_virtual_disks(node): - controller = dict() - controller_cap = delete_virtual_disk(node, disk.id) - controller['raid_controller'] = disk.controller - controller['is_reboot_required'] = controller_cap[ - 'is_reboot_required'] - controllers.append(controller) + drac_raid_controllers = list_raid_controllers(node) + for cntrl in drac_raid_controllers: + if _is_raid_controller(node, cntrl.id, drac_raid_controllers): + controller = dict() + controller_cap = _reset_raid_config(node, cntrl.id) + controller["raid_controller"] = cntrl.id + controller["is_reboot_required"] = controller_cap[ + "is_reboot_required"] + controllers.append(controller) - return _commit_to_controllers(node, controllers) + return _commit_to_controllers(node, controllers, + substep="delete_foreign_config") @METRICS.timer('DracRAID.get_logical_disks') def get_logical_disks(self, task): @@ -864,7 +987,7 @@ class DracRAID(base.RAIDInterface): for config_job_id in raid_config_job_ids: config_job = drac_job.get_job(node, job_id=config_job_id) - if config_job.status == 'Completed': + if config_job is None or config_job.status == 'Completed': finished_job_ids.append(config_job_id) elif config_job.status == 'Failed': finished_job_ids.append(config_job_id) @@ -876,13 +999,58 @@ class DracRAID(base.RAIDInterface): task.upgrade_lock() self._delete_cached_config_job_id(node, finished_job_ids) - if not node.driver_internal_info['raid_config_job_ids']: - if not node.driver_internal_info.get('raid_config_job_failure', - False): - self._resume_cleaning(task) + if not node.driver_internal_info.get('raid_config_job_failure', + False): + if 'raid_config_substep' in node.driver_internal_info: + if node.driver_internal_info['raid_config_substep'] == \ + 'delete_foreign_config': + self._execute_cleaning_foreign_drives(task, node) + elif node.driver_internal_info['raid_config_substep'] == \ + 'completed': + self._complete_raid_cleaning_substep(task, node) else: - self._clear_raid_config_job_failure(node) - self._set_clean_failed(task, config_job) + self._complete_raid_cleaning_substep(task, node) + else: + self._clear_raid_substep(node) + self._clear_raid_config_job_failure(node) + self._set_clean_failed(task, config_job) + + def _execute_cleaning_foreign_drives(self, task, node): + controllers = list() + jobs_required = False + for controller_id in node.driver_internal_info[ + 'raid_config_parameters']: + controller_cap = clear_foreign_config( + node, controller_id) + controller = {'raid_controller': controller_id, + 'is_reboot_required': + controller_cap[ + 'is_reboot_required']} + controllers.append(controller) + jobs_required = jobs_required or controller_cap[ + 'is_commit_required'] + + if not jobs_required: + LOG.info( + "No foreign drives detected, so " + "resume cleaning") + self._complete_raid_cleaning_substep(task, node) + else: + _commit_to_controllers( + node, + controllers, + substep='completed') + + def _complete_raid_cleaning_substep(self, task, node): + self._clear_raid_substep(node) + self._resume_cleaning(task) + + def _clear_raid_substep(self, node): + driver_internal_info = node.driver_internal_info + driver_internal_info.pop('raid_config_substep', None) + driver_internal_info.pop('raid_config_parameters', None) + node.driver_internal_info = driver_internal_info + node.save() def _set_raid_config_job_failure(self, node): driver_internal_info = node.driver_internal_info diff --git a/ironic/tests/unit/drivers/modules/drac/test_raid.py b/ironic/tests/unit/drivers/modules/drac/test_raid.py index df99736f28..eb0cab3640 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_raid.py +++ b/ironic/tests/unit/drivers/modules/drac/test_raid.py @@ -15,12 +15,14 @@ Test class for DRAC RAID interface """ +from dracclient import constants from dracclient import exceptions as drac_exceptions import mock from ironic.common import exception from ironic.common import states from ironic.conductor import task_manager +from ironic.conductor import utils as conductor_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 raid as drac_raid @@ -222,6 +224,62 @@ class DracManageVirtualDisksTestCase(test_utils.BaseDracTest): exception.DracOperationError, drac_raid.delete_virtual_disk, self.node, 'disk1') + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test__reset_raid_config(self, mock_validate_job_queue, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + + drac_raid._reset_raid_config( + self.node, 'controller') + + mock_validate_job_queue.assert_called_once_with(self.node) + mock_client.reset_raid_config.assert_called_once_with( + 'controller') + + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test__reset_raid_config_fail(self, mock_validate_job_queue, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + + exc = drac_exceptions.BaseClientException('boom') + mock_client.reset_raid_config.side_effect = exc + + self.assertRaises( + exception.DracOperationError, drac_raid._reset_raid_config, + self.node, 'RAID.Integrated.1-1') + + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test_clear_foreign_config(self, mock_validate_job_queue, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + + drac_raid.clear_foreign_config( + self.node, 'RAID.Integrated.1-1') + + mock_validate_job_queue.assert_called_once_with(self.node) + mock_client.clear_foreign_config.assert_called_once_with( + 'RAID.Integrated.1-1') + + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test_clear_foreign_config_fail(self, mock_validate_job_queue, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + + exc = drac_exceptions.BaseClientException('boom') + mock_client.clear_foreign_config.side_effect = exc + + self.assertRaises( + exception.DracOperationError, drac_raid.clear_foreign_config, + self.node, 'RAID.Integrated.1-1') + def test_commit_config(self, mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client @@ -620,7 +678,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_list_physical_disks.return_value = physical_disks mock_commit_config.return_value = '42' mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, @@ -657,7 +715,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): physical_disks = self._generate_physical_disks() mock_list_physical_disks.return_value = physical_disks mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, @@ -699,10 +757,9 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): physical_disks = self._generate_physical_disks() mock_list_physical_disks.return_value = physical_disks - mock_commit_config.return_value = '42' mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, @@ -758,7 +815,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_commit_config.return_value = '42' mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, @@ -802,15 +859,14 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_list_physical_disks.return_value = physical_disks mock_commit_config.side_effect = ['42', '12', '13'] - mock_client.create_virtual_disk.side_effect = [{ - 'is_reboot_required': 'True', + 'is_reboot_required': constants.RebootRequired.true, 'is_commit_required': True }, { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True }, { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True }] @@ -878,7 +934,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_commit_config.side_effect = ['42', '12', '13'] mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True } @@ -940,9 +996,8 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): physical_disks = self._generate_physical_disks() mock_list_physical_disks.return_value = physical_disks mock_commit_config.side_effect = ['42', '12'] - mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True } @@ -1007,7 +1062,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_commit_config.side_effect = ['42', '12', '13'] mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True } @@ -1100,7 +1155,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_commit_config.side_effect = ['42', '12'] mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True } @@ -1158,7 +1213,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_commit_config.return_value = '42' mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, @@ -1177,7 +1232,8 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): autospec=True) def test_create_configuration_with_max_size_and_share_physical_disks( self, mock_commit_config, mock_validate_job_queue, - mock_list_physical_disks, mock_get_drac_client): + mock_list_physical_disks, + mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client @@ -1199,7 +1255,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_commit_config.return_value = '42' mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True } @@ -1268,7 +1324,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_commit_config.return_value = '42' mock_client.create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, @@ -1309,7 +1365,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_commit_config.return_value = '42' mock_create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, @@ -1352,7 +1408,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_commit_config.return_value = '42' mock_create_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, @@ -1364,38 +1420,35 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, autospec=True) - @mock.patch.object(drac_raid, 'list_virtual_disks', autospec=True) + @mock.patch.object(drac_raid, 'list_raid_controllers', autospec=True) @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, autospec=True) @mock.patch.object(drac_raid, 'commit_config', spec_set=True, autospec=True) - @mock.patch.object(drac_raid, 'delete_virtual_disk', spec_set=True, + @mock.patch.object(drac_raid, '_reset_raid_config', spec_set=True, autospec=True) - def test_delete_configuration(self, mock_delete_virtual_disk, + def test_delete_configuration(self, mock__reset_raid_config, mock_commit_config, mock_validate_job_queue, - mock_list_virtual_disks, + mock_list_raid_controllers, mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client - virtual_disk_dict = { - 'id': 'Disk.Virtual.0:RAID.Integrated.1-1', - 'name': 'disk 0', - 'description': 'Virtual Disk 0 on Integrated RAID Controller 1', - 'controller': 'RAID.Integrated.1-1', - 'raid_level': '1', - 'size_mb': 571776, - 'status': 'ok', - 'raid_status': 'online', - 'span_depth': 1, - 'span_length': 2, - 'pending_operations': None, - 'physical_disks': []} - mock_list_virtual_disks.return_value = [ - test_utils.make_virtual_disk(virtual_disk_dict)] + raid_controller_dict = { + 'id': 'RAID.Integrated.1-1', + 'description': 'Integrated RAID Controller 1', + 'manufacturer': 'DELL', + 'model': 'PERC H710 Mini', + 'primary_status': 'ok', + 'firmware_version': '21.3.0-0009', + 'bus': '1', + 'supports_realtime': True} + + mock_list_raid_controllers.return_value = [ + test_utils.make_raid_controller(raid_controller_dict)] mock_commit_config.return_value = '42' - mock_delete_virtual_disk.return_value = { - 'is_reboot_required': 'optional', + mock__reset_raid_config.return_value = { + 'is_reboot_required': constants.RebootRequired.optional, 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, @@ -1413,30 +1466,24 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, autospec=True) - @mock.patch.object(drac_raid, 'list_virtual_disks', autospec=True) + @mock.patch.object(drac_raid, 'list_raid_controllers', autospec=True) @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, autospec=True) @mock.patch.object(drac_raid, 'commit_config', spec_set=True, autospec=True) - @mock.patch.object(drac_raid, 'delete_virtual_disk', spec_set=True, - autospec=True) - def test_delete_configuration_no_change(self, mock_delete_virtual_disk, - mock_commit_config, + def test_delete_configuration_no_change(self, mock_commit_config, mock_validate_job_queue, - mock_list_virtual_disks, + mock_list_raid_controllers, mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client - mock_list_virtual_disks.return_value = [] - mock_delete_virtual_disk.return_value = { - 'is_reboot_required': 'optional', - 'is_commit_required': True} + mock_list_raid_controllers.return_value = [] with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: return_value = task.driver.raid.delete_configuration(task) - self.assertEqual(0, mock_client.delete_virtual_disk.call_count) + self.assertEqual(0, mock_client._reset_raid_config.call_count) self.assertEqual(0, mock_commit_config.call_count) self.assertIsNone(return_value) @@ -1473,3 +1520,61 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): self.assertEqual({'logical_disks': [expected_logical_disk]}, props) + + @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) + @mock.patch.object(conductor_utils, '_notify_conductor_resume_operation', + autospec=True) + @mock.patch.object(drac_raid, 'clear_foreign_config', spec_set=True, + autospec=True) + @mock.patch.object(drac_raid, 'list_virtual_disks', autospec=True) + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test__execute_cleaning_foreign_drives(self, + mock_validate_job_queue, + mock_list_virtual_disks, + mock_clear_foreign_config, + mock_resume, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + virtual_disk_dict = { + 'id': 'Disk.Virtual.0:RAID.Integrated.1-1', + 'name': 'disk 0', + 'description': 'Virtual Disk 0 on Integrated RAID Controller 1', + 'controller': 'RAID.Integrated.1-1', + 'raid_level': '1', + 'size_mb': 571776, + 'status': 'ok', + 'raid_status': 'online', + 'span_depth': 1, + 'span_length': 2, + 'pending_operations': None, + 'physical_disks': []} + mock_list_virtual_disks.return_value = [ + test_utils.make_virtual_disk(virtual_disk_dict)] + + raid_config_params = ['RAID.Integrated.1-1'] + raid_config_substep = ['completed'] + driver_internal_info = self.node.driver_internal_info + driver_internal_info['raid_config_parameters'] = raid_config_params + driver_internal_info['raid_config_substep'] = raid_config_substep + self.node.driver_internal_info = driver_internal_info + self.node.save() + mock_clear_foreign_config.return_value = { + 'is_reboot_required': constants.RebootRequired.false, + 'is_commit_required': False + } + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + return_value = task.driver.raid._execute_cleaning_foreign_drives( + task, self.node) + mock_resume.assert_called_once_with( + task, 'cleaning', 'continue_node_clean') + + self.assertIsNone(return_value) + self.assertNotIn('raid_config_parameters', + self.node.driver_internal_info) + self.assertNotIn('raid_config_substep', + self.node.driver_internal_info) diff --git a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py index 49660c4add..da51d5059b 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py +++ b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py @@ -30,7 +30,14 @@ DRACCLIENT_CLIENT_MOD_SPEC = ( DRACCLIENT_CONSTANTS_MOD_SPEC = ( 'POWER_OFF', 'POWER_ON', - 'REBOOT' + 'REBOOT', + 'RebootRequired' +) + +DRACCLIENT_CONSTANTS_REBOOT_REQUIRED_MOD_SPEC = ( + 'true', + 'optional', + 'false' ) # ironic_inspector diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py index 6e6b188de7..2ac55889f3 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mocks.py +++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py @@ -90,6 +90,12 @@ if not dracclient: POWER_OFF=mock.sentinel.POWER_OFF, POWER_ON=mock.sentinel.POWER_ON, REBOOT=mock.sentinel.REBOOT) + dracclient.constants.RebootRequired = mock.MagicMock( + spec_set=mock_specs.DRACCLIENT_CONSTANTS_REBOOT_REQUIRED_MOD_SPEC, + true=mock.sentinel.true, + optional=mock.sentinel.optional, + false=mock.sentinel.false) + sys.modules['dracclient'] = dracclient sys.modules['dracclient.client'] = dracclient.client sys.modules['dracclient.constants'] = dracclient.constants diff --git a/releasenotes/notes/upgrade-delete_configuration-0f0bb43c57278734.yaml b/releasenotes/notes/upgrade-delete_configuration-0f0bb43c57278734.yaml new file mode 100644 index 0000000000..2df1c00442 --- /dev/null +++ b/releasenotes/notes/upgrade-delete_configuration-0f0bb43c57278734.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Foreign drives and global and dedicated hot spares will be freed + up during the RAID ``delete_configuration`` cleaning step.