From 3a54fe3e8559405a6901d23d65d17f50b3fe1ca8 Mon Sep 17 00:00:00 2001 From: digambar Date: Fri, 5 Apr 2019 20:05:24 -0400 Subject: [PATCH] Add iDRAC driver realtime RAID creation and deletion It adds capability to the idrac driver for creating and deleting RAID sets without rebooting the bare metal node. Change-Id: I3f4282afff860d9771a3d37f4d5e1172d88e87a3 Story: 2004861 --- driver-requirements.txt | 2 +- ironic/drivers/modules/drac/raid.py | 55 +++-- .../unit/drivers/modules/drac/test_raid.py | 206 +++++++++++++----- ...add-realtime-support-d814d5917836e9e2.yaml | 12 + 4 files changed, 208 insertions(+), 67 deletions(-) create mode 100644 releasenotes/notes/add-realtime-support-d814d5917836e9e2.yaml diff --git a/driver-requirements.txt b/driver-requirements.txt index da30bcb963..fd0dbc4d3e 100644 --- a/driver-requirements.txt +++ b/driver-requirements.txt @@ -9,7 +9,7 @@ pysnmp>=4.3.0,<5.0.0 python-ironic-inspector-client>=1.5.0 python-scciclient>=0.8.0 UcsSdk==0.8.2.2;python_version<'3' -python-dracclient>=1.5.0,<3.0.0 +python-dracclient>=3.0.0,<4.0.0 python-xclarityclient>=0.1.6 # The CIMC drivers use the Cisco IMC SDK version 0.7.2 or greater diff --git a/ironic/drivers/modules/drac/raid.py b/ironic/drivers/modules/drac/raid.py index 633216d1c5..f879560485 100644 --- a/ironic/drivers/modules/drac/raid.py +++ b/ironic/drivers/modules/drac/raid.py @@ -202,20 +202,25 @@ def delete_virtual_disk(node, virtual_disk): raise exception.DracOperationError(error=exc) -def commit_config(node, raid_controller, reboot=False): +def commit_config(node, raid_controller, reboot=False, realtime=False): """Apply all pending changes on a RAID controller. :param node: an ironic node object. :param raid_controller: id of the RAID controller. :param reboot: indicates whether a reboot job should be automatically created with the config job. (optional, defaults to False) + :param realtime: indicates RAID controller supports realtime. + (optional, defaults to False) :returns: id of the created job :raises: DracOperationError on an error from python-dracclient. """ client = drac_common.get_drac_client(node) try: - return client.commit_pending_raid_changes(raid_controller, reboot) + return client.commit_pending_raid_changes( + raid_controller=raid_controller, + reboot=reboot, + realtime=realtime) except drac_exceptions.BaseClientException as exc: LOG.error('DRAC driver failed to commit pending RAID config for' ' controller %(raid_controller_fqdd)s on node ' @@ -642,15 +647,25 @@ def _commit_to_controllers(node, controllers): if 'raid_config_job_ids' not in driver_internal_info: driver_internal_info['raid_config_job_ids'] = [] - controllers = list(controllers) + all_realtime = True for controller in controllers: - # Do a reboot only for the last controller + raid_controller = controller['raid_controller'] + + # Commit the configuration + # The logic below will reboot the node if there is at least one + # 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' + all_realtime = all_realtime and realtime if controller == controllers[-1]: - job_id = commit_config(node, raid_controller=controller, - reboot=True) + job_id = commit_config(node, raid_controller=raid_controller, + reboot=not all_realtime, + realtime=realtime) else: - job_id = commit_config(node, raid_controller=controller, - reboot=False) + job_id = commit_config(node, raid_controller=raid_controller, + reboot=False, + realtime=realtime) LOG.info('Change has been committed to RAID controller ' '%(controller)s on node %(node)s. ' @@ -735,10 +750,10 @@ class DracRAID(base.RAIDInterface): logical_disks_to_create = _filter_logical_disks( logical_disks, create_root_volume, create_nonroot_volumes) - controllers = set() + controllers = list() for logical_disk in logical_disks_to_create: - controllers.add(logical_disk['controller']) - create_virtual_disk( + controller = dict() + controller_cap = create_virtual_disk( node, raid_controller=logical_disk['controller'], physical_disks=logical_disk['physical_disks'], @@ -747,8 +762,12 @@ class DracRAID(base.RAIDInterface): disk_name=logical_disk.get('name'), span_length=logical_disk.get('span_length'), span_depth=logical_disk.get('span_depth')) + controller['raid_controller'] = logical_disk['controller'] + controller['is_reboot_required'] = controller_cap[ + 'is_reboot_required'] + controllers.append(controller) - return _commit_to_controllers(node, list(controllers)) + return _commit_to_controllers(node, controllers) @METRICS.timer('DracRAID.delete_configuration') @base.clean_step(priority=0) @@ -762,12 +781,16 @@ class DracRAID(base.RAIDInterface): """ node = task.node - controllers = set() + controllers = list() for disk in list_virtual_disks(node): - controllers.add(disk.controller) - delete_virtual_disk(node, disk.id) + 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) - return _commit_to_controllers(node, list(controllers)) + return _commit_to_controllers(node, controllers) @METRICS.timer('DracRAID.get_logical_disks') def get_logical_disks(self, task): diff --git a/ironic/tests/unit/drivers/modules/drac/test_raid.py b/ironic/tests/unit/drivers/modules/drac/test_raid.py index 8c4ffbaab5..4f27dae6f5 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_raid.py +++ b/ironic/tests/unit/drivers/modules/drac/test_raid.py @@ -47,7 +47,8 @@ class DracQueryRaidConfigurationTestCase(test_utils.BaseDracTest): 'model': 'PERC H710 Mini', 'primary_status': 'ok', 'firmware_version': '21.3.0-0009', - 'bus': '1'} + 'bus': '1', + 'supports_realtime': True} self.raid_controller = test_utils.make_raid_controller( raid_controller_dict) @@ -228,16 +229,27 @@ class DracManageVirtualDisksTestCase(test_utils.BaseDracTest): drac_raid.commit_config(self.node, 'controller1') mock_client.commit_pending_raid_changes.assert_called_once_with( - 'controller1', False) + raid_controller='controller1', reboot=False, realtime=False) def test_commit_config_with_reboot(self, mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client - drac_raid.commit_config(self.node, 'controller1', reboot=True) + drac_raid.commit_config(self.node, 'controller1', reboot=True, + realtime=False) mock_client.commit_pending_raid_changes.assert_called_once_with( - 'controller1', True) + raid_controller='controller1', reboot=True, realtime=False) + + def test_commit_config_with_realtime(self, mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + + drac_raid.commit_config(self.node, 'RAID.Integrated.1-1', reboot=False, + realtime=True) + + mock_client.commit_pending_raid_changes.assert_called_once_with( + raid_controller='RAID.Integrated.1-1', reboot=False, realtime=True) def test_commit_config_fail(self, mock_get_drac_client): mock_client = mock.Mock() @@ -607,6 +619,10 @@ 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', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -618,8 +634,10 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): ['Disk.Bay.0:Enclosure.Internal.0-1:RAID.Integrated.1-1', 'Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1'], '1', 51200, None, 2, 1) + mock_commit_config.assert_called_once_with( - task.node, raid_controller='RAID.Integrated.1-1', reboot=True) + task.node, raid_controller='RAID.Integrated.1-1', reboot=False, + realtime=True) self.node.refresh() self.assertEqual(['42'], @@ -639,6 +657,10 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_get_drac_client.return_value = mock_client 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', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -671,6 +693,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): 'raid_level': '5+0', 'is_root_volume': True } + self.logical_disks = [self.root_logical_disk] self.target_raid_configuration = {'logical_disks': self.logical_disks} self.node.target_raid_config = self.target_raid_configuration @@ -680,6 +703,10 @@ 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', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -698,7 +725,8 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): # Commits to the controller mock_commit_config.assert_called_once_with( - mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=True) + mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=False, + realtime=True) self.node.refresh() self.assertEqual(['42'], @@ -712,8 +740,9 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): @mock.patch.object(drac_raid, 'commit_config', spec_set=True, autospec=True) def test_create_configuration_with_nested_raid_10( - self, mock_commit_config, mock_validate_job_queue, - mock_list_physical_disks, mock_get_drac_client): + self, mock_commit_config, + mock_validate_job_queue, mock_list_physical_disks, + mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client @@ -731,6 +760,10 @@ 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', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -747,7 +780,8 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): # Commits to the controller mock_commit_config.assert_called_once_with( - mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=True) + mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=False, + realtime=True) self.node.refresh() self.assertEqual(['42'], @@ -771,13 +805,26 @@ 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_commit_config.side_effect = ['42', '12', '13'] + + mock_client.create_virtual_disk.side_effect = [{ + 'is_reboot_required': 'True', + 'commit_required': True, + 'is_commit_required': True + }, { + 'is_reboot_required': 'optional', + 'commit_required': False, + 'is_commit_required': True + }, { + 'is_reboot_required': 'optional', + 'commit_required': False, + 'is_commit_required': True + }] with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.driver.raid.create_configuration( task, create_root_volume=True, create_nonroot_volumes=True) - mock_client.create_virtual_disk.assert_has_calls( [mock.call( 'controller-2', @@ -798,23 +845,17 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): '5', 102400, None, 3, 1) ], any_order=True) + # Commits to both controller mock_commit_config.assert_has_calls( [mock.call(mock.ANY, raid_controller='controller-2', - reboot=mock.ANY), + reboot=mock.ANY, realtime=mock.ANY), mock.call(mock.ANY, raid_controller='RAID.Integrated.1-1', - reboot=mock.ANY)], - any_order=True) - # One of the config jobs should issue a reboot - mock_commit_config.assert_has_calls( - [mock.call(mock.ANY, raid_controller=mock.ANY, - reboot=False), - mock.call(mock.ANY, raid_controller=mock.ANY, - reboot=True)], + reboot=mock.ANY, realtime=mock.ANY)], any_order=True) self.node.refresh() - self.assertEqual(['42', '12'], + self.assertEqual(['42', '12', '13'], self.node.driver_internal_info['raid_config_job_ids']) @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, @@ -842,13 +883,17 @@ 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_commit_config.side_effect = ['42', '12', '13'] + mock_client.create_virtual_disk.return_value = { + 'is_reboot_required': 'optional', + 'commit_required': False, + 'is_commit_required': True + } with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.driver.raid.create_configuration( task, create_root_volume=True, create_nonroot_volumes=True) - mock_client.create_virtual_disk.assert_has_calls( [mock.call( 'RAID.Integrated.1-1', @@ -870,11 +915,12 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): any_order=True) # Commits to the controller - mock_commit_config.assert_called_once_with( - mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=True) + mock_commit_config.assert_called_with( + mock.ANY, raid_controller='RAID.Integrated.1-1', + reboot=False, realtime=True) self.node.refresh() - self.assertEqual(['42'], + self.assertEqual(['42', '12', '13'], self.node.driver_internal_info['raid_config_job_ids']) @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, @@ -887,6 +933,7 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): def test_create_configuration_with_predefined_number_of_phyisical_disks( self, mock_commit_config, mock_validate_job_queue, mock_list_physical_disks, mock_get_drac_client): + mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client @@ -900,8 +947,13 @@ 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_commit_config.return_value = '42' + mock_client.create_virtual_disk.return_value = { + 'is_reboot_required': 'optional', + 'commit_required': False, + 'is_commit_required': True + } with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -924,11 +976,12 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): any_order=True) # Commits to the controller - mock_commit_config.assert_called_once_with( - mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=True) + mock_commit_config.assert_called_with( + mock.ANY, raid_controller='RAID.Integrated.1-1', + reboot=False, realtime=True) self.node.refresh() - self.assertEqual(['42'], + self.assertEqual(['42', '12'], self.node.driver_internal_info['raid_config_job_ids']) @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, @@ -961,7 +1014,12 @@ 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_commit_config.side_effect = ['42', '12', '13'] + mock_client.create_virtual_disk.return_value = { + 'is_reboot_required': 'optional', + 'commit_required': False, + 'is_commit_required': True + } with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -989,11 +1047,12 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): any_order=True) # Commits to the controller - mock_commit_config.assert_called_once_with( - mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=True) + mock_commit_config.assert_called_with( + mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=False, + realtime=True) self.node.refresh() - self.assertEqual(['42'], + self.assertEqual(['42', '12', '13'], self.node.driver_internal_info['raid_config_job_ids']) @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, @@ -1049,7 +1108,12 @@ 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_commit_config.side_effect = ['42', '12'] + mock_client.create_virtual_disk.return_value = { + 'is_reboot_required': 'optional', + 'commit_required': False, + 'is_commit_required': True + } with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -1071,11 +1135,12 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): '5', 102400, None, 3, 1)]) # Commits to the controller - mock_commit_config.assert_called_once_with( - mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=True) + mock_commit_config.assert_called_with( + mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=False, + realtime=True) self.node.refresh() - self.assertEqual(['42'], + self.assertEqual(['42', '12'], self.node.driver_internal_info['raid_config_job_ids']) @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, @@ -1103,6 +1168,10 @@ 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', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -1141,6 +1210,11 @@ 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', + 'commit_required': False, + 'is_commit_required': True + } with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -1163,11 +1237,12 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): any_order=True) # Commits to the controller - mock_commit_config.assert_called_once_with( - mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=True) + mock_commit_config.assert_called_with( + mock.ANY, raid_controller='RAID.Integrated.1-1', reboot=False, + realtime=True) self.node.refresh() - self.assertEqual(['42'], + self.assertEqual(['42', '42'], self.node.driver_internal_info['raid_config_job_ids']) @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, @@ -1205,6 +1280,10 @@ 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', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -1220,9 +1299,12 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): autospec=True) @mock.patch.object(drac_raid, 'commit_config', spec_set=True, autospec=True) + @mock.patch.object(drac_raid, 'create_virtual_disk', spec_set=True, + autospec=True) def test_create_configuration_fails_if_not_enough_space( - self, mock_commit_config, mock_validate_job_queue, - mock_list_physical_disks, mock_get_drac_client): + self, mock_create_virtual_disk, mock_commit_config, + mock_validate_job_queue, mock_list_physical_disks, + mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client @@ -1240,6 +1322,10 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_list_physical_disks.return_value = physical_disks mock_commit_config.return_value = '42' + mock_create_virtual_disk.return_value = { + 'is_reboot_required': 'optional', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -1255,9 +1341,12 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): autospec=True) @mock.patch.object(drac_raid, 'commit_config', spec_set=True, autospec=True) + @mock.patch.object(drac_raid, 'create_virtual_disk', spec_set=True, + autospec=True) def test_create_configuration_fails_if_disk_already_reserved( - self, mock_commit_config, mock_validate_job_queue, - mock_list_physical_disks, mock_get_drac_client): + self, mock_create_virtual_disk, mock_commit_config, + mock_validate_job_queue, mock_list_physical_disks, + mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client @@ -1277,6 +1366,10 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_list_physical_disks.return_value = physical_disks mock_commit_config.return_value = '42' + mock_create_virtual_disk.return_value = { + 'is_reboot_required': 'optional', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -1292,7 +1385,10 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): autospec=True) @mock.patch.object(drac_raid, 'commit_config', spec_set=True, autospec=True) - def test_delete_configuration(self, mock_commit_config, + @mock.patch.object(drac_raid, 'delete_virtual_disk', spec_set=True, + autospec=True) + def test_delete_configuration(self, mock_delete_virtual_disk, + mock_commit_config, mock_validate_job_queue, mock_list_virtual_disks, mock_get_drac_client): @@ -1314,15 +1410,18 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_list_virtual_disks.return_value = [ test_utils.make_virtual_disk(virtual_disk_dict)] mock_commit_config.return_value = '42' + mock_delete_virtual_disk.return_value = { + 'is_reboot_required': 'optional', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: return_value = task.driver.raid.delete_configuration(task) - mock_client.delete_virtual_disk.assert_called_once_with( - 'Disk.Virtual.0:RAID.Integrated.1-1') mock_commit_config.assert_called_once_with( - task.node, raid_controller='RAID.Integrated.1-1', reboot=True) + task.node, raid_controller='RAID.Integrated.1-1', reboot=False, + realtime=True) self.assertEqual(states.CLEANWAIT, return_value) self.node.refresh() @@ -1336,13 +1435,20 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): autospec=True) @mock.patch.object(drac_raid, 'commit_config', spec_set=True, autospec=True) - def test_delete_configuration_no_change(self, mock_commit_config, + @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, mock_validate_job_queue, mock_list_virtual_disks, 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', + 'commit_required': False, + 'is_commit_required': True} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: diff --git a/releasenotes/notes/add-realtime-support-d814d5917836e9e2.yaml b/releasenotes/notes/add-realtime-support-d814d5917836e9e2.yaml new file mode 100644 index 0000000000..a6774c4453 --- /dev/null +++ b/releasenotes/notes/add-realtime-support-d814d5917836e9e2.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Adds capability to hardware type ``idrac`` for creating and + deleting RAID sets without rebooting the baremetal node. + This realtime mechanism is supported on PERC H730 and H740 + RAID controllers that are running firmware version 25.5.5.0005 + or later. + +upgrade: + - | + This change requires python-dracclient version 3.0.0 and later.