From 217edfeca57b0c97ba421e153d698cb6ff1d7531 Mon Sep 17 00:00:00 2001 From: Yolanda Robla Date: Wed, 19 Sep 2018 17:19:47 +0200 Subject: [PATCH] Accepts option for conv flags in dd command When using dd command, it may be needed to specify conversion options, such as sparse for optimization. This change allows to accept this option, in preparation for ironic to sent it when enabled on configuration. Change-Id: I166abbf513557a836de3cce4fc9ea6b7fedbb562 Story: #2003755 Task: #26443 --- ironic_lib/disk_utils.py | 23 ++++++++++----- ironic_lib/tests/test_disk_utils.py | 43 +++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 12 deletions(-) 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):