def test_mount_order(self, mock_exec_sudo): config = self.load_config_file('multiple_partitions_graph.yaml') graph, call_order = create_graph(config, self.fake_default_config) result = {} result['filesys'] = {} result['filesys']['mkfs_root'] = {} result['filesys']['mkfs_root']['device'] = 'fake' result['filesys']['mkfs_var'] = {} result['filesys']['mkfs_var']['device'] = 'fake' result['filesys']['mkfs_var_log'] = {} result['filesys']['mkfs_var_log']['device'] = 'fake' rollback = [] for node in call_order: if isinstance(node, MountPointNode): # XXX: do we even need to create? We could test the # sudo arguments from the mock in the below asserts # too node.create(result, rollback) # ensure that partitions are mounted in order root->var->var/log self.assertListEqual(result['mount_order'], ['/', '/var', '/var/log'])
def cmd_create(self): """Creates the block device""" logger.info("create() called") logger.debug("Using config [%s]", self.config) # Create a new, empty state state = BlockDeviceState() try: dg, call_order = create_graph(self.config, self.params, state) for node in call_order: node.create() except Exception: logger.exception("Create failed; rollback initiated") reverse_order = reversed(call_order) for node in reverse_order: node.rollback() # save the state for debugging state.save_state(self.state_json_file_name) logger.error("Rollback complete, exiting") raise # dump state and nodes, in order # XXX: we only dump the call_order (i.e. nodes) not the whole # graph here, because later calls do not need the graph # at this stage. might they? state.save_state(self.state_json_file_name) pickle.dump(call_order, open(self.node_pickle_file_name, 'wb')) logger.info("create() finished") return 0
def test_mount_order(self, mock_exec_sudo): config = self.load_config_file('multiple_partitions_graph.yaml') state = {} graph, call_order = create_graph(config, self.fake_default_config, state) # build up some fake state so that we don't have to mock out # all the parent calls that would really make these values, as # we just want to test MountPointNode state['filesys'] = {} state['filesys']['mkfs_root'] = {} state['filesys']['mkfs_root']['device'] = 'fake' state['filesys']['mkfs_var'] = {} state['filesys']['mkfs_var']['device'] = 'fake' state['filesys']['mkfs_var_log'] = {} state['filesys']['mkfs_var_log']['device'] = 'fake' for node in call_order: if isinstance(node, MountPointNode): # XXX: do we even need to create? We could test the # sudo arguments from the mock in the below asserts # too node.create() # ensure that partitions are mounted in order root->var->var/log self.assertListEqual(state['mount_order'], ['/', '/var', '/var/log'])
def test_mount_order(self, mock_exec_sudo): # XXX: better mocking for the os.path.exists calls to avoid # failing if this exists. self.assertFalse(os.path.exists('/fake/')) # This is probably in order after graph creation, so ensure it # remains stable. We test the mount and umount call sequences config = self.load_config_file('multiple_partitions_graph.yaml') state = {} graph, call_order = create_graph(config, self.fake_default_config, state) # build up some fake state so that we don't have to mock out # all the parent calls that would really make these values, as # we just want to test MountPointNode state['filesys'] = { 'mkfs_root': { 'device': 'fake_root_device' }, 'mkfs_var': { 'device': 'fake_var_device' }, 'mkfs_var_log': { 'device': 'fake_var_log_device' } } 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 root->var->var/log self.assertListEqual(state['mount_order'], ['/', '/var', '/var/log']) cmd_sequence = [ # mount sequence mock.call(['mkdir', '-p', '/fake/']), mock.call(['mount', 'fake_root_device', '/fake/']), mock.call(['mkdir', '-p', '/fake/var']), mock.call(['mount', 'fake_var_device', '/fake/var']), mock.call(['mkdir', '-p', '/fake/var/log']), mock.call(['mount', 'fake_var_log_device', '/fake/var/log']), # umount sequence mock.call(['sync']), mock.call(['fstrim', '--verbose', '/fake/var/log']), mock.call(['umount', '/fake/var/log']), mock.call(['sync']), mock.call(['fstrim', '--verbose', '/fake/var']), mock.call(['umount', '/fake/var']), mock.call(['sync']), mock.call(['fstrim', '--verbose', '/fake/']), mock.call(['umount', '/fake/']) ] self.assertListEqual(mock_exec_sudo.call_args_list, cmd_sequence)
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 cmd_delete(self): """Cleanup all remaining relicts - in case of an error""" # Deleting must be done in reverse order dg, call_order = create_graph(self.config, self.params) reverse_order = reversed(call_order) for node in reverse_order: node.delete(self.state) logger.info("Removing temporary dir [%s]", self.state_dir) shutil.rmtree(self.state_dir) return 0
def test_deep_graph_generator(self): config = self.load_config_file('deep_graph.yaml') graph, call_order = create_graph(config, self.fake_default_config, {}) call_order_list = [n.name for n in call_order] # manually created from deep_graph.yaml # Note unlike below, the sort here is stable because the graph # doesn't have multiple paths with only one partition call_order_names = ['image0', 'root', 'mkfs_root', 'mount_mkfs_root', 'fstab_mount_mkfs_root'] self.assertListEqual(call_order_list, call_order_names)
def cmd_umount(self): """Unmounts the blockdevice and cleanup resources""" if self.state is None: logger.info("State already cleaned - no way to do anything here") return 0 # Deleting must be done in reverse order dg, call_order = create_graph(self.config, self.params) reverse_order = reversed(call_order) if dg is None: return 0 for node in reverse_order: node.umount(self.state) return 0
def cmd_delete(self): """Cleanup all remaining relicts - in case of an error""" # State should have been created by prior calls; we only need # the dict state = BlockDeviceState(self.state_json_file_name) # Deleting must be done in reverse order dg, call_order = create_graph(self.config, self.params) reverse_order = reversed(call_order) for node in reverse_order: node.delete(state) logger.info("Removing temporary state dir [%s]", self.state_dir) shutil.rmtree(self.state_dir) return 0
def cmd_umount(self): """Unmounts the blockdevice and cleanup resources""" # State should have been created by prior calls; we only need # the dict. If it is not here, it has been cleaned up already # (? more details?) try: state = BlockDeviceState(self.state_json_file_name) except BlockDeviceSetupException: logger.info("State already cleaned - no way to do anything here") return 0 # Deleting must be done in reverse order dg, call_order = create_graph(self.config, self.params) reverse_order = reversed(call_order) if dg is None: return 0 for node in reverse_order: node.umount(state) return 0
def test_multiple_partitions_graph_generator(self): config = self.load_config_file('multiple_partitions_graph.yaml') graph, call_order = create_graph(config, self.fake_default_config, {}) call_order_list = [n.name for n in call_order] # The sort creating call_order_list is unstable. # We want to ensure we see the "partitions" object in # root->var->var_log order root_pos = call_order_list.index('root') var_pos = call_order_list.index('var') var_log_pos = call_order_list.index('var_log') self.assertGreater(var_pos, root_pos) self.assertGreater(var_log_pos, var_pos) # Ensure mkfs happens after partition mkfs_root_pos = call_order_list.index('mkfs_root') self.assertLess(root_pos, mkfs_root_pos) mkfs_var_pos = call_order_list.index('mkfs_var') self.assertLess(var_pos, mkfs_var_pos) mkfs_var_log_pos = call_order_list.index('mkfs_var_log') self.assertLess(var_log_pos, mkfs_var_log_pos)
def test_multiple_partitions_graph_generator(self): config = self.load_config_file('multiple_partitions_graph.yaml') graph, call_order = create_graph(config, self.fake_default_config) call_order_list = [n.name for n in call_order] # The sort creating call_order_list is unstable. # We want to ensure we see the "partitions" object in # root->var->var_log order root_pos = call_order_list.index('root') var_pos = call_order_list.index('var') var_log_pos = call_order_list.index('var_log') self.assertGreater(var_pos, root_pos) self.assertGreater(var_log_pos, var_pos) # Ensure mkfs happens after partition mkfs_root_pos = call_order_list.index('mkfs_root') self.assertLess(root_pos, mkfs_root_pos) mkfs_var_pos = call_order_list.index('mkfs_var') self.assertLess(var_pos, mkfs_var_pos) mkfs_var_log_pos = call_order_list.index('mkfs_var_log') self.assertLess(var_log_pos, mkfs_var_log_pos)
def cmd_create(self): """Creates the block device""" logger.info("create() called") logger.debug("Using config [%s]", self.config) rollback = [] # Create a new, empty state state = BlockDeviceState() try: dg, call_order = create_graph(self.config, self.params) for node in call_order: node.create(state, rollback) except Exception: logger.exception("Create failed; rollback initiated") for rollback_cb in reversed(rollback): rollback_cb() sys.exit(1) state.save_state(self.state_json_file_name) logger.info("create() finished") return 0
def test_mfks_and_mount_order(self, mock_exec_sudo_mkfs, mock_exec_sudo_mount): # XXX: better mocking for the os.path.exists calls to avoid # failing if this exists. self.assertFalse(os.path.exists('/fake/')) # This is probably in order after graph creation, so ensure it # remains stable. We test the mount and umount call sequences config = self.load_config_file('multiple_partitions_graph.yaml') state = {} graph, call_order = create_graph(config, self.fake_default_config, state) # Mocked block device state state['blockdev'] = {} state['blockdev']['root'] = {'device': '/dev/loopXp1/root'} state['blockdev']['var'] = {'device': '/dev/loopXp2/var'} state['blockdev']['var_log'] = {'device': '/dev/loopXp3/var_log'} for node in call_order: if isinstance(node, (FilesystemNode, MountPointNode)): node.create() for node in reversed(call_order): if isinstance(node, (FilesystemNode, MountPointNode)): node.umount() # ensure that partitions were mounted in order root->var->var/log self.assertListEqual(state['mount_order'], ['/', '/var', '/var/log']) # fs creation sequence (note we don't care about order of this # as they're all independent) cmd_sequence = [ mock.call(['mkfs', '-t', 'xfs', '-L', 'mkfs_root', '-m', 'uuid=root-uuid-1234', '-q', '/dev/loopXp1/root']), mock.call(['mkfs', '-t', 'xfs', '-L', 'mkfs_var', '-m', 'uuid=var-uuid-1234', '-q', '/dev/loopXp2/var']), mock.call(['mkfs', '-t', 'vfat', '-n', 'VARLOG', '/dev/loopXp3/var_log']) ] self.assertEqual(mock_exec_sudo_mkfs.call_count, len(cmd_sequence)) mock_exec_sudo_mkfs.assert_has_calls(cmd_sequence, any_order=True) # Check mount sequence cmd_sequence = [ # mount sequence mock.call(['mkdir', '-p', '/fake/']), mock.call(['mount', '/dev/loopXp1/root', '/fake/']), mock.call(['mkdir', '-p', '/fake/var']), mock.call(['mount', '/dev/loopXp2/var', '/fake/var']), mock.call(['mkdir', '-p', '/fake/var/log']), mock.call(['mount', '/dev/loopXp3/var_log', '/fake/var/log']), # umount sequence mock.call(['sync']), # note /fake/var/log is a vfs partition to make sure # we don't try to fstrim it mock.call(['umount', '/fake/var/log']), mock.call(['sync']), mock.call(['fstrim', '--verbose', '/fake/var']), mock.call(['umount', '/fake/var']), mock.call(['sync']), mock.call(['fstrim', '--verbose', '/fake/']), mock.call(['umount', '/fake/']) ] self.assertListEqual(mock_exec_sudo_mount.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', '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_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)
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_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_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_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_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_mfks_and_mount_order(self, mock_exec_sudo_mkfs, mock_exec_sudo_mount): # XXX: better mocking for the os.path.exists calls to avoid # failing if this exists. self.assertFalse(os.path.exists('/fake/')) # This is probably in order after graph creation, so ensure it # remains stable. We test the mount and umount call sequences config = self.load_config_file('multiple_partitions_graph.yaml') state = {} graph, call_order = create_graph(config, self.fake_default_config, state) # Mocked block device state state['blockdev'] = {} state['blockdev']['root'] = {'device': '/dev/loopXp1/root'} state['blockdev']['var'] = {'device': '/dev/loopXp2/var'} state['blockdev']['var_log'] = {'device': '/dev/loopXp3/var_log'} for node in call_order: if isinstance(node, (FilesystemNode, MountPointNode)): node.create() for node in reversed(call_order): if isinstance(node, (FilesystemNode, MountPointNode)): node.umount() # ensure that partitions were mounted in order root->var->var/log self.assertListEqual(state['mount_order'], ['/', '/var', '/var/log']) # fs creation sequence (note we don't care about order of this # as they're all independent) cmd_sequence = [ mock.call([ 'mkfs', '-t', 'xfs', '-L', 'mkfs_root', '-m', 'uuid=root-uuid-1234', '-q', '/dev/loopXp1/root' ]), mock.call([ 'mkfs', '-t', 'xfs', '-L', 'mkfs_var', '-m', 'uuid=var-uuid-1234', '-q', '/dev/loopXp2/var' ]), mock.call( ['mkfs', '-t', 'vfat', '-n', 'VARLOG', '/dev/loopXp3/var_log']) ] self.assertEqual(mock_exec_sudo_mkfs.call_count, len(cmd_sequence)) mock_exec_sudo_mkfs.assert_has_calls(cmd_sequence, any_order=True) # Check mount sequence cmd_sequence = [ # mount sequence mock.call(['mkdir', '-p', '/fake/']), mock.call(['mount', '/dev/loopXp1/root', '/fake/']), mock.call(['mkdir', '-p', '/fake/var']), mock.call(['mount', '/dev/loopXp2/var', '/fake/var']), mock.call(['mkdir', '-p', '/fake/var/log']), mock.call(['mount', '/dev/loopXp3/var_log', '/fake/var/log']), # umount sequence mock.call(['sync']), # note /fake/var/log is a vfs partition to make sure # we don't try to fstrim it mock.call(['umount', '/fake/var/log']), mock.call(['sync']), mock.call(['fstrim', '--verbose', '/fake/var']), mock.call(['umount', '/fake/var']), mock.call(['sync']), mock.call(['fstrim', '--verbose', '/fake/']), mock.call(['umount', '/fake/']) ] self.assertListEqual(mock_exec_sudo_mount.call_args_list, 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', '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 create(self, result, rollback): dg, call_order = create_graph(self.config, self.params) for node in call_order: node.create(result, rollback)