diff --git a/ironic_lib/disk_utils.py b/ironic_lib/disk_utils.py index 3e8f51c8..290155d5 100644 --- a/ironic_lib/disk_utils.py +++ b/ironic_lib/disk_utils.py @@ -313,9 +313,15 @@ def is_block_device(dev): raise exception.InstanceDeployFailure(msg) -def dd(src, dst): +def dd(src, dst, conv_flags=None): """Execute dd from src to dst.""" - utils.dd(src, dst, 'bs=%s' % CONF.disk_utils.dd_block_size, 'oflag=direct') + if conv_flags: + extra_args = ['conv=%s' % conv_flags] + else: + extra_args = [] + + utils.dd(src, dst, 'bs=%s' % CONF.disk_utils.dd_block_size, 'oflag=direct', + *extra_args) def qemu_img_info(path): @@ -335,10 +341,10 @@ def convert_image(source, dest, out_format, run_as_root=False): utils.execute(*cmd, run_as_root=run_as_root, prlimit=QEMU_IMG_LIMITS) -def populate_image(src, dst): +def populate_image(src, dst, conv_flags=None): data = qemu_img_info(src) if data.file_format == 'raw': - dd(src, dst) + dd(src, dst, conv_flags=conv_flags) else: convert_image(src, dst, 'raw', True) @@ -485,7 +491,7 @@ def _get_configdrive(configdrive, node_uuid, tempdir=None): def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path, node_uuid, preserve_ephemeral=False, configdrive=None, boot_option="netboot", boot_mode="bios", - tempdir=None, disk_label=None, cpu_arch=""): + tempdir=None, disk_label=None, cpu_arch="", conv_flags=None): """Create partitions and copy an image to the root partition. :param dev: Path for the device to work on. @@ -513,6 +519,9 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, steps will be taken. This default should be used for x86_64. When set to ppc64*, architecture specific steps are taken for booting a partition image locally. + :param conv_flags: Flags that need to be sent to the dd command, to control + the conversion of the original file when copying to the host. It can + contain several options separated by commas. :returns: a dictionary containing the following keys: 'root uuid': UUID of root partition 'efi system partition uuid': UUID of the uefi system partition @@ -575,7 +584,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, if configdrive_part: # Copy the configdrive content to the configdrive partition - dd(configdrive_file, configdrive_part) + dd(configdrive_file, configdrive_part, conv_flags=conv_flags) LOG.info("Configdrive for node %(node)s successfully copied " "onto partition %(partition)s", {'node': node_uuid, 'partition': configdrive_part}) @@ -586,7 +595,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, if configdrive_file: utils.unlink_without_raise(configdrive_file) - populate_image(image_path, root_part) + populate_image(image_path, root_part, conv_flags=conv_flags) LOG.info("Image for %(node)s successfully populated", {'node': node_uuid}) diff --git a/ironic_lib/tests/test_disk_utils.py b/ironic_lib/tests/test_disk_utils.py index 508bf33b..d1f20e70 100644 --- a/ironic_lib/tests/test_disk_utils.py +++ b/ironic_lib/tests/test_disk_utils.py @@ -202,7 +202,8 @@ class WorkOnDiskTestCase(base.IronicLibTestCase): @mock.patch.object(utils, 'mkfs', lambda fs, path, label=None: None) @mock.patch.object(disk_utils, 'block_uuid', lambda p: 'uuid') - @mock.patch.object(disk_utils, 'populate_image', lambda *_: None) + @mock.patch.object(disk_utils, 'populate_image', lambda image_path, + root_path, conv_flags=None: None) def test_gpt_disk_label(self): ephemeral_part = '/dev/fake-part1' swap_part = '/dev/fake-part2' @@ -220,7 +221,7 @@ class WorkOnDiskTestCase(base.IronicLibTestCase): disk_utils.work_on_disk(self.dev, self.root_mb, self.swap_mb, ephemeral_mb, ephemeral_format, self.image_path, self.node_uuid, - disk_label='gpt') + disk_label='gpt', conv_flags=None) self.assertEqual(self.mock_ibd.call_args_list, calls) self.mock_mp.assert_called_once_with(self.dev, self.root_mb, self.swap_mb, ephemeral_mb, @@ -263,7 +264,7 @@ class WorkOnDiskTestCase(base.IronicLibTestCase): mock_mkfs.assert_called_once_with(fs='vfat', path=efi_part, label='efi-part') mock_populate_image.assert_called_once_with(self.image_path, - root_part) + root_part, conv_flags=None) mock_block_uuid.assert_any_call(root_part) mock_block_uuid.assert_any_call(efi_part) @@ -309,7 +310,7 @@ class WorkOnDiskTestCase(base.IronicLibTestCase): self.mock_mp.return_value = {'PReP Boot partition': prep_part, 'root': root_part} - self.mock_ibd.return_value = True + self.mock_ibd.return_vaue = True calls = [mock.call(root_part), mock.call(prep_part)] disk_utils.work_on_disk(self.dev, self.root_mb, @@ -328,6 +329,30 @@ class WorkOnDiskTestCase(base.IronicLibTestCase): cpu_arch="ppc64le") self.assertFalse(mock_mkfs.called) + @mock.patch.object(disk_utils, 'block_uuid', autospec=True) + @mock.patch.object(disk_utils, 'populate_image', autospec=True) + @mock.patch.object(utils, 'mkfs', autospec=True) + def test_convert_to_sparse(self, mock_mkfs, mock_populate_image, + mock_block_uuid): + ephemeral_part = '/dev/fake-part1' + swap_part = '/dev/fake-part2' + root_part = '/dev/fake-part3' + ephemeral_mb = 256 + ephemeral_format = 'exttest' + + self.mock_mp.return_value = {'ephemeral': ephemeral_part, + 'swap': swap_part, + 'root': root_part} + self.mock_ibd.return_value = True + disk_utils.work_on_disk(self.dev, self.root_mb, + self.swap_mb, ephemeral_mb, ephemeral_format, + self.image_path, self.node_uuid, + disk_label='gpt', conv_flags='sparse') + + mock_populate_image.assert_called_once_with(self.image_path, + root_part, + conv_flags='sparse') + @mock.patch.object(utils, 'execute', autospec=True) class MakePartitionsTestCase(base.IronicLibTestCase): @@ -612,7 +637,15 @@ class PopulateImageTestCase(base.IronicLibTestCase): type(mock_qinfo.return_value).file_format = mock.PropertyMock( return_value='raw') disk_utils.populate_image('src', 'dst') - mock_dd.assert_called_once_with('src', 'dst') + mock_dd.assert_called_once_with('src', 'dst', conv_flags=None) + self.assertFalse(mock_cg.called) + + def test_populate_raw_image_with_convert(self, mock_cg, mock_qinfo, + mock_dd): + type(mock_qinfo.return_value).file_format = mock.PropertyMock( + return_value='raw') + disk_utils.populate_image('src', 'dst', conv_flags='sparse') + mock_dd.assert_called_once_with('src', 'dst', conv_flags='sparse') self.assertFalse(mock_cg.called) def test_populate_qcow2_image(self, mock_cg, mock_qinfo, mock_dd):