diff options
author | Dao Cong Tien <tiendc@vn.fujitsu.com> | 2018-02-13 16:55:22 +0700 |
---|---|---|
committer | Dao Cong Tien <tiendc@vn.fujitsu.com> | 2018-03-13 09:45:42 +0000 |
commit | f7da3f6ec2b906c9e8a42308659778dbd8038171 (patch) | |
tree | 5a6aaf8b0c233833fc9f10b0d2d69d1b3f4ee37b | |
parent | 9ab04d0962b5d3ccf9b616efdb0c5113be84041c (diff) | |
download | ironic-f7da3f6ec2b906c9e8a42308659778dbd8038171.tar.gz |
Implements validate_rescue() for IRMCVirtualMediaBoot
This commit implements validate_rescue() for 'irmc-virtual-media'
boot interface of 'irmc' hardware type.
With this it enables 'agent' rescue interface for 'irmc' hardware
type when corresponding boot interface being used is
'irmc-virtual-media'. Support already exists for the 'agent'
rescue interface with 'irmc-pxe' boot interface.
Also fix a bug in _remove_share_file() causing files
not to be removed correctly.
Change-Id: Ib602b2705b8fa4f7161b9f97857ec6546f5e9b19
Closes-Bug: #1747842
-rw-r--r-- | ironic/drivers/modules/irmc/boot.py | 149 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/irmc/test_boot.py | 188 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/test_irmc.py | 31 | ||||
-rw-r--r-- | releasenotes/notes/rescue-interface-for-irmc-hardware-type-17e38197849748e0.yaml | 7 |
4 files changed, 280 insertions, 95 deletions
diff --git a/ironic/drivers/modules/irmc/boot.py b/ironic/drivers/modules/irmc/boot.py index 3d4cd0659..2732702bb 100644 --- a/ironic/drivers/modules/irmc/boot.py +++ b/ironic/drivers/modules/irmc/boot.py @@ -58,6 +58,12 @@ REQUIRED_PROPERTIES = { "Required."), } +RESCUE_PROPERTIES = { + 'irmc_rescue_iso': _("UUID (from Glance) of the rescue ISO. Only " + "required if rescue mode is being used and ironic " + "is managing booting the rescue ramdisk.") +} + OPTIONAL_PROPERTIES = { 'irmc_pci_physical_ids': _("Physical IDs of PCI cards. A dictionary of pairs of resource UUID " @@ -96,7 +102,7 @@ def _parse_config_option(): raise exception.InvalidParameterValue(msg) -def _parse_driver_info(node): +def _parse_driver_info(node, mode='deploy'): """Gets the driver specific Node deployment info. This method validates whether the 'driver_info' property of the @@ -104,6 +110,9 @@ def _parse_driver_info(node): for this driver to deploy images to the node. :param node: a target node of the deployment + :param mode: Label indicating a deploy or rescue operation being + carried out on the node. Supported values are + 'deploy' and 'rescue'. Defaults to 'deploy'. :returns: the driver_info values of the node. :raises: MissingParameterValue, if any of the required parameters are missing. @@ -113,19 +122,26 @@ def _parse_driver_info(node): d_info = node.driver_info deploy_info = {} - deploy_info['irmc_deploy_iso'] = d_info.get('irmc_deploy_iso') - error_msg = _("Error validating iRMC virtual media deploy. Some parameters" - " were missing in node's driver_info") + if mode == 'deploy': + image_iso = d_info.get('irmc_deploy_iso') + deploy_info['irmc_deploy_iso'] = image_iso + else: + image_iso = d_info.get('irmc_rescue_iso') + deploy_info['irmc_rescue_iso'] = image_iso + + error_msg = (_("Error validating iRMC virtual media for %s. Some " + "parameters were missing in node's driver_info") % mode) deploy_utils.check_for_missing_params(deploy_info, error_msg) - if service_utils.is_image_href_ordinary_file_name( - deploy_info['irmc_deploy_iso']): - deploy_iso = os.path.join(CONF.irmc.remote_image_share_root, - deploy_info['irmc_deploy_iso']) - if not os.path.isfile(deploy_iso): - msg = (_("Deploy ISO file, %(deploy_iso)s, " + if service_utils.is_image_href_ordinary_file_name(image_iso): + image_iso_file = os.path.join(CONF.irmc.remote_image_share_root, + image_iso) + if not os.path.isfile(image_iso_file): + msg = (_("%(mode)s ISO file, %(iso_file)s, " "not found for node: %(node)s.") % - {'deploy_iso': deploy_iso, 'node': node.uuid}) + {'mode': mode.capitalize(), + 'iso_file': image_iso_file, + 'node': node.uuid}) raise exception.InvalidParameterValue(msg) return deploy_info @@ -185,13 +201,16 @@ def _parse_deploy_info(node): return deploy_info -def _setup_deploy_iso(task, ramdisk_options): +def _setup_vmedia(task, mode, ramdisk_options): """Attaches virtual media and sets it as boot device. - This method attaches the given deploy ISO as virtual media, prepares the - arguments for ramdisk in virtual media floppy. + This method attaches the deploy or rescue ISO as virtual media, prepares + the arguments for ramdisk in virtual media floppy. :param task: a TaskManager instance containing the node to act on. + :param mode: Label indicating a deploy or rescue operation being + carried out on the node. Supported values are + 'deploy' and 'rescue'. :param ramdisk_options: the options to be passed to the ramdisk in virtual media floppy. :raises: ImageRefValidationFailed if no image service can handle specified @@ -201,35 +220,31 @@ def _setup_deploy_iso(task, ramdisk_options): :raises: InvalidParameterValue if the validation of the PowerInterface or ManagementInterface fails. """ - d_info = task.node.driver_info - deploy_iso_href = d_info['irmc_deploy_iso'] - if service_utils.is_image_href_ordinary_file_name(deploy_iso_href): - deploy_iso_file = deploy_iso_href + if mode == 'rescue': + iso = task.node.driver_info['irmc_rescue_iso'] else: - deploy_iso_file = _get_deploy_iso_name(task.node) - deploy_iso_fullpathname = os.path.join( - CONF.irmc.remote_image_share_root, deploy_iso_file) - images.fetch(task.context, deploy_iso_href, deploy_iso_fullpathname) + iso = task.node.driver_info['irmc_deploy_iso'] - _setup_vmedia_for_boot(task, deploy_iso_file, ramdisk_options) - manager_utils.node_set_boot_device(task, boot_devices.CDROM) - - -def _get_deploy_iso_name(node): - """Returns the deploy ISO file name for a given node. + if service_utils.is_image_href_ordinary_file_name(iso): + iso_file = iso + else: + iso_file = _get_iso_name(task.node, label=mode) + iso_fullpathname = os.path.join( + CONF.irmc.remote_image_share_root, iso_file) + images.fetch(task.context, iso, iso_fullpathname) - :param node: the node for which ISO file name is to be provided. - """ - return "deploy-%s.iso" % node.uuid + _setup_vmedia_for_boot(task, iso_file, ramdisk_options) + manager_utils.node_set_boot_device(task, boot_devices.CDROM) -def _get_boot_iso_name(node): - """Returns the boot ISO file name for a given node. +def _get_iso_name(node, label): + """Returns the ISO file name for a given node. :param node: the node for which ISO file name is to be provided. + :param label: a string used as a base name for the ISO file. """ - return "boot-%s.iso" % node.uuid + return "%s-%s.iso" % (label, node.uuid) def _prepare_boot_iso(task, root_uuid): @@ -253,7 +268,7 @@ def _prepare_boot_iso(task, root_uuid): if service_utils.is_image_href_ordinary_file_name(boot_iso_href): driver_internal_info['irmc_boot_iso'] = boot_iso_href else: - boot_iso_filename = _get_boot_iso_name(task.node) + boot_iso_filename = _get_iso_name(task.node, label='boot') boot_iso_fullpathname = os.path.join( CONF.irmc.remote_image_share_root, boot_iso_filename) images.fetch(task.context, boot_iso_href, boot_iso_fullpathname) @@ -271,13 +286,13 @@ def _prepare_boot_iso(task, root_uuid): ramdisk_href = (task.node.instance_info.get('ramdisk') or image_properties['ramdisk_id']) - deploy_iso_filename = _get_deploy_iso_name(task.node) + deploy_iso_filename = _get_iso_name(task.node, label='deploy') deploy_iso = ('file://' + os.path.join( CONF.irmc.remote_image_share_root, deploy_iso_filename)) boot_mode = deploy_utils.get_boot_mode_for_deploy(task.node) kernel_params = CONF.pxe.pxe_append_params - boot_iso_filename = _get_boot_iso_name(task.node) + boot_iso_filename = _get_iso_name(task.node, label='boot') boot_iso_fullpathname = os.path.join( CONF.irmc.remote_image_share_root, boot_iso_filename) @@ -399,7 +414,8 @@ def _cleanup_vmedia_boot(task): _detach_virtual_fd(node) _remove_share_file(_get_floppy_image_name(node)) - _remove_share_file(_get_deploy_iso_name(node)) + _remove_share_file(_get_iso_name(node, label='deploy')) + _remove_share_file(_get_iso_name(node, label='rescue')) def _remove_share_file(share_filename): @@ -408,7 +424,7 @@ def _remove_share_file(share_filename): :param share_filename: a file name to be removed. """ share_fullpathname = os.path.join( - CONF.irmc.remote_image_share_name, share_filename) + CONF.irmc.remote_image_share_root, share_filename) ironic_utils.unlink_without_raise(share_fullpathname) @@ -877,6 +893,9 @@ class IRMCVirtualMediaBoot(base.BootInterface, IRMCVolumeBootMixIn): super(IRMCVirtualMediaBoot, self).__init__() def get_properties(self): + # TODO(tiendc): COMMON_PROPERTIES should also include rescue + # related properties (RESCUE_PROPERTIES). We can add them in Rocky, + # when classic drivers get removed. return COMMON_PROPERTIES @METRICS.timer('IRMCVirtualMediaBoot.validate') @@ -913,13 +932,13 @@ class IRMCVirtualMediaBoot(base.BootInterface, IRMCVolumeBootMixIn): @METRICS.timer('IRMCVirtualMediaBoot.prepare_ramdisk') def prepare_ramdisk(self, task, ramdisk_params): - """Prepares the deploy ramdisk using virtual media. + """Prepares the deploy or rescue ramdisk using virtual media. - Prepares the options for the deployment ramdisk, sets the node to boot - from virtual media cdrom. + Prepares the options for the deploy or rescue ramdisk, sets the node + to boot from virtual media cdrom. :param task: a TaskManager instance containing the node to act on. - :param ramdisk_params: the options to be passed to the deploy ramdisk. + :param ramdisk_params: the options to be passed to the ramdisk. :raises: ImageRefValidationFailed if no image service can handle specified href. :raises: ImageCreationFailed, if it failed while creating the floppy @@ -930,11 +949,12 @@ class IRMCVirtualMediaBoot(base.BootInterface, IRMCVolumeBootMixIn): """ # NOTE(TheJulia): If this method is being called by something - # aside from deployment and clean, such as conductor takeover, we - # should treat this as a no-op and move on otherwise we would modify - # the state of the node due to virtual media operations. - if (task.node.provision_state != states.DEPLOYING and - task.node.provision_state != states.CLEANING): + # aside from deployment, clean and rescue, such as conductor takeover, + # we should treat this as a no-op and move on otherwise we would + # modify the state of the node due to virtual media operations. + if task.node.provision_state not in (states.DEPLOYING, + states.CLEANING, + states.RESCUING): return # NOTE(tiendc): Before deploying, we need to backup BIOS config @@ -951,14 +971,19 @@ class IRMCVirtualMediaBoot(base.BootInterface, IRMCVolumeBootMixIn): deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task) ramdisk_params['BOOTIF'] = deploy_nic_mac - _setup_deploy_iso(task, ramdisk_params) + if task.node.provision_state == states.RESCUING: + mode = 'rescue' + else: + mode = 'deploy' + + _setup_vmedia(task, mode, ramdisk_params) @METRICS.timer('IRMCVirtualMediaBoot.clean_up_ramdisk') def clean_up_ramdisk(self, task): """Cleans up the boot of ironic ramdisk. This method cleans up the environment that was setup for booting the - deploy ramdisk. + deploy or rescue ramdisk. :param task: a task from TaskManager. :returns: None @@ -1018,10 +1043,18 @@ class IRMCVirtualMediaBoot(base.BootInterface, IRMCVolumeBootMixIn): if deploy_utils.is_secure_boot_requested(task.node): irmc_common.set_secure_boot_mode(task.node, enable=False) - _remove_share_file(_get_boot_iso_name(task.node)) + _remove_share_file(_get_iso_name(task.node, label='boot')) driver_internal_info = task.node.driver_internal_info driver_internal_info.pop('irmc_boot_iso', None) - driver_internal_info.pop('root_uuid_or_disk_id', None) + + # When rescue, this function is called. But we need to retain the + # root_uuid_or_disk_id to use on unrescue (see prepare_instance). + boot_local_or_iwdi = ( + deploy_utils.get_boot_option(task.node) == "local" or + driver_internal_info.get('is_whole_disk_image')) + if task.node.provision_state != states.RESCUING or boot_local_or_iwdi: + driver_internal_info.pop('root_uuid_or_disk_id', None) + task.node.driver_internal_info = driver_internal_info task.node.save() _cleanup_vmedia_boot(task) @@ -1035,6 +1068,18 @@ class IRMCVirtualMediaBoot(base.BootInterface, IRMCVolumeBootMixIn): manager_utils.node_set_boot_device(task, boot_devices.CDROM, persistent=True) + @METRICS.timer('IRMCVirtualMediaBoot.validate_rescue') + def validate_rescue(self, task): + """Validate that the node has required properties for rescue. + + :param task: a TaskManager instance with the node being checked + :raises: MissingParameterValue if node is missing one or more required + parameters + :raises: InvalidParameterValue, if any of the parameters have invalid + value. + """ + _parse_driver_info(task.node, mode='rescue') + class IRMCPXEBoot(pxe.PXEBoot): """iRMC PXE boot.""" diff --git a/ironic/tests/unit/drivers/modules/irmc/test_boot.py b/ironic/tests/unit/drivers/modules/irmc/test_boot.py index af6fa2521..9e4159623 100644 --- a/ironic/tests/unit/drivers/modules/irmc/test_boot.py +++ b/ironic/tests/unit/drivers/modules/irmc/test_boot.py @@ -111,7 +111,8 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): self.node.driver_info['irmc_deploy_iso'] = 'deploy.iso' driver_info_expected = {'irmc_deploy_iso': 'deploy.iso'} - driver_info_actual = irmc_boot._parse_driver_info(self.node) + driver_info_actual = irmc_boot._parse_driver_info(self.node, + mode='deploy') isfile_mock.assert_called_once_with( '/remote_image_share_root/deploy.iso') @@ -123,17 +124,18 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): self, is_image_href_ordinary_file_name_mock): """With required 'irmc_deploy_iso' not in share.""" self.node.driver_info[ - 'irmc_deploy_iso'] = 'bc784057-a140-4130-add3-ef890457e6b3' - driver_info_expected = {'irmc_deploy_iso': + 'irmc_rescue_iso'] = 'bc784057-a140-4130-add3-ef890457e6b3' + driver_info_expected = {'irmc_rescue_iso': 'bc784057-a140-4130-add3-ef890457e6b3'} is_image_href_ordinary_file_name_mock.return_value = False - driver_info_actual = irmc_boot._parse_driver_info(self.node) + driver_info_actual = irmc_boot._parse_driver_info(self.node, + mode='rescue') self.assertEqual(driver_info_expected, driver_info_actual) @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True) - def test__parse_driver_info_with_deploy_iso_invalid(self, isfile_mock): + def test__parse_driver_info_with_iso_invalid(self, isfile_mock): """With required 'irmc_deploy_iso' non existed.""" isfile_mock.return_value = False @@ -146,19 +148,19 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): e = self.assertRaises(exception.InvalidParameterValue, irmc_boot._parse_driver_info, - task.node) + task.node, mode='deploy') self.assertEqual(error_msg, str(e)) - def test__parse_driver_info_with_deploy_iso_missing(self): - """With required 'irmc_deploy_iso' empty.""" - self.node.driver_info['irmc_deploy_iso'] = None + def test__parse_driver_info_with_iso_missing(self): + """With required 'irmc_rescue_iso' empty.""" + self.node.driver_info['irmc_rescue_iso'] = None - error_msg = ("Error validating iRMC virtual media deploy. Some" + error_msg = ("Error validating iRMC virtual media for rescue. Some" " parameters were missing in node's driver_info." - " Missing are: ['irmc_deploy_iso']") + " Missing are: ['irmc_rescue_iso']") e = self.assertRaises(exception.MissingParameterValue, irmc_boot._parse_driver_info, - self.node) + self.node, mode='rescue') self.assertEqual(error_msg, str(e)) def test__parse_instance_info_with_boot_iso_file_name_ok(self): @@ -274,15 +276,16 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): autospec=True) @mock.patch.object(images, 'fetch', spec_set=True, autospec=True) - def test__setup_deploy_iso_with_file(self, - fetch_mock, - setup_vmedia_mock, - set_boot_device_mock): + def test__setup_vmedia_with_file_deploy(self, + fetch_mock, + setup_vmedia_mock, + set_boot_device_mock): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.node.driver_info['irmc_deploy_iso'] = 'deploy_iso_filename' ramdisk_opts = {'a': 'b'} - irmc_boot._setup_deploy_iso(task, ramdisk_opts) + irmc_boot._setup_vmedia(task, mode='deploy', + ramdisk_options=ramdisk_opts) self.assertFalse(fetch_mock.called) @@ -299,7 +302,33 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): autospec=True) @mock.patch.object(images, 'fetch', spec_set=True, autospec=True) - def test_setup_deploy_iso_with_image_service( + def test__setup_vmedia_with_file_rescue(self, + fetch_mock, + setup_vmedia_mock, + set_boot_device_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.driver_info['irmc_rescue_iso'] = 'rescue_iso_filename' + ramdisk_opts = {'a': 'b'} + irmc_boot._setup_vmedia(task, mode='rescue', + ramdisk_options=ramdisk_opts) + + self.assertFalse(fetch_mock.called) + + setup_vmedia_mock.assert_called_once_with( + task, + 'rescue_iso_filename', + ramdisk_opts) + set_boot_device_mock.assert_called_once_with(task, + boot_devices.CDROM) + + @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, + autospec=True) + @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, + autospec=True) + @mock.patch.object(images, 'fetch', spec_set=True, + autospec=True) + def test_setup_vmedia_with_image_service_deploy( self, fetch_mock, setup_vmedia_mock, @@ -310,7 +339,8 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): shared=False) as task: task.node.driver_info['irmc_deploy_iso'] = 'glance://deploy_iso' ramdisk_opts = {'a': 'b'} - irmc_boot._setup_deploy_iso(task, ramdisk_opts) + irmc_boot._setup_vmedia(task, mode='deploy', + ramdisk_options=ramdisk_opts) fetch_mock.assert_called_once_with( task.context, @@ -324,14 +354,41 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): set_boot_device_mock.assert_called_once_with( task, boot_devices.CDROM) - def test__get_deploy_iso_name(self): - actual = irmc_boot._get_deploy_iso_name(self.node) - expected = "deploy-%s.iso" % self.node.uuid - self.assertEqual(expected, actual) + @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, + autospec=True) + @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, + autospec=True) + @mock.patch.object(images, 'fetch', spec_set=True, + autospec=True) + def test_setup_vmedia_with_image_service_rescue( + self, + fetch_mock, + setup_vmedia_mock, + set_boot_device_mock): + CONF.irmc.remote_image_share_root = '/' - def test__get_boot_iso_name(self): - actual = irmc_boot._get_boot_iso_name(self.node) - expected = "boot-%s.iso" % self.node.uuid + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.driver_info['irmc_rescue_iso'] = 'glance://rescue_iso' + ramdisk_opts = {'a': 'b'} + irmc_boot._setup_vmedia(task, mode='rescue', + ramdisk_options=ramdisk_opts) + + fetch_mock.assert_called_once_with( + task.context, + 'glance://rescue_iso', + "/rescue-%s.iso" % self.node.uuid) + + setup_vmedia_mock.assert_called_once_with( + task, + "rescue-%s.iso" % self.node.uuid, + ramdisk_opts) + set_boot_device_mock.assert_called_once_with( + task, boot_devices.CDROM) + + def test__get_iso_name(self): + actual = irmc_boot._get_iso_name(self.node, label='deploy') + expected = "deploy-%s.iso" % self.node.uuid self.assertEqual(expected, actual) @mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True) @@ -599,7 +656,7 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): task.node, 'bootable_iso_filename') - @mock.patch.object(irmc_boot, '_get_deploy_iso_name', spec_set=True, + @mock.patch.object(irmc_boot, '_get_iso_name', spec_set=True, autospec=True) @mock.patch.object(irmc_boot, '_get_floppy_image_name', spec_set=True, autospec=True) @@ -614,7 +671,7 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): _detach_virtual_fd_mock, _remove_share_file_mock, _get_floppy_image_name_mock, - _get_deploy_iso_name_mock): + _get_iso_name_mock): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: irmc_boot._cleanup_vmedia_boot(task) @@ -622,20 +679,23 @@ class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase): _detach_virtual_cd_mock.assert_called_once_with(task.node) _detach_virtual_fd_mock.assert_called_once_with(task.node) _get_floppy_image_name_mock.assert_called_once_with(task.node) - _get_deploy_iso_name_mock.assert_called_once_with(task.node) - self.assertTrue(_remove_share_file_mock.call_count, 2) + _get_iso_name_mock.assert_has_calls( + [mock.call(task.node, label='deploy'), + mock.call(task.node, label='rescue')]) + self.assertTrue(_remove_share_file_mock.call_count, 3) _remove_share_file_mock.assert_has_calls( [mock.call(_get_floppy_image_name_mock(task.node)), - mock.call(_get_deploy_iso_name_mock(task.node))]) + mock.call(_get_iso_name_mock(task.node, label='deploy')), + mock.call(_get_iso_name_mock(task.node, label='rescue'))]) @mock.patch.object(ironic_utils, 'unlink_without_raise', spec_set=True, autospec=True) def test__remove_share_file(self, unlink_without_raise_mock): - CONF.irmc.remote_image_share_name = '/' + CONF.irmc.remote_image_share_root = '/share' irmc_boot._remove_share_file("boot.iso") - unlink_without_raise_mock.assert_called_once_with('/boot.iso') + unlink_without_raise_mock.assert_called_once_with('/share/boot.iso') @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, autospec=True) @@ -833,9 +893,18 @@ class IRMCVirtualMediaBootTestCase(db_base.DbTestCase): irmc_boot.check_share_fs_mounted_patcher.start() self.addCleanup(irmc_boot.check_share_fs_mounted_patcher.stop) super(IRMCVirtualMediaBootTestCase, self).setUp() - mgr_utils.mock_the_extension_manager(driver="iscsi_irmc") + self.config(enabled_hardware_types=['irmc'], + enabled_boot_interfaces=['irmc-virtual-media'], + enabled_console_interfaces=['ipmitool-socat'], + enabled_deploy_interfaces=['iscsi'], + enabled_inspect_interfaces=['irmc'], + enabled_management_interfaces=['irmc'], + enabled_power_interfaces=['irmc'], + enabled_raid_interfaces=['no-raid'], + enabled_rescue_interfaces=['agent'], + enabled_vendor_interfaces=['no-vendor']) self.node = obj_utils.create_test_node( - self.context, driver='iscsi_irmc', driver_info=INFO_DICT) + self.context, driver='irmc', driver_info=INFO_DICT) @mock.patch.object(deploy_utils, 'validate_image_properties', spec_set=True, autospec=True) @@ -915,14 +984,15 @@ class IRMCVirtualMediaBootTestCase(db_base.DbTestCase): @mock.patch.object(irmc_management, 'backup_bios_config', spec_set=True, autospec=True) - @mock.patch.object(irmc_boot, '_setup_deploy_iso', + @mock.patch.object(irmc_boot, '_setup_vmedia', spec_set=True, autospec=True) @mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id', spec_set=True, autospec=True) def _test_prepare_ramdisk(self, get_single_nic_with_vif_port_id_mock, - _setup_deploy_iso_mock, - mock_backup_bios): + _setup_vmedia_mock, + mock_backup_bios, + mode='deploy'): instance_info = self.node.instance_info instance_info['irmc_boot_iso'] = 'glance://abcdef' instance_info['image_source'] = '6b2f0c0c-79e8-4db6-842e-43c9764204af' @@ -938,8 +1008,8 @@ class IRMCVirtualMediaBootTestCase(db_base.DbTestCase): expected_ramdisk_opts = {'a': 'b', 'BOOTIF': '12:34:56:78:90:ab'} get_single_nic_with_vif_port_id_mock.assert_called_once_with( task) - _setup_deploy_iso_mock.assert_called_once_with( - task, expected_ramdisk_opts) + _setup_vmedia_mock.assert_called_once_with( + task, mode, expected_ramdisk_opts) self.assertEqual('glance://abcdef', self.node.instance_info['irmc_boot_iso']) provision_state = task.node.provision_state @@ -951,12 +1021,17 @@ class IRMCVirtualMediaBootTestCase(db_base.DbTestCase): self.node.save() self._test_prepare_ramdisk() + def test_prepare_ramdisk_glance_image_rescuing(self): + self.node.provision_state = states.RESCUING + self.node.save() + self._test_prepare_ramdisk(mode='rescue') + def test_prepare_ramdisk_glance_image_cleaning(self): self.node.provision_state = states.CLEANING self.node.save() self._test_prepare_ramdisk() - @mock.patch.object(irmc_boot, '_setup_deploy_iso', spec_set=True, + @mock.patch.object(irmc_boot, '_setup_vmedia', spec_set=True, autospec=True) def test_prepare_ramdisk_not_deploying_not_cleaning(self, mock_is_image): """Ensure deploy ops are blocked when not deploying and not cleaning""" @@ -1037,7 +1112,7 @@ class IRMCVirtualMediaBootTestCase(db_base.DbTestCase): task.driver.boot.clean_up_instance(task) _remove_share_file_mock.assert_called_once_with( - irmc_boot._get_boot_iso_name(task.node)) + irmc_boot._get_iso_name(task.node, label='boot')) self.assertNotIn('irmc_boot_iso', task.node.driver_internal_info) self.assertNotIn('root_uuid_or_disk_id', @@ -1208,6 +1283,35 @@ class IRMCVirtualMediaBootTestCase(db_base.DbTestCase): self.assertFalse(mock_set_secure_boot_mode.called) mock_cleanup_vmedia_boot.assert_called_once_with(task) + @mock.patch.object(os.path, 'isfile', return_value=True, + autospec=True) + def test_validate_rescue(self, mock_isfile): + driver_info = self.node.driver_info + driver_info['irmc_rescue_iso'] = 'rescue.iso' + self.node.driver_info = driver_info + self.node.save() + with task_manager.acquire(self.context, self.node.uuid) as task: + task.driver.boot.validate_rescue(task) + + def test_validate_rescue_no_rescue_ramdisk(self): + with task_manager.acquire(self.context, self.node.uuid) as task: + self.assertRaisesRegex(exception.MissingParameterValue, + 'Missing.*irmc_rescue_iso', + task.driver.boot.validate_rescue, task) + + @mock.patch.object(os.path, 'isfile', return_value=False, + autospec=True) + def test_validate_rescue_ramdisk_not_exist(self, mock_isfile): + driver_info = self.node.driver_info + driver_info['irmc_rescue_iso'] = 'rescue.iso' + self.node.driver_info = driver_info + self.node.save() + with task_manager.acquire(self.context, self.node.uuid) as task: + self.assertRaisesRegex(exception.InvalidParameterValue, + 'Rescue ISO file, .*' + 'not found for node: .*', + task.driver.boot.validate_rescue, task) + class IRMCPXEBootTestCase(db_base.DbTestCase): diff --git a/ironic/tests/unit/drivers/test_irmc.py b/ironic/tests/unit/drivers/test_irmc.py index 6970b9f2a..47e67263e 100644 --- a/ironic/tests/unit/drivers/test_irmc.py +++ b/ironic/tests/unit/drivers/test_irmc.py @@ -128,7 +128,8 @@ class IRMCHardwareTestCase(db_base.DbTestCase): enabled_inspect_interfaces=['irmc'], enabled_management_interfaces=['irmc'], enabled_power_interfaces=['irmc'], - enabled_raid_interfaces=['no-raid', 'agent']) + enabled_raid_interfaces=['no-raid', 'agent'], + enabled_rescue_interfaces=['no-rescue', 'agent']) def test_default_interfaces(self): node = obj_utils.create_test_node(self.context, driver='irmc') @@ -147,6 +148,8 @@ class IRMCHardwareTestCase(db_base.DbTestCase): irmc.power.IRMCPower) self.assertIsInstance(task.driver.raid, noop.NoRAID) + self.assertIsInstance(task.driver.rescue, + noop.NoRescue) def test_override_with_inspector(self): self.config(enabled_inspect_interfaces=['inspector', 'irmc']) @@ -170,3 +173,29 @@ class IRMCHardwareTestCase(db_base.DbTestCase): irmc.power.IRMCPower) self.assertIsInstance(task.driver.raid, agent.AgentRAID) + self.assertIsInstance(task.driver.rescue, + noop.NoRescue) + + def test_override_with_agent_rescue(self): + node = obj_utils.create_test_node( + self.context, driver='irmc', + deploy_interface='direct', + rescue_interface='agent', + raid_interface='agent') + with task_manager.acquire(self.context, node.id) as task: + self.assertIsInstance(task.driver.boot, + irmc.boot.IRMCVirtualMediaBoot) + self.assertIsInstance(task.driver.console, + ipmitool.IPMISocatConsole) + self.assertIsInstance(task.driver.deploy, + agent.AgentDeploy) + self.assertIsInstance(task.driver.inspect, + irmc.inspect.IRMCInspect) + self.assertIsInstance(task.driver.management, + irmc.management.IRMCManagement) + self.assertIsInstance(task.driver.power, + irmc.power.IRMCPower) + self.assertIsInstance(task.driver.raid, + agent.AgentRAID) + self.assertIsInstance(task.driver.rescue, + agent.AgentRescue) diff --git a/releasenotes/notes/rescue-interface-for-irmc-hardware-type-17e38197849748e0.yaml b/releasenotes/notes/rescue-interface-for-irmc-hardware-type-17e38197849748e0.yaml new file mode 100644 index 000000000..652262b27 --- /dev/null +++ b/releasenotes/notes/rescue-interface-for-irmc-hardware-type-17e38197849748e0.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Adds support for rescue interface ``agent`` for ``irmc`` hardware type + when corresponding boot interface is ``irmc-virtual-media``. + The supported values of rescue interface for ``irmc`` hardware type + are ``agent`` and ``no-rescue``. The default value is ``no-rescue``.
\ No newline at end of file |