diff --git a/doc/source/install/standalone.rst b/doc/source/install/standalone.rst index 8c80eb78a8..d747eb059d 100644 --- a/doc/source/install/standalone.rst +++ b/doc/source/install/standalone.rst @@ -300,6 +300,13 @@ Populating instance_info * ``ilo_boot_iso``, ``image_source``, ``root_gb`` under ``instance_info``. +#. For software RAID with whole-disk images, the root UUID of the root + partition has to be provided so that the bootloader can be correctly + installed:: + + baremetal node set $NODE_UUID \ + --instance-info image_rootfs_uuid= + Deployment ---------- diff --git a/ironic/drivers/modules/agent_base.py b/ironic/drivers/modules/agent_base.py index ee1af290d4..43d4bb18b5 100644 --- a/ironic/drivers/modules/agent_base.py +++ b/ironic/drivers/modules/agent_base.py @@ -1333,26 +1333,29 @@ class AgentDeployMixin(HeartbeatMixin, AgentOobStepsMixin): # image's metadata (via Glance). Fall back to the driver internal # info in case it is not available (e.g. not set or there's no Glance). if software_raid: - image_source = node.instance_info.get('image_source') - try: - context = task.context - context.is_admin = True - glance = image_service.GlanceImageService( - context=context) - image_info = glance.show(image_source) - image_properties = image_info.get('properties') - root_uuid = image_properties['rootfs_uuid'] - LOG.debug('Got rootfs_uuid from Glance: %s ' - '(node %s)', root_uuid, node.uuid) - except Exception as e: - LOG.warning('Could not get \'rootfs_uuid\' property for ' - 'image %(image)s from Glance for node %(node)s. ' - '%(cls)s: %(error)s.', - {'image': image_source, 'node': node.uuid, - 'cls': e.__class__.__name__, 'error': e}) - root_uuid = internal_info.get('root_uuid_or_disk_id') - LOG.debug('Got rootfs_uuid from driver internal info: ' - '%s (node %s)', root_uuid, node.uuid) + root_uuid = node.instance_info.get('image_rootfs_uuid') + if not root_uuid: + image_source = node.instance_info.get('image_source') + try: + context = task.context + context.is_admin = True + glance = image_service.GlanceImageService( + context=context) + image_info = glance.show(image_source) + image_properties = image_info.get('properties') + root_uuid = image_properties['rootfs_uuid'] + LOG.debug('Got rootfs_uuid from Glance: %s ' + '(node %s)', root_uuid, node.uuid) + except Exception as e: + LOG.warning( + 'Could not get \'rootfs_uuid\' property for ' + 'image %(image)s from Glance for node %(node)s. ' + '%(cls)s: %(error)s.', + {'image': image_source, 'node': node.uuid, + 'cls': e.__class__.__name__, 'error': e}) + root_uuid = internal_info.get('root_uuid_or_disk_id') + LOG.debug('Got rootfs_uuid from driver internal info: ' + '%s (node %s)', root_uuid, node.uuid) # For whole disk images it is not necessary that the root_uuid # be provided since the bootloaders on the disk will be used diff --git a/ironic/tests/unit/drivers/modules/test_agent_base.py b/ironic/tests/unit/drivers/modules/test_agent_base.py index dac8e2fca2..97abaf1d98 100644 --- a/ironic/tests/unit/drivers/modules/test_agent_base.py +++ b/ironic/tests/unit/drivers/modules/test_agent_base.py @@ -1317,6 +1317,8 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): def test_configure_local_boot_on_software_raid( self, install_bootloader_mock, try_set_boot_device_mock, GlanceImageService_mock): + image = GlanceImageService_mock.return_value.show.return_value + image.get.return_value = {'rootfs_uuid': 'rootfs'} with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: task.node.driver_internal_info['is_whole_disk_image'] = True @@ -1336,7 +1338,50 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): } self.deploy.configure_local_boot(task) self.assertTrue(GlanceImageService_mock.called) - self.assertTrue(install_bootloader_mock.called) + install_bootloader_mock.assert_called_once_with( + mock.ANY, task.node, + root_uuid='rootfs', + efi_system_part_uuid=None, + prep_boot_part_uuid=None, + target_boot_mode='bios', + software_raid=True) + try_set_boot_device_mock.assert_called_once_with( + task, boot_devices.DISK, persistent=True) + + @mock.patch.object(image_service, 'GlanceImageService', autospec=True) + @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True) + @mock.patch.object(agent_client.AgentClient, 'install_bootloader', + autospec=True) + def test_configure_local_boot_on_software_raid_explicit_uuid( + self, install_bootloader_mock, try_set_boot_device_mock, + GlanceImageService_mock): + with task_manager.acquire(self.context, self.node['uuid'], + shared=False) as task: + task.node.driver_internal_info['is_whole_disk_image'] = True + task.node.instance_info['image_rootfs_uuid'] = 'rootfs' + task.node.target_raid_config = { + "logical_disks": [ + { + "size_gb": 100, + "raid_level": "1", + "controller": "software", + }, + { + "size_gb": 'MAX', + "raid_level": "0", + "controller": "software", + } + ] + } + self.deploy.configure_local_boot(task) + self.assertFalse(GlanceImageService_mock.called) + install_bootloader_mock.assert_called_once_with( + mock.ANY, task.node, + root_uuid='rootfs', + efi_system_part_uuid=None, + prep_boot_part_uuid=None, + target_boot_mode='bios', + software_raid=True) try_set_boot_device_mock.assert_called_once_with( task, boot_devices.DISK, persistent=True) diff --git a/releasenotes/notes/image_rootfs_uuid-1ea54ba043d1aeaf.yaml b/releasenotes/notes/image_rootfs_uuid-1ea54ba043d1aeaf.yaml new file mode 100644 index 0000000000..0d916abbcc --- /dev/null +++ b/releasenotes/notes/image_rootfs_uuid-1ea54ba043d1aeaf.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + When deploying a node with software RAID with an image not from Glance, + the new ``instance_info`` field ``image_rootfs_uuid`` can be used to + specify the UUID of the root partition to install the bootloader on.