def test_mount_order_unsorted(self, mock_exec_sudo): # As above, but this is out of order and gets sorted # so that root is mounted first (and skips the mkfs testing). config = self.load_config_file('lvm_tree_partition_ordering.yaml') parsed_graph = config_tree_to_graph(config) state = {} graph, call_order = create_graph(parsed_graph, self.fake_default_config, state) state['filesys'] = { 'mkfs_root': { 'device': '/dev/loopXp1', 'fstype': 'xfs' }, 'mkfs_var': { 'device': '/dev/loopXp2', 'fstype': 'xfs', }, 'mkfs_boot': { 'device': '/dev/loopXp3', 'fstype': 'vfat', }, } for node in call_order: if isinstance(node, MountPointNode): node.create() for node in reversed(call_order): if isinstance(node, MountPointNode): node.umount() # ensure that partitions are mounted in order / -> /boot -> /var self.assertListEqual(state['mount_order'], ['/', '/boot', '/var']) cmd_sequence = [ # mount sequence mock.call(['mkdir', '-p', '/fake/']), mock.call(['mount', '/dev/loopXp1', '/fake/']), mock.call(['mkdir', '-p', '/fake/boot']), mock.call(['mount', '/dev/loopXp3', '/fake/boot']), mock.call(['mkdir', '-p', '/fake/var']), mock.call(['mount', '/dev/loopXp2', '/fake/var']), # umount sequence mock.call(['sync']), mock.call(['fstrim', '--verbose', '/fake/var']), mock.call(['umount', '/fake/var']), mock.call(['sync']), # no trim on vfat /fake/boot mock.call(['umount', '/fake/boot']), mock.call(['sync']), mock.call(['fstrim', '--verbose', '/fake/']), mock.call(['umount', '/fake/']) ] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence)
def test_gpt_efi(self, mock_exec_sudo): # Test the command-sequence for a GPT/EFI partition setup tree = self.load_config_file('gpt_efi.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # Create a fake temp backing file (we check the size of it, # etc). # TODO(ianw): exec_sudo is generically mocked out, thus the # actual creation is mocked out ... but we could do this # without root and use parted to create the partitions on this # for slightly better testing. An exercise for another day... self.tmp_dir = fixtures.TempDir() self.useFixture(self.tmp_dir) self.image_path = os.path.join(self.tmp_dir.path, "image.raw") # should be sparse... image_create(self.image_path, 1024 * 1024 * 1024) logger.debug("Temp image in %s", self.image_path) # Fake state for the loopback device state['blockdev'] = {} state['blockdev']['image0'] = {} state['blockdev']['image0']['image'] = self.image_path state['blockdev']['image0']['device'] = "/dev/loopX" for node in call_order: if isinstance(node, PartitionNode): node.create() # check the parted call looks right parted_cmd = [ 'sgdisk', self.image_path, '-n', '1:0:+8M', '-t', '1:EF00', '-c', '1:ESP', '-n', '2:0:+8M', '-t', '2:EF02', '-c', '2:BSP', '-n', '3:0:+1006M', '-t', '3:8300', '-c', '3:Root Part' ] cmd_sequence = [ mock.call(parted_cmd), mock.call(['sync']), mock.call(['kpartx', '-avs', '/dev/loopX']) ] self.assertEqual(mock_exec_sudo.call_count, len(cmd_sequence)) mock_exec_sudo.assert_has_calls(cmd_sequence) # Check two new partitions appear in state correctly self.assertDictEqual(state['blockdev']['ESP'], {'device': '/dev/mapper/loopXp1'}) self.assertDictEqual(state['blockdev']['BSP'], {'device': '/dev/mapper/loopXp2'}) self.assertDictEqual(state['blockdev']['Root Part'], {'device': '/dev/mapper/loopXp3'})
def test_gpt_efi(self, mock_exec_sudo): # Test the command-sequence for a GPT/EFI partition setup tree = self.load_config_file('gpt_efi.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # Create a fake temp backing file (we check the size of it, # etc). # TODO(ianw): exec_sudo is generically mocked out, thus the # actual creation is mocked out ... but we could do this # without root and use parted to create the partitions on this # for slightly better testing. An exercise for another day... self.tmp_dir = fixtures.TempDir() self.useFixture(self.tmp_dir) self.image_path = os.path.join(self.tmp_dir.path, "image.raw") # should be sparse... image_create(self.image_path, 1024 * 1024 * 1024) logger.debug("Temp image in %s", self.image_path) # Fake state for the loopback device state['blockdev'] = {} state['blockdev']['image0'] = {} state['blockdev']['image0']['image'] = self.image_path state['blockdev']['image0']['device'] = "/dev/loopX" for node in call_order: if isinstance(node, PartitionNode): node.create() # check the parted call looks right parted_cmd = ['sgdisk', self.image_path, '-n', '1:0:+8M', '-t', '1:EF00', '-c', '1:ESP', '-n', '2:0:+8M', '-t', '2:EF02', '-c', '2:BSP', '-n', '3:0:+1006M', '-t', '3:8300', '-c', '3:Root Part'] cmd_sequence = [ mock.call(parted_cmd), mock.call(['sync']), mock.call(['kpartx', '-avs', '/dev/loopX']) ] self.assertEqual(mock_exec_sudo.call_count, len(cmd_sequence)) mock_exec_sudo.assert_has_calls(cmd_sequence) # Check two new partitions appear in state correctly self.assertDictEqual(state['blockdev']['ESP'], {'device': '/dev/mapper/loopXp1'}) self.assertDictEqual(state['blockdev']['BSP'], {'device': '/dev/mapper/loopXp2'}) self.assertDictEqual(state['blockdev']['Root Part'], {'device': '/dev/mapper/loopXp3'})
def test_validate_lvs_type(self, mock_exec_sudo): # Test the command-sequence for a more complicated LVM setup tree = self.load_config_file('lvm_tree_thin_provision.yaml') print(tree) tree[2]['lvm']['lvs'][0]['type'] = 'thin-pol' config = config_tree_to_graph(tree) state = BlockDeviceState() self.assertRaisesRegex( BlockDeviceSetupException, "Unsupported type:thin-pol, supported types: thin, thin-pool", create_graph, config, self.fake_default_config, state)
def cmd_init(self): """Initialize block device setup This initializes the block device setup layer. One major task is to parse and check the configuration, write it down for later examiniation and execution. """ with open(self.params['config'], "rt") as config_fd: self.config = yaml.safe_load(config_fd) logger.debug("Config before merge [%s]", self.config) self.config = config_tree_to_graph(self.config) logger.debug("Config before merge [%s]", self.config) self._merge_rootfs_params() logger.debug("Final config [%s]", self.config) # Write the final config with open(self.config_json_file_name, "wt") as fd: json.dump(self.config, fd) logger.info("Wrote final block device config to [%s]", self.config_json_file_name)
def cmd_init(self): """Initialize block device setup This initializes the block device setup layer. One major task is to parse and check the configuration, write it down for later examiniation and execution. """ with open(self.params['config'], "rt") as config_fd: self.config = yaml.safe_load(config_fd) logger.debug("Config before merge [%s]", self.config) self.config = config_tree_to_graph(self.config) logger.debug("Config before merge [%s]", self.config) self._merge_into_config() logger.debug("Final config [%s]", self.config) # Write the final config with open(self.config_json_file_name, "wt") as fd: json.dump(self.config, fd) logger.info("Wrote final block device config to [%s]", self.config_json_file_name)
def test_lvm_thin_provision(self, mock_exec_sudo): # Test the command-sequence for a more complicated LVM setup tree = self.load_config_file('lvm_tree_thin_provision.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # Fake state for the two PV's specified by this config state['blockdev'] = {} state['blockdev']['root'] = {} state['blockdev']['root']['device'] = '/dev/fake/root' for node in call_order: # XXX: This has not mocked out the "lower" layers of # creating the devices, which we're assuming works OK, nor # the upper layers. if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): # only the LVMNode actually does anything here... node.create() # ensure the sequence of calls correctly setup the devices cmd_sequence = [ # create the pv's on the faked out block devices mock.call(['pvcreate', '/dev/fake/root', '--force']), # create a volume called "vg" out of these two pv's mock.call(['vgcreate', 'vg', '/dev/fake/root', '--force']), mock.call(['lvcreate', '--name', 'lv_thinpool', '--type', 'thin-pool', '-L', '2936012800B', 'vg']), # create a bunch of lv's on vg using the pool mock.call(['lvcreate', '--name', 'lv_root', '--type', 'thin', '--thin-pool', 'lv_thinpool', '-V', '1887436800B', 'vg']), mock.call(['lvcreate', '--name', 'lv_tmp', '--type', 'thin', '--thin-pool', 'lv_thinpool', '-V', '104857600B', 'vg']), mock.call(['lvcreate', '--name', 'lv_var', '--type', 'thin', '--thin-pool', 'lv_thinpool', '-V', '524288000B', 'vg']), mock.call(['lvcreate', '--name', 'lv_log', '--type', 'thin', '--thin-pool', 'lv_thinpool', '-V', '104857600B', 'vg']), mock.call(['lvcreate', '--name', 'lv_audit', '--type', 'thin', '--thin-pool', 'lv_thinpool', '-V', '104857600B', 'vg']), mock.call(['lvcreate', '--name', 'lv_home', '--type', 'thin', '--thin-pool', 'lv_thinpool', '-V', '209715200B', 'vg'])] self.assertEqual(mock_exec_sudo.call_count, len(cmd_sequence)) mock_exec_sudo.assert_has_calls(cmd_sequence) # Ensure the correct LVM state was preserved blockdev_state = { 'root': {'device': '/dev/fake/root'}, 'lv_thinpool': { 'vgs': 'vg', 'size': '2800MiB', 'extents': None, 'opts': None, 'device': '/dev/mapper/vg-lv_thinpool' }, 'lv_root': { 'vgs': 'vg', 'size': '1800MiB', 'extents': None, 'opts': None, 'device': '/dev/mapper/vg-lv_root' }, 'lv_tmp': { 'vgs': 'vg', 'size': '100MiB', 'extents': None, 'opts': None, 'device': '/dev/mapper/vg-lv_tmp' }, 'lv_var': { 'vgs': 'vg', 'size': '500MiB', 'extents': None, 'opts': None, 'device': '/dev/mapper/vg-lv_var' }, 'lv_log': { 'vgs': 'vg', 'size': '100MiB', 'extents': None, 'opts': None, 'device': '/dev/mapper/vg-lv_log' }, 'lv_audit': { 'vgs': 'vg', 'size': '100MiB', 'extents': None, 'opts': None, 'device': '/dev/mapper/vg-lv_audit' }, 'lv_home': { 'vgs': 'vg', 'size': '200MiB', 'extents': None, 'opts': None, 'device': '/dev/mapper/vg-lv_home' } } self.assertDictEqual(state['blockdev'], blockdev_state)
def test_lvm_multiple_partitions(self): # Test the command-sequence for several partitions, one containing # volumes on it tree = self.load_config_file('lvm_tree_multiple_partitions.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # Fake state for the partitions on this config state['blockdev'] = {} state['blockdev']['image0'] = {} state['blockdev']['image0']['device'] = '/dev/fake/image0' state['blockdev']['image0']['image'] = 'image' state['blockdev']['root'] = {} state['blockdev']['root']['device'] = '/dev/fake/root' state['blockdev']['ESP'] = {} state['blockdev']['ESP']['device'] = '/dev/fake/ESP' state['blockdev']['BSP'] = {} state['blockdev']['BSP']['device'] = '/dev/fake/BSP' # # Creation test # # We mock out the following exec_sudo and other related calls # calls for the layers we are testing. exec_sudo_lvm = 'diskimage_builder.block_device.level1.lvm.exec_sudo' exec_sudo_part = ('diskimage_builder.block_device.' 'level1.partitioning.exec_sudo') exec_sudo_loop = ('diskimage_builder.block_device.' 'level0.localloop.exec_sudo') image_create = ('diskimage_builder.block_device.level0.' 'localloop.LocalLoopNode.create') size_of_block = ('diskimage_builder.block_device.level1.' 'partitioning.Partitioning._size_of_block_dev') create_mbr = ('diskimage_builder.block_device.level1.' 'partitioning.Partitioning._create_mbr') manager = mock.MagicMock() with mock.patch(exec_sudo_lvm) as mock_sudo_lvm, \ mock.patch(exec_sudo_part) as mock_sudo_part, \ mock.patch(exec_sudo_loop) as mock_sudo_loop, \ mock.patch(image_create) as mock_image_create, \ mock.patch(size_of_block) as mock_size_of_block, \ mock.patch(create_mbr) as mock_create_mbr: manager.attach_mock(mock_sudo_lvm, 'sudo_lvm') manager.attach_mock(mock_sudo_part, 'sudo_part') manager.attach_mock(mock_sudo_loop, 'sudo_loop') manager.attach_mock(mock_image_create, 'image_create') manager.attach_mock(mock_size_of_block, 'size_of_block') manager.attach_mock(mock_create_mbr, 'create_mbr') for node in call_order: # We're just keeping this to the partition setup and # LVM creation; i.e. skipping mounting, mkfs, etc. if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode, LocalLoopNode, PartitionNode)): node.create() else: logger.debug("Skipping node for test: %s", node) cmd_sequence = [ # create the underlying block device mock.call.image_create(), mock.call.size_of_block('image'), # write out partition table mock.call.create_mbr(), # now mount partitions mock.call.sudo_part(['sync']), mock.call.sudo_part(['kpartx', '-uvs', '/dev/fake/image0']), # now create lvm environment mock.call.sudo_lvm(['pvcreate', '/dev/fake/root', '--force']), mock.call.sudo_lvm( ['vgcreate', 'vg', '/dev/fake/root', '--force']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_root', '-l', '28%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_tmp', '-l', '4%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_var', '-l', '40%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_log', '-l', '23%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_audit', '-l', '4%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_home', '-l', '1%VG', 'vg']), ] manager.assert_has_calls(cmd_sequence) # # Umount/cleanup test # manager = mock.MagicMock() with mock.patch(exec_sudo_lvm) as mock_sudo_lvm, \ mock.patch(exec_sudo_part) as mock_sudo_part, \ mock.patch(exec_sudo_loop) as mock_sudo_loop: manager.attach_mock(mock_sudo_lvm, 'sudo_lvm') manager.attach_mock(mock_sudo_part, 'sudo_part') manager.attach_mock(mock_sudo_loop, 'sudo_loop') def run_it(phase): reverse_order = reversed(call_order) for node in reverse_order: if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode, LocalLoopNode, PartitionNode)): getattr(node, phase)() else: logger.debug("Skipping node for test: %s", node) run_it('umount') run_it('cleanup') cmd_sequence = [ # deactivate LVM first mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_root']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_tmp']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_var']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_log']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_audit']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_home']), mock.call.sudo_lvm(['vgchange', '-an', 'vg']), mock.call.sudo_lvm(['udevadm', 'settle']), # now remove partitions (note has to happen after lvm removal) mock.call.sudo_part(['kpartx', '-d', '/dev/fake/image0']), # now remove loopback device mock.call.sudo_loop(['losetup', '-d', '/dev/fake/image0']), # now final LVM cleanup call mock.call.sudo_lvm(['pvscan', '--cache']), ] manager.assert_has_calls(cmd_sequence)
def test_lvm_tree_to_graph(self): # equivalence of tree-based to graph-based config tree = self.load_config_file('lvm_tree.yaml') graph = self.load_config_file('lvm_graph.yaml') parsed_graph = config_tree_to_graph(tree) self.assertCountEqual(parsed_graph, graph)
def test_lvm_spanned_vg(self): # Test when a volume group spans some partitions tree = self.load_config_file('lvm_tree_spanned_vg.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # XXX: todo; test call_order. Make sure PV's come before, VG; # VG before LV, and that mounts/etc happen afterwards. # Fake state for the two PV's specified by this config state['blockdev'] = {} state['blockdev']['root'] = {} state['blockdev']['root']['device'] = '/dev/fake/root' state['blockdev']['data1'] = {} state['blockdev']['data1']['device'] = '/dev/fake/data1' state['blockdev']['data2'] = {} state['blockdev']['data2']['device'] = '/dev/fake/data2' # We mock patch this ... it's just a little long! exec_sudo = 'diskimage_builder.block_device.level1.lvm.exec_sudo' # # Creation test # with mock.patch(exec_sudo) as mock_exec_sudo: for node in call_order: # XXX: This has not mocked out the "lower" layers of # creating the devices, which we're assuming works OK, nor # the upper layers. if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): # only the LVMNode actually does anything here... node.create() # ensure the sequence of calls correctly setup the devices cmd_sequence = [ # create the pv's on the faked out block devices mock.call(['pvcreate', '/dev/fake/root', '--force']), mock.call(['pvcreate', '/dev/fake/data1', '--force']), mock.call(['pvcreate', '/dev/fake/data2', '--force']), # create a root and a data volume, with the data volume # spanning data1 & data2 mock.call(['vgcreate', 'vg_root', '/dev/fake/root', '--force']), mock.call(['vgcreate', 'vg_data', '/dev/fake/data1', '/dev/fake/data2', '--force']), # create root and data volume mock.call(['lvcreate', '--name', 'lv_root', '-L', '1799356416B', 'vg_root']), mock.call(['lvcreate', '--name', 'lv_data', '-L', '1996488704B', 'vg_data']) ] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence) with mock.patch(exec_sudo) as mock_exec_sudo, \ mock.patch('tempfile.NamedTemporaryFile') as mock_temp, \ mock.patch('os.unlink'): # see above ... tempfiles = [] def new_tempfile(*args, **kwargs): n = '/tmp/files%s' % len(tempfiles) r = mock.Mock() r.configure_mock(name=n) tempfiles.append(n) return r mock_temp.side_effect = new_tempfile def run_it(phase): reverse_order = reversed(call_order) for node in reverse_order: if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): getattr(node, phase)() else: logger.debug("Skipping node for test: %s", node) run_it('umount') run_it('cleanup') cmd_sequence = [ # deactivate lv's mock.call(['lvchange', '-an', '/dev/vg_root/lv_root']), mock.call(['lvchange', '-an', '/dev/vg_data/lv_data']), # deactivate vg's mock.call(['vgchange', '-an', 'vg_root']), mock.call(['vgchange', '-an', 'vg_data']), mock.call(['udevadm', 'settle']), mock.call(['pvscan', '--cache']), ] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence)
def test_lvm_tree_to_graph(self): # equivalence of tree-based to graph-based config tree = self.load_config_file('lvm_tree.yaml') graph = self.load_config_file('lvm_graph.yaml') parsed_graph = config_tree_to_graph(tree) self.assertItemsEqual(parsed_graph, graph)
def test_deep_tree(self): tree = self.load_config_file('deep_tree.yaml') graph = self.load_config_file('deep_graph.yaml') parsed_graph = config_tree_to_graph(tree) self.assertItemsEqual(parsed_graph, graph)
def test_multipart_tree(self): tree = self.load_config_file('multiple_partitions_tree.yaml') graph = self.load_config_file('multiple_partitions_graph.yaml') parsed_graph = config_tree_to_graph(tree) logger.debug(parsed_graph) self.assertItemsEqual(parsed_graph, graph)
def test_graph(self): graph = self.load_config_file('simple_graph.yaml') parsed_graph = config_tree_to_graph(graph) self.assertItemsEqual(parsed_graph, graph)
def test_lvm_multi_pv(self, mock_exec_sudo): # Test the command-sequence for a more complicated LVM setup tree = self.load_config_file('lvm_tree_multiple_pv.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # XXX: todo; test call_order. Make sure PV's come before, VG; # VG before LV, and that mounts/etc happen afterwards. # Fake state for the two PV's specified by this config state['blockdev'] = {} state['blockdev']['root'] = {} state['blockdev']['root']['device'] = '/dev/fake/root' state['blockdev']['data'] = {} state['blockdev']['data']['device'] = '/dev/fake/data' for node in call_order: # XXX: This has not mocked out the "lower" layers of # creating the devices, which we're assuming works OK, nor # the upper layers. if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): # only the LVMNode actually does anything here... node.create() # ensure the sequence of calls correctly setup the devices cmd_sequence = [ # create the pv's on the faked out block devices mock.call(['pvcreate', '/dev/fake/root', '--force']), mock.call(['pvcreate', '/dev/fake/data', '--force']), # create a volume called "vg" out of these two pv's mock.call(['vgcreate', 'vg', '/dev/fake/root', '/dev/fake/data', '--force']), # create a bunch of lv's on vg mock.call(['lvcreate', '--name', 'lv_root', '-L', '1800M', 'vg']), mock.call(['lvcreate', '--name', 'lv_tmp', '-L', '100M', 'vg']), mock.call(['lvcreate', '--name', 'lv_var', '-L', '500M', 'vg']), mock.call(['lvcreate', '--name', 'lv_log', '-L', '100M', 'vg']), mock.call(['lvcreate', '--name', 'lv_audit', '-L', '100M', 'vg']), mock.call(['lvcreate', '--name', 'lv_home', '-L', '200M', 'vg'])] self.assertEqual(mock_exec_sudo.call_count, len(cmd_sequence)) mock_exec_sudo.assert_has_calls(cmd_sequence) # Ensure the correct LVM state was preserved blockdev_state = { 'data': {'device': '/dev/fake/data'}, 'root': {'device': '/dev/fake/root'}, 'lv_audit': { 'device': '/dev/mapper/vg-lv_audit', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg' }, 'lv_home': { 'device': '/dev/mapper/vg-lv_home', 'extents': None, 'opts': None, 'size': '200M', 'vgs': 'vg' }, 'lv_log': { 'device': '/dev/mapper/vg-lv_log', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg' }, 'lv_root': { 'device': '/dev/mapper/vg-lv_root', 'extents': None, 'opts': None, 'size': '1800M', 'vgs': 'vg' }, 'lv_tmp': { 'device': '/dev/mapper/vg-lv_tmp', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg' }, 'lv_var': { 'device': '/dev/mapper/vg-lv_var', 'extents': None, 'opts': None, 'size': '500M', 'vgs': 'vg' }, } # state.debug_dump() self.assertDictEqual(state['blockdev'], blockdev_state)
def test_lvm_multiple_partitions(self): # Test the command-sequence for several partitions, one containing # volumes on it tree = self.load_config_file('lvm_tree_multiple_partitions.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # Fake state for the partitions on this config state['blockdev'] = {} state['blockdev']['image0'] = {} state['blockdev']['image0']['device'] = '/dev/fake/image0' state['blockdev']['image0']['image'] = 'image' state['blockdev']['root'] = {} state['blockdev']['root']['device'] = '/dev/fake/root' state['blockdev']['ESP'] = {} state['blockdev']['ESP']['device'] = '/dev/fake/ESP' state['blockdev']['BSP'] = {} state['blockdev']['BSP']['device'] = '/dev/fake/BSP' # # Creation test # # We mock out the following exec_sudo and other related calls # calls for the layers we are testing. exec_sudo_lvm = 'diskimage_builder.block_device.level1.lvm.exec_sudo' exec_sudo_part = ('diskimage_builder.block_device.' 'level1.partitioning.exec_sudo') exec_sudo_loop = ('diskimage_builder.block_device.' 'level0.localloop.exec_sudo') image_create = ('diskimage_builder.block_device.level0.' 'localloop.LocalLoopNode.create') size_of_block = ('diskimage_builder.block_device.level1.' 'partitioning.Partitioning._size_of_block_dev') create_mbr = ('diskimage_builder.block_device.level1.' 'partitioning.Partitioning._create_mbr') manager = mock.MagicMock() with mock.patch(exec_sudo_lvm) as mock_sudo_lvm, \ mock.patch(exec_sudo_part) as mock_sudo_part, \ mock.patch(exec_sudo_loop) as mock_sudo_loop, \ mock.patch(image_create) as mock_image_create, \ mock.patch(size_of_block) as mock_size_of_block, \ mock.patch(create_mbr) as mock_create_mbr: manager.attach_mock(mock_sudo_lvm, 'sudo_lvm') manager.attach_mock(mock_sudo_part, 'sudo_part') manager.attach_mock(mock_sudo_loop, 'sudo_loop') manager.attach_mock(mock_image_create, 'image_create') manager.attach_mock(mock_size_of_block, 'size_of_block') manager.attach_mock(mock_create_mbr, 'create_mbr') for node in call_order: # We're just keeping this to the partition setup and # LVM creation; i.e. skipping mounting, mkfs, etc. if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode, LocalLoopNode, PartitionNode)): node.create() else: logger.debug("Skipping node for test: %s", node) cmd_sequence = [ # create the underlying block device mock.call.image_create(), mock.call.size_of_block('image'), # write out partition table mock.call.create_mbr(), # now mount partitions mock.call.sudo_part(['sync']), mock.call.sudo_part(['kpartx', '-avs', '/dev/fake/image0']), # now create lvm environment mock.call.sudo_lvm(['pvcreate', '/dev/fake/root', '--force']), mock.call.sudo_lvm( ['vgcreate', 'vg', '/dev/fake/root', '--force']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_root', '-l', '28%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_tmp', '-l', '4%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_var', '-l', '40%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_log', '-l', '23%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_audit', '-l', '4%VG', 'vg']), mock.call.sudo_lvm( ['lvcreate', '--name', 'lv_home', '-l', '1%VG', 'vg']), ] manager.assert_has_calls(cmd_sequence) # # Umount/cleanup test # manager = mock.MagicMock() with mock.patch(exec_sudo_lvm) as mock_sudo_lvm, \ mock.patch(exec_sudo_part) as mock_sudo_part, \ mock.patch(exec_sudo_loop) as mock_sudo_loop: manager.attach_mock(mock_sudo_lvm, 'sudo_lvm') manager.attach_mock(mock_sudo_part, 'sudo_part') manager.attach_mock(mock_sudo_loop, 'sudo_loop') def run_it(phase): reverse_order = reversed(call_order) for node in reverse_order: if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode, LocalLoopNode, PartitionNode)): getattr(node, phase)() else: logger.debug("Skipping node for test: %s", node) run_it('umount') run_it('cleanup') cmd_sequence = [ # deactivate LVM first mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_root']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_tmp']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_var']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_log']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_audit']), mock.call.sudo_lvm(['lvchange', '-an', '/dev/vg/lv_home']), mock.call.sudo_lvm(['vgchange', '-an', 'vg']), mock.call.sudo_lvm(['udevadm', 'settle']), # now remove partitions (note has to happen after lvm removal) mock.call.sudo_part(['kpartx', '-d', '/dev/fake/image0']), # now remove loopback device mock.call.sudo_loop(['losetup', '-d', '/dev/fake/image0']), # now final LVM cleanup call mock.call.sudo_lvm(['pvscan', '--cache']), ] manager.assert_has_calls(cmd_sequence)
def test_lvm_multi_pv(self, mock_exec_sudo): # Test the command-sequence for a more complicated LVM setup tree = self.load_config_file('lvm_tree_multiple_pv.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # XXX: todo; test call_order. Make sure PV's come before, VG; # VG before LV, and that mounts/etc happen afterwards. # Fake state for the two PV's specified by this config state['blockdev'] = {} state['blockdev']['root'] = {} state['blockdev']['root']['device'] = '/dev/fake/root' state['blockdev']['data'] = {} state['blockdev']['data']['device'] = '/dev/fake/data' for node in call_order: # XXX: This has not mocked out the "lower" layers of # creating the devices, which we're assuming works OK, nor # the upper layers. if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): # only the LVMNode actually does anything here... node.create() # ensure the sequence of calls correctly setup the devices cmd_sequence = [ # create the pv's on the faked out block devices mock.call(['pvcreate', '/dev/fake/root', '--force']), mock.call(['pvcreate', '/dev/fake/data', '--force']), # create a volume called "vg" out of these two pv's mock.call(['vgcreate', 'vg', '/dev/fake/root', '/dev/fake/data', '--force']), # create a bunch of lv's on vg mock.call(['lvcreate', '--name', 'lv_root', '-L', '1799356416B', 'vg']), mock.call(['lvcreate', '--name', 'lv_tmp', '-L', '96468992B', 'vg']), mock.call(['lvcreate', '--name', 'lv_var', '-L', '499122176B', 'vg']), mock.call(['lvcreate', '--name', 'lv_log', '-L', '96468992B', 'vg']), mock.call(['lvcreate', '--name', 'lv_audit', '-L', '96468992B', 'vg']), mock.call(['lvcreate', '--name', 'lv_home', '-L', '197132288B', 'vg'])] self.assertEqual(mock_exec_sudo.call_count, len(cmd_sequence)) mock_exec_sudo.assert_has_calls(cmd_sequence) # Ensure the correct LVM state was preserved blockdev_state = { 'data': {'device': '/dev/fake/data'}, 'root': {'device': '/dev/fake/root'}, 'lv_audit': { 'device': '/dev/mapper/vg-lv_audit', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg' }, 'lv_home': { 'device': '/dev/mapper/vg-lv_home', 'extents': None, 'opts': None, 'size': '200M', 'vgs': 'vg' }, 'lv_log': { 'device': '/dev/mapper/vg-lv_log', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg' }, 'lv_root': { 'device': '/dev/mapper/vg-lv_root', 'extents': None, 'opts': None, 'size': '1800M', 'vgs': 'vg' }, 'lv_tmp': { 'device': '/dev/mapper/vg-lv_tmp', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg' }, 'lv_var': { 'device': '/dev/mapper/vg-lv_var', 'extents': None, 'opts': None, 'size': '500M', 'vgs': 'vg' }, } # state.debug_dump() self.assertDictEqual(state['blockdev'], blockdev_state)
def test_simple_tree(self): tree = self.load_config_file('simple_tree.yaml') graph = self.load_config_file('simple_graph.yaml') parsed_graph = config_tree_to_graph(tree) self.assertCountEqual(parsed_graph, graph)
def test_lvm_multi_pv_vg(self): # Test the command-sequence for a more complicated LVM setup tree = self.load_config_file('lvm_tree_multiple_pv_vg.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # XXX: todo; test call_order. Make sure PV's come before, VG; # VG before LV, and that mounts/etc happen afterwards. # Fake state for the two PV's specified by this config state['blockdev'] = {} state['blockdev']['root'] = {} state['blockdev']['root']['device'] = '/dev/fake/root' state['blockdev']['data'] = {} state['blockdev']['data']['device'] = '/dev/fake/data' # We mock patch this ... it's just a little long! exec_sudo = 'diskimage_builder.block_device.level1.lvm.exec_sudo' # # Creation test # with mock.patch(exec_sudo) as mock_exec_sudo: for node in call_order: # XXX: This has not mocked out the "lower" layers of # creating the devices, which we're assuming works OK, nor # the upper layers. if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): # only the PvsNode actually does anything here... node.create() # ensure the sequence of calls correctly setup the devices cmd_sequence = [ # create the pv's on the faked out block devices mock.call(['pvcreate', '/dev/fake/root', '--force']), mock.call(['pvcreate', '/dev/fake/data', '--force']), # create a volume called "vg" out of these two pv's mock.call(['vgcreate', 'vg1', '/dev/fake/root', '--force']), mock.call(['vgcreate', 'vg2', '/dev/fake/data', '--force']), # create a bunch of lv's on vg mock.call(['lvcreate', '--name', 'lv_root', '-L', '1800M', 'vg1']), mock.call(['lvcreate', '--name', 'lv_tmp', '-L', '100M', 'vg1']), mock.call(['lvcreate', '--name', 'lv_var', '-L', '500M', 'vg2']), mock.call(['lvcreate', '--name', 'lv_log', '-L', '100M', 'vg2']), mock.call(['lvcreate', '--name', 'lv_audit', '-L', '100M', 'vg2']), mock.call(['lvcreate', '--name', 'lv_home', '-L', '200M', 'vg2'])] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence) # Ensure the correct LVM state was preserved blockdev_state = { 'data': {'device': '/dev/fake/data'}, 'root': {'device': '/dev/fake/root'}, 'lv_audit': { 'device': '/dev/mapper/vg2-lv_audit', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg2' }, 'lv_home': { 'device': '/dev/mapper/vg2-lv_home', 'extents': None, 'opts': None, 'size': '200M', 'vgs': 'vg2' }, 'lv_log': { 'device': '/dev/mapper/vg2-lv_log', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg2' }, 'lv_root': { 'device': '/dev/mapper/vg1-lv_root', 'extents': None, 'opts': None, 'size': '1800M', 'vgs': 'vg1' }, 'lv_tmp': { 'device': '/dev/mapper/vg1-lv_tmp', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg1' }, 'lv_var': { 'device': '/dev/mapper/vg2-lv_var', 'extents': None, 'opts': None, 'size': '500M', 'vgs': 'vg2' }, } # state.debug_dump() self.assertDictEqual(state['blockdev'], blockdev_state) # # Umount test # with mock.patch(exec_sudo) as mock_exec_sudo, \ mock.patch('tempfile.NamedTemporaryFile') as mock_temp, \ mock.patch('os.unlink'): # each call to tempfile.NamedTemporaryFile will return a # new mock with a unique filename, which we store in # tempfiles tempfiles = [] def new_tempfile(*args, **kwargs): n = '/tmp/files%s' % len(tempfiles) # trap! note mock.Mock(name = n) doesn't work like you # think it would, since mock has a name attribute. # That's why we override it with the configure_mock # (this is mentioned in mock documentation if you read # it :) r = mock.Mock() r.configure_mock(name=n) tempfiles.append(n) return r mock_temp.side_effect = new_tempfile def run_it(phase): reverse_order = reversed(call_order) for node in reverse_order: if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): getattr(node, phase)() else: logger.debug("Skipping node for test: %s", node) run_it('umount') run_it('cleanup') cmd_sequence = [ # delete the lv's mock.call(['lvchange', '-an', '/dev/vg1/lv_root']), mock.call(['lvchange', '-an', '/dev/vg1/lv_tmp']), mock.call(['lvchange', '-an', '/dev/vg2/lv_var']), mock.call(['lvchange', '-an', '/dev/vg2/lv_log']), mock.call(['lvchange', '-an', '/dev/vg2/lv_audit']), mock.call(['lvchange', '-an', '/dev/vg2/lv_home']), # delete the vg's mock.call(['vgchange', '-an', 'vg1']), mock.call(['vgchange', '-an', 'vg2']), mock.call(['udevadm', 'settle']), mock.call(['pvscan', '--cache']), ] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence)
def test_lvm_multi_pv_vg(self): # Test the command-sequence for a more complicated LVM setup tree = self.load_config_file('lvm_tree_multiple_pv_vg.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # XXX: todo; test call_order. Make sure PV's come before, VG; # VG before LV, and that mounts/etc happen afterwards. # Fake state for the two PV's specified by this config state['blockdev'] = {} state['blockdev']['root'] = {} state['blockdev']['root']['device'] = '/dev/fake/root' state['blockdev']['data'] = {} state['blockdev']['data']['device'] = '/dev/fake/data' # We mock patch this ... it's just a little long! exec_sudo = 'diskimage_builder.block_device.level1.lvm.exec_sudo' # # Creation test # with mock.patch(exec_sudo) as mock_exec_sudo: for node in call_order: # XXX: This has not mocked out the "lower" layers of # creating the devices, which we're assuming works OK, nor # the upper layers. if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): # only the PvsNode actually does anything here... node.create() # ensure the sequence of calls correctly setup the devices cmd_sequence = [ # create the pv's on the faked out block devices mock.call(['pvcreate', '/dev/fake/root', '--force']), mock.call(['pvcreate', '/dev/fake/data', '--force']), # create a volume called "vg" out of these two pv's mock.call(['vgcreate', 'vg1', '/dev/fake/root', '--force']), mock.call(['vgcreate', 'vg2', '/dev/fake/data', '--force']), # create a bunch of lv's on vg mock.call(['lvcreate', '--name', 'lv_root', '-L', '1799356416B', 'vg1']), mock.call(['lvcreate', '--name', 'lv_tmp', '-L', '96468992B', 'vg1']), mock.call(['lvcreate', '--name', 'lv_var', '-L', '499122176B', 'vg2']), mock.call(['lvcreate', '--name', 'lv_log', '-L', '96468992B', 'vg2']), mock.call(['lvcreate', '--name', 'lv_audit', '-L', '96468992B', 'vg2']), mock.call(['lvcreate', '--name', 'lv_home', '-L', '197132288B', 'vg2'])] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence) # Ensure the correct LVM state was preserved blockdev_state = { 'data': {'device': '/dev/fake/data'}, 'root': {'device': '/dev/fake/root'}, 'lv_audit': { 'device': '/dev/mapper/vg2-lv_audit', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg2' }, 'lv_home': { 'device': '/dev/mapper/vg2-lv_home', 'extents': None, 'opts': None, 'size': '200M', 'vgs': 'vg2' }, 'lv_log': { 'device': '/dev/mapper/vg2-lv_log', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg2' }, 'lv_root': { 'device': '/dev/mapper/vg1-lv_root', 'extents': None, 'opts': None, 'size': '1800M', 'vgs': 'vg1' }, 'lv_tmp': { 'device': '/dev/mapper/vg1-lv_tmp', 'extents': None, 'opts': None, 'size': '100M', 'vgs': 'vg1' }, 'lv_var': { 'device': '/dev/mapper/vg2-lv_var', 'extents': None, 'opts': None, 'size': '500M', 'vgs': 'vg2' }, } # state.debug_dump() self.assertDictEqual(state['blockdev'], blockdev_state) # # Umount test # with mock.patch(exec_sudo) as mock_exec_sudo, \ mock.patch('tempfile.NamedTemporaryFile') as mock_temp, \ mock.patch('os.unlink'): # each call to tempfile.NamedTemporaryFile will return a # new mock with a unique filename, which we store in # tempfiles tempfiles = [] def new_tempfile(*args, **kwargs): n = '/tmp/files%s' % len(tempfiles) # trap! note mock.Mock(name = n) doesn't work like you # think it would, since mock has a name attribute. # That's why we override it with the configure_mock # (this is mentioned in mock documentation if you read # it :) r = mock.Mock() r.configure_mock(name=n) tempfiles.append(n) return r mock_temp.side_effect = new_tempfile def run_it(phase): reverse_order = reversed(call_order) for node in reverse_order: if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): getattr(node, phase)() else: logger.debug("Skipping node for test: %s", node) run_it('umount') run_it('cleanup') cmd_sequence = [ # delete the lv's mock.call(['lvchange', '-an', '/dev/vg1/lv_root']), mock.call(['lvchange', '-an', '/dev/vg1/lv_tmp']), mock.call(['lvchange', '-an', '/dev/vg2/lv_var']), mock.call(['lvchange', '-an', '/dev/vg2/lv_log']), mock.call(['lvchange', '-an', '/dev/vg2/lv_audit']), mock.call(['lvchange', '-an', '/dev/vg2/lv_home']), # delete the vg's mock.call(['vgchange', '-an', 'vg1']), mock.call(['vgchange', '-an', 'vg2']), mock.call(['udevadm', 'settle']), mock.call(['pvscan', '--cache']), ] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence)
def test_lvm_spanned_vg(self): # Test when a volume group spans some partitions tree = self.load_config_file('lvm_tree_spanned_vg.yaml') config = config_tree_to_graph(tree) state = BlockDeviceState() graph, call_order = create_graph(config, self.fake_default_config, state) # XXX: todo; test call_order. Make sure PV's come before, VG; # VG before LV, and that mounts/etc happen afterwards. # Fake state for the two PV's specified by this config state['blockdev'] = {} state['blockdev']['root'] = {} state['blockdev']['root']['device'] = '/dev/fake/root' state['blockdev']['data1'] = {} state['blockdev']['data1']['device'] = '/dev/fake/data1' state['blockdev']['data2'] = {} state['blockdev']['data2']['device'] = '/dev/fake/data2' # We mock patch this ... it's just a little long! exec_sudo = 'diskimage_builder.block_device.level1.lvm.exec_sudo' # # Creation test # with mock.patch(exec_sudo) as mock_exec_sudo: for node in call_order: # XXX: This has not mocked out the "lower" layers of # creating the devices, which we're assuming works OK, nor # the upper layers. if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): # only the LVMNode actually does anything here... node.create() # ensure the sequence of calls correctly setup the devices cmd_sequence = [ # create the pv's on the faked out block devices mock.call(['pvcreate', '/dev/fake/root', '--force']), mock.call(['pvcreate', '/dev/fake/data1', '--force']), mock.call(['pvcreate', '/dev/fake/data2', '--force']), # create a root and a data volume, with the data volume # spanning data1 & data2 mock.call(['vgcreate', 'vg_root', '/dev/fake/root', '--force']), mock.call(['vgcreate', 'vg_data', '/dev/fake/data1', '/dev/fake/data2', '--force']), # create root and data volume mock.call(['lvcreate', '--name', 'lv_root', '-L', '1800M', 'vg_root']), mock.call(['lvcreate', '--name', 'lv_data', '-L', '2G', 'vg_data']) ] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence) with mock.patch(exec_sudo) as mock_exec_sudo, \ mock.patch('tempfile.NamedTemporaryFile') as mock_temp, \ mock.patch('os.unlink'): # see above ... tempfiles = [] def new_tempfile(*args, **kwargs): n = '/tmp/files%s' % len(tempfiles) r = mock.Mock() r.configure_mock(name=n) tempfiles.append(n) return r mock_temp.side_effect = new_tempfile def run_it(phase): reverse_order = reversed(call_order) for node in reverse_order: if isinstance(node, (LVMNode, PvsNode, VgsNode, LvsNode)): getattr(node, phase)() else: logger.debug("Skipping node for test: %s", node) run_it('umount') run_it('cleanup') cmd_sequence = [ # deactivate lv's mock.call(['lvchange', '-an', '/dev/vg_root/lv_root']), mock.call(['lvchange', '-an', '/dev/vg_data/lv_data']), # deactivate vg's mock.call(['vgchange', '-an', 'vg_root']), mock.call(['vgchange', '-an', 'vg_data']), mock.call(['udevadm', 'settle']), mock.call(['pvscan', '--cache']), ] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence)