def test_commit_with_device_is_always_busy(self, mock_utils_exc, mock_disk_partitioner_exec): CONF.set_override('check_device_interval', 0, group='disk_partitioner') dp = disk_partitioner.DiskPartitioner('/dev/fake') fake_parts = [(1, { 'boot_flag': None, 'extra_flags': None, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 }), (2, { 'boot_flag': 'boot', 'extra_flags': None, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 })] with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts # Test as if the 'busybox' version of 'fuser' which does not have # stderr output mock_utils_exc.return_value = ("10000 10001", '') self.assertRaises(exception.InstanceDeployFailure, dp.commit) mock_disk_partitioner_exec.assert_called_once_with( mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') mock_utils_exc.assert_called_with('fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) self.assertEqual(20, mock_utils_exc.call_count)
def test_commit_with_device_disconnected(self, mock_utils_exc, mock_disk_partitioner_exec): CONF.set_override('check_device_interval', 0, group='disk_partitioner') dp = disk_partitioner.DiskPartitioner('/dev/fake') fake_parts = [(1, { 'boot_flag': None, 'extra_flags': None, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 }), (2, { 'boot_flag': 'boot', 'extra_flags': None, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 })] with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.return_value = ('', "Specified filename /dev/fake" " does not exist.") self.assertRaises(exception.InstanceDeployFailure, dp.commit) mock_disk_partitioner_exec.assert_called_once_with( mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') mock_utils_exc.assert_called_with('fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) self.assertEqual(20, mock_utils_exc.call_count)
def test_commit(self, mock_utils_exc, mock_disk_partitioner_exec): dp = disk_partitioner.DiskPartitioner('/dev/fake') fake_parts = [(1, { 'bootable': False, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 }), (2, { 'bootable': True, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 })] with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.return_value = (None, None) dp.commit() mock_disk_partitioner_exec.assert_called_once_with( mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') mock_utils_exc.assert_called_once_with('fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1])
def test_commit_with_device_is_busy_once(self, mock_utils_exc, mock_disk_partitioner_exec): CONF.set_override('check_device_interval', 0, group='disk_partitioner') dp = disk_partitioner.DiskPartitioner('/dev/fake') fake_parts = [(1, { 'boot_flag': None, 'extra_flags': None, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 }), (2, { 'boot_flag': 'boot', 'extra_flags': None, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 })] # Test as if the 'psmisc' version of 'fuser' which has stderr output fuser_outputs = iter([(" 10000 10001", '/dev/fake:\n'), ('', '')]) with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.side_effect = fuser_outputs dp.commit() mock_disk_partitioner_exec.assert_called_once_with( mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') mock_utils_exc.assert_called_with('fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) self.assertEqual(2, mock_utils_exc.call_count)
def test_commit_with_device_is_busy_once(self, mock_utils_exc, mock_disk_partitioner_exec): dp = disk_partitioner.DiskPartitioner('/dev/fake') fake_parts = [(1, { 'bootable': False, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 }), (2, { 'bootable': True, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 })] fuser_outputs = iter([("/dev/fake: 10000 10001", None), (None, None)]) with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.side_effect = fuser_outputs dp.commit() mock_disk_partitioner_exec.assert_called_once_with( mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') mock_utils_exc.assert_called_with('fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) self.assertEqual(2, mock_utils_exc.call_count)
def test_add_partition(self): dp = disk_partitioner.DiskPartitioner('/dev/fake') dp.add_partition(1024) dp.add_partition(512, fs_type='linux-swap') dp.add_partition(2048, bootable=True) expected = [(1, { 'bootable': False, 'fs_type': '', 'type': 'primary', 'size': 1024 }), (2, { 'bootable': False, 'fs_type': 'linux-swap', 'type': 'primary', 'size': 512 }), (3, { 'bootable': True, 'fs_type': '', 'type': 'primary', 'size': 2048 })] partitions = [(n, p) for n, p in dp.get_partitions()] self.assertThat(partitions, HasLength(3)) self.assertEqual(expected, partitions)
def test_commit_with_device_is_always_busy(self, mock_utils_exc, mock_disk_partitioner_exec): dp = disk_partitioner.DiskPartitioner('/dev/fake') fake_parts = [(1, { 'bootable': False, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 }), (2, { 'bootable': True, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 })] with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.return_value = ("/dev/fake: 10000 10001", None) self.assertRaises(exception.InstanceDeployFailure, dp.commit) mock_disk_partitioner_exec.assert_called_once_with( mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') mock_utils_exc.assert_called_with('fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) self.assertEqual(20, mock_utils_exc.call_count)
def test_add_partition(self): dp = disk_partitioner.DiskPartitioner('/dev/fake') dp.add_partition(1024) dp.add_partition(512, fs_type='linux-swap') dp.add_partition(2048, boot_flag='boot') dp.add_partition(2048, boot_flag='bios_grub') expected = [(1, {'boot_flag': None, 'extra_flags': None, 'fs_type': '', 'type': 'primary', 'size': 1024}), (2, {'boot_flag': None, 'extra_flags': None, 'fs_type': 'linux-swap', 'type': 'primary', 'size': 512}), (3, {'boot_flag': 'boot', 'extra_flags': None, 'fs_type': '', 'type': 'primary', 'size': 2048}), (4, {'boot_flag': 'bios_grub', 'extra_flags': None, 'fs_type': '', 'type': 'primary', 'size': 2048})] partitions = [(n, p) for n, p in dp.get_partitions()] self.assertThat(partitions, HasLength(4)) self.assertEqual(expected, partitions)
def test_commit(self, mock_utils_exc, mock_disk_partitioner_exec): dp = disk_partitioner.DiskPartitioner('/dev/fake') fake_parts = [(1, { 'boot_flag': None, 'extra_flags': None, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 }), (2, { 'boot_flag': 'boot', 'extra_flags': None, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 }), (3, { 'boot_flag': 'bios_grub', 'extra_flags': None, 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 }), (4, { 'boot_flag': 'boot', 'extra_flags': ['prep', 'fake-flag'], 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1 })] with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.return_value = ('', '') dp.commit() mock_disk_partitioner_exec.assert_called_once_with( mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on', 'mkpart', 'fake-type', 'fake-fs-type', '3', '4', 'set', '3', 'bios_grub', 'on', 'mkpart', 'fake-type', 'fake-fs-type', '4', '5', 'set', '4', 'boot', 'on', 'set', '4', 'prep', 'on', 'set', '4', 'fake-flag', 'on') mock_utils_exc.assert_called_once_with('fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1])
def make_partitions(dev, root_mb, swap_mb, ephemeral_mb, configdrive_mb, node_uuid, commit=True, boot_option="netboot", boot_mode="bios", disk_label=None, cpu_arch=""): """Partition the disk device. Create partitions for root, swap, ephemeral and configdrive on a disk device. :param dev: Path for the device to work on. :param root_mb: Size of the root partition in mebibytes (MiB). :param swap_mb: Size of the swap partition in mebibytes (MiB). If 0, no partition will be created. :param ephemeral_mb: Size of the ephemeral partition in mebibytes (MiB). If 0, no partition will be created. :param configdrive_mb: Size of the configdrive partition in mebibytes (MiB). If 0, no partition will be created. :param commit: True/False. Default for this setting is True. If False partitions will not be written to disk. :param boot_option: Can be "local" or "netboot". "netboot" by default. :param boot_mode: Can be "bios" or "uefi". "bios" by default. :param node_uuid: Node's uuid. Used for logging. :param disk_label: The disk label to be used when creating the partition table. Valid values are: "msdos", "gpt" or None; If None Ironic will figure it out according to the boot_mode parameter. :param cpu_arch: Architecture of the node the disk device belongs to. When using the default value of None, no architecture specific 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. :returns: A dictionary containing the partition type as Key and partition path as Value for the partitions created by this method. """ LOG.debug("Starting to partition the disk device: %(dev)s " "for node %(node)s", {'dev': dev, 'node': node_uuid}) # the actual device names in the baremetal are like /dev/sda, /dev/sdb etc. # While for the iSCSI device, the naming convention has a format which has # iqn also embedded in it. # When this function is called by ironic-conductor, the iSCSI device name # should be appended by "part%d". While on the baremetal, it should name # the device partitions as /dev/sda1 and not /dev/sda-part1. if is_iscsi_device(dev, node_uuid): part_template = dev + '-part%d' elif is_nvme_device(dev): part_template = dev + 'p%d' else: part_template = dev + '%d' part_dict = {} if disk_label is None: disk_label = 'gpt' if boot_mode == 'uefi' else 'msdos' dp = disk_partitioner.DiskPartitioner(dev, disk_label=disk_label) # For uefi localboot, switch partition table to gpt and create the efi # system partition as the first partition. if boot_mode == "uefi" and boot_option == "local": part_num = dp.add_partition(CONF.disk_utils.efi_system_partition_size, fs_type='fat32', boot_flag='boot') part_dict['efi system partition'] = part_template % part_num if (boot_mode == "bios" and boot_option == "local" and disk_label == "gpt" and not cpu_arch.startswith('ppc64')): part_num = dp.add_partition(CONF.disk_utils.bios_boot_partition_size, boot_flag='bios_grub') part_dict['BIOS Boot partition'] = part_template % part_num # NOTE(mjturek): With ppc64* nodes, partition images are expected to have # a PrEP partition at the start of the disk. This is an 8 MiB partition # with the boot and prep flags set. The bootloader should be installed # here. if (cpu_arch.startswith("ppc64") and boot_mode == "bios" and boot_option == "local"): LOG.debug("Add PReP boot partition (8 MB) to device: " "%(dev)s for node %(node)s", {'dev': dev, 'node': node_uuid}) boot_flag = 'boot' if disk_label == 'msdos' else None part_num = dp.add_partition(8, part_type='primary', boot_flag=boot_flag, extra_flags=['prep']) part_dict['PReP Boot partition'] = part_template % part_num if ephemeral_mb: LOG.debug("Add ephemeral partition (%(size)d MB) to device: %(dev)s " "for node %(node)s", {'dev': dev, 'size': ephemeral_mb, 'node': node_uuid}) part_num = dp.add_partition(ephemeral_mb) part_dict['ephemeral'] = part_template % part_num if swap_mb: LOG.debug("Add Swap partition (%(size)d MB) to device: %(dev)s " "for node %(node)s", {'dev': dev, 'size': swap_mb, 'node': node_uuid}) part_num = dp.add_partition(swap_mb, fs_type='linux-swap') part_dict['swap'] = part_template % part_num if configdrive_mb: LOG.debug("Add config drive partition (%(size)d MB) to device: " "%(dev)s for node %(node)s", {'dev': dev, 'size': configdrive_mb, 'node': node_uuid}) part_num = dp.add_partition(configdrive_mb) part_dict['configdrive'] = part_template % part_num # NOTE(lucasagomes): Make the root partition the last partition. This # enables tools like cloud-init's growroot utility to expand the root # partition until the end of the disk. LOG.debug("Add root partition (%(size)d MB) to device: %(dev)s " "for node %(node)s", {'dev': dev, 'size': root_mb, 'node': node_uuid}) boot_val = 'boot' if (not cpu_arch.startswith("ppc64") and boot_mode == "bios" and boot_option == "local" and disk_label == "msdos") else None part_num = dp.add_partition(root_mb, boot_flag=boot_val) part_dict['root'] = part_template % part_num if commit: # write to the disk dp.commit() return part_dict
def make_partitions(dev, root_mb, swap_mb, ephemeral_mb, configdrive_mb, node_uuid, commit=True, boot_option="netboot", boot_mode="bios", disk_label=None): """Partition the disk device. Create partitions for root, swap, ephemeral and configdrive on a disk device. :param root_mb: Size of the root partition in mebibytes (MiB). :param swap_mb: Size of the swap partition in mebibytes (MiB). If 0, no partition will be created. :param ephemeral_mb: Size of the ephemeral partition in mebibytes (MiB). If 0, no partition will be created. :param configdrive_mb: Size of the configdrive partition in mebibytes (MiB). If 0, no partition will be created. :param commit: True/False. Default for this setting is True. If False partitions will not be written to disk. :param boot_option: Can be "local" or "netboot". "netboot" by default. :param boot_mode: Can be "bios" or "uefi". "bios" by default. :param node_uuid: Node's uuid. Used for logging. :param disk_label: The disk label to be used when creating the partition table. Valid values are: "msdos", "gpt" or None; If None Ironic will figure it out according to the boot_mode parameter. :returns: A dictionary containing the partition type as Key and partition path as Value for the partitions created by this method. """ LOG.debug( "Starting to partition the disk device: %(dev)s " "for node %(node)s", { 'dev': dev, 'node': node_uuid }) # the actual device names in the baremetal are like /dev/sda, /dev/sdb etc. # While for the iSCSI device, the naming convention has a format which has # iqn also embedded in it. # When this function is called by ironic-conductor, the iSCSI device name # should be appended by "part%d". While on the baremetal, it should name # the device partitions as /dev/sda1 and not /dev/sda-part1. if is_iscsi_device(dev, node_uuid): part_template = dev + '-part%d' else: part_template = dev + '%d' part_dict = {} if disk_label is None: disk_label = 'gpt' if boot_mode == 'uefi' else 'msdos' dp = disk_partitioner.DiskPartitioner(dev, disk_label=disk_label) # For uefi localboot, switch partition table to gpt and create the efi # system partition as the first partition. if boot_mode == "uefi" and boot_option == "local": part_num = dp.add_partition(CONF.disk_utils.efi_system_partition_size, fs_type='fat32', bootable=True) part_dict['efi system partition'] = part_template % part_num if ephemeral_mb: LOG.debug( "Add ephemeral partition (%(size)d MB) to device: %(dev)s " "for node %(node)s", { 'dev': dev, 'size': ephemeral_mb, 'node': node_uuid }) part_num = dp.add_partition(ephemeral_mb) part_dict['ephemeral'] = part_template % part_num if swap_mb: LOG.debug( "Add Swap partition (%(size)d MB) to device: %(dev)s " "for node %(node)s", { 'dev': dev, 'size': swap_mb, 'node': node_uuid }) part_num = dp.add_partition(swap_mb, fs_type='linux-swap') part_dict['swap'] = part_template % part_num if configdrive_mb: LOG.debug( "Add config drive partition (%(size)d MB) to device: " "%(dev)s for node %(node)s", { 'dev': dev, 'size': configdrive_mb, 'node': node_uuid }) part_num = dp.add_partition(configdrive_mb) part_dict['configdrive'] = part_template % part_num # NOTE(lucasagomes): Make the root partition the last partition. This # enables tools like cloud-init's growroot utility to expand the root # partition until the end of the disk. LOG.debug( "Add root partition (%(size)d MB) to device: %(dev)s " "for node %(node)s", { 'dev': dev, 'size': root_mb, 'node': node_uuid }) part_num = dp.add_partition(root_mb, bootable=(boot_option == "local" and boot_mode == "bios")) part_dict['root'] = part_template % part_num if commit: # write to the disk dp.commit() return part_dict
def make_partitions(dev, root_mb, swap_mb, ephemeral_mb, configdrive_mb, commit=True, boot_option="netboot", boot_mode="bios"): """Partition the disk device. Create partitions for root, swap, ephemeral and configdrive on a disk device. :param root_mb: Size of the root partition in mebibytes (MiB). :param swap_mb: Size of the swap partition in mebibytes (MiB). If 0, no partition will be created. :param ephemeral_mb: Size of the ephemeral partition in mebibytes (MiB). If 0, no partition will be created. :param configdrive_mb: Size of the configdrive partition in mebibytes (MiB). If 0, no partition will be created. :param commit: True/False. Default for this setting is True. If False partitions will not be written to disk. :param boot_option: Can be "local" or "netboot". "netboot" by default. :param boot_mode: Can be "bios" or "uefi". "bios" by default. :returns: A dictionary containing the partition type as Key and partition path as Value for the partitions created by this method. """ LOG.debug("Starting to partition the disk device: %(dev)s", {'dev': dev}) if is_iscsi_device(dev): part_template = dev + '-part%d' else: part_template = dev + '%d' part_dict = {} # For uefi localboot, switch partition table to gpt and create the efi # system partition as the first partition. if boot_mode == "uefi" and boot_option == "local": dp = disk_partitioner.DiskPartitioner(dev, disk_label="gpt") part_num = dp.add_partition(CONF.disk_utils.efi_system_partition_size, fs_type='fat32', bootable=True) part_dict['efi system partition'] = part_template % part_num else: dp = disk_partitioner.DiskPartitioner(dev) if ephemeral_mb: LOG.debug("Add ephemeral partition (%(size)d MB) to device: %(dev)s", { 'dev': dev, 'size': ephemeral_mb }) part_num = dp.add_partition(ephemeral_mb) part_dict['ephemeral'] = part_template % part_num if swap_mb: LOG.debug("Add Swap partition (%(size)d MB) to device: %(dev)s", { 'dev': dev, 'size': swap_mb }) part_num = dp.add_partition(swap_mb, fs_type='linux-swap') part_dict['swap'] = part_template % part_num if configdrive_mb: LOG.debug( "Add config drive partition (%(size)d MB) to device: " "%(dev)s", { 'dev': dev, 'size': configdrive_mb }) part_num = dp.add_partition(configdrive_mb) part_dict['configdrive'] = part_template % part_num # NOTE(lucasagomes): Make the root partition the last partition. This # enables tools like cloud-init's growroot utility to expand the root # partition until the end of the disk. LOG.debug("Add root partition (%(size)d MB) to device: %(dev)s", { 'dev': dev, 'size': root_mb }) part_num = dp.add_partition(root_mb, bootable=(boot_option == "local" and boot_mode == "bios")) part_dict['root'] = part_template % part_num if commit: # write to the disk dp.commit() return part_dict