--- tests/storage_test/Makefile.am | 2 +- tests/storage_test/action_test.py | 1191 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1192 insertions(+), 1 deletions(-) create mode 100755 tests/storage_test/action_test.py diff --git a/tests/storage_test/Makefile.am b/tests/storage_test/Makefile.am index e734b42..343cfff 100644 --- a/tests/storage_test/Makefile.am +++ b/tests/storage_test/Makefile.am @@ -26,4 +26,4 @@ MAINTAINERCLEANFILES = Makefile.in ANACDIR = $(top_builddir)/pyanaconda TESTS_ENVIRONMENT = PATH=/sbin:/usr/sbin:$$PATH PYTHONPATH=$(top_builddir)/tests:$(ANACDIR)/isys/.libs:$(ANACDIR):$(top_builddir) -TESTS = size_test.py +TESTS = action_test.py size_test.py diff --git a/tests/storage_test/action_test.py b/tests/storage_test/action_test.py new file mode 100755 index 0000000..2fcfc6b --- /dev/null +++ b/tests/storage_test/action_test.py @@ -0,0 +1,1191 @@ +#!/usr/bin/python + +import unittest +from mock import Mock +from mock import TestCase + +import parted + +import pyanaconda.anaconda_log +pyanaconda.anaconda_log.init() + +import pyanaconda.iutil +import pyanaconda.storage as storage +from pyanaconda.storage.formats import getFormat + +# device classes for brevity's sake -- later on, that is +from pyanaconda.storage.devices import StorageDevice +from pyanaconda.storage.devices import DiskDevice +from pyanaconda.storage.devices import PartitionDevice +from pyanaconda.storage.devices import MDRaidArrayDevice +from pyanaconda.storage.devices import DMDevice +from pyanaconda.storage.devices import LUKSDevice +from pyanaconda.storage.devices import LVMVolumeGroupDevice +from pyanaconda.storage.devices import LVMLogicalVolumeDevice +from pyanaconda.storage.devices import FileDevice + +# action classes +from pyanaconda.storage.deviceaction import ActionCreateDevice +from pyanaconda.storage.deviceaction import ActionResizeDevice +from pyanaconda.storage.deviceaction import ActionDestroyDevice +from pyanaconda.storage.deviceaction import ActionCreateFormat +from pyanaconda.storage.deviceaction import ActionResizeFormat +from pyanaconda.storage.deviceaction import ActionMigrateFormat +from pyanaconda.storage.deviceaction import ActionDestroyFormat + +""" DeviceActionTestSuite """ + +class StorageTestCase(TestCase): + """ StorageTestCase + + This is a base class for storage test cases. It sets up imports of + the storage package, along with an Anaconda instance and a Storage + instance. There are lots of little patches to prevent various pieces + of code from trying to access filesystems and/or devices on the host + system, along with a couple of convenience methods. + + """ + def __init__(self, *args, **kwargs): + TestCase.__init__(self, *args, **kwargs) + + self.setUpAnaconda() + + def setUpAnaconda(self): + pyanaconda.iutil.execWithRedirect = Mock() + pyanaconda.iutil.execWithCapture = Mock() + pyanaconda.iutil.execWithPulseProgress = Mock() + pyanaconda.baseudev = Mock() + + self.anaconda = Mock() + self.setUpStorage() + + def setUpStorage(self): + self.setUpDeviceLibs() + self.storage = storage.Storage(self.anaconda) + + # device status + StorageDevice.status = False + DMDevice.status = False + LUKSDevice.status = False + LVMVolumeGroupDevice.status = False + MDRaidArrayDevice.status = False + FileDevice.status = False + + # prevent PartitionDevice from trying to dig around in the partition's + # geometry + PartitionDevice._setTargetSize = StorageDevice._setTargetSize + + # prevent Ext2FS from trying to run resize2fs to get a filesystem's + # minimum size + storage.formats.fs.Ext2FS.minSize = storage.formats.DeviceFormat.minSize + storage.formats.fs.FS.migratable = storage.formats.DeviceFormat.migratable + + def setUpDeviceLibs(self): + # devicelibs shouldn't be touching or looking at the host system + + # lvm is easy because all calls to /sbin/lvm are via lvm() + storage.devicelibs.lvm.lvm = Mock() + + # mdraid is easy because all calls to /sbin/mdadm are via mdadm() + storage.devicelibs.mdraid.mdadm = Mock() + + # swap + storage.devicelibs.swap.swapstatus = Mock(return_value=False) + storage.devicelibs.swap.swapon = Mock() + storage.devicelibs.swap.swapoff = Mock() + + # dm + storage.devicelibs.dm = Mock() + + # crypto/luks + storage.devicelibs.crypto.luks_status = Mock(return_value=False) + storage.devicelibs.crypto.luks_uuid = Mock() + storage.devicelibs.crypto.luks_format = Mock() + storage.devicelibs.crypto.luks_open = Mock() + storage.devicelibs.crypto.luks_close = Mock() + storage.devicelibs.crypto.luks_add_key = Mock() + storage.devicelibs.crypto.luks_remove_key = Mock() + + # this list would normally be obtained by parsing /proc/mdstat + storage.devicelibs.mdraid.raid_levels = \ + [storage.devicelibs.mdraid.RAID10, + storage.devicelibs.mdraid.RAID0, + storage.devicelibs.mdraid.RAID1, + storage.devicelibs.mdraid.RAID4, + storage.devicelibs.mdraid.RAID5, + storage.devicelibs.mdraid.RAID6] + + def newDevice(*args, **kwargs): + """ Return a new Device instance suitable for testing. """ + args = args[1:] # drop self arg + device_class = kwargs.pop("device_class") + exists = kwargs.pop("exists", False) + part_type = kwargs.pop("part_type", parted.PARTITION_NORMAL) + device = device_class(*args, **kwargs) + + if exists: + # set up mock parted.Device w/ correct size + device._partedDevice = Mock() + device._partedDevice.getSize = Mock(return_value=float(device.size)) + device._partedDevice.sectorSize = 512 + + if isinstance(device, PartitionDevice): + #if exists: + # device.parents = device.req_disks + device.parents = device.req_disks + + partedPartition = Mock() + + if device.disk: + part_num = device.name[len(device.disk.name):].split("p")[-1] + partedPartition.number = int(part_num) + + partedPartition.type = part_type + partedPartition.path = device.path + partedPartition.getDeviceNodeName = Mock(return_value=device.name) + partedPartition.getSize = Mock(return_value=float(device.size)) + device._partedPartition = partedPartition + + device.exists = exists + device.format.exists = exists + + if isinstance(device, PartitionDevice): + # PartitionDevice.probe sets up data needed for resize operations + device.probe() + + return device + + def newFormat(*args, **kwargs): + """ Return a new DeviceFormat instance suitable for testing. + + Keyword Arguments: + + device_instance - StorageDevice instance this format will be + created on. This is needed for setup of + resizable formats. + + All other arguments are passed directly to + pyanaconda.storage.formats.getFormat. + """ + args = args[1:] # drop self arg + exists = kwargs.pop("exists", False) + device_instance = kwargs.pop("device_instance", None) + format = getFormat(*args, **kwargs) + if isinstance(format, storage.formats.disklabel.DiskLabel): + format._partedDevice = Mock() + format._partedDisk = Mock() + + format.exists = exists + + if format.resizable and device_instance: + format._size = device_instance.currentSize + + return format + + def destroyAllDevices(self, disks=None): + """ Remove all devices from the devicetree. + + Keyword Arguments: + + disks - a list of names of disks to remove partitions from + + Note: this is largely ripped off from partitioning.clearPartitions. + + """ + partitions = self.storage.partitions + + # Sort partitions by descending partition number to minimize confusing + # things like multiple "destroy sda5" actions due to parted renumbering + # partitions. This can still happen through the UI but it makes sense to + # avoid it where possible. + partitions.sort(key=lambda p: p.partedPartition.number, reverse=True) + for part in partitions: + if disks and part.disk.name not in disks: + continue + + devices = self.storage.deviceDeps(part) + while devices: + leaves = [d for d in devices if d.isleaf] + for leaf in leaves: + self.storage.destroyDevice(leaf) + devices.remove(leaf) + + self.storage.destroyDevice(part) + + def scheduleCreateDevice(self, *args, **kwargs): + """ Schedule an action to create the specified device. + + Verify that the device is not already in the tree and that the + act of scheduling/registering the action also adds the device to + the tree. + + Return the DeviceAction instance. + """ + device = kwargs.pop("device") + if hasattr(device, "req_disks") and \ + len(device.req_disks) == 1 and \ + not device.parents: + device.parents = device.req_disks + + devicetree = self.storage.devicetree + + self.assertEqual(devicetree.getDeviceByName(device.name), None) + action = storage.deviceaction.ActionCreateDevice(device) + devicetree.registerAction(action) + self.assertEqual(devicetree.getDeviceByName(device.name), device) + return action + + def scheduleDestroyDevice(self, *args, **kwargs): + """ Schedule an action to destroy the specified device. + + Verify that the device exists initially and that the act of + scheduling/registering the action also removes the device from + the tree. + + Return the DeviceAction instance. + """ + device = kwargs.pop("device") + devicetree = self.storage.devicetree + + self.assertEqual(devicetree.getDeviceByName(device.name), device) + action = storage.deviceaction.ActionDestroyDevice(device) + devicetree.registerAction(action) + self.assertEqual(devicetree.getDeviceByName(device.name), None) + return action + + def scheduleCreateFormat(self, *args, **kwargs): + """ Schedule an action to write a new format to a device. + + Verify that the device is already in the tree, that it is not + already set up to contain the specified format, and that the act + of registering/scheduling the action causes the new format to be + reflected in the tree. + + Return the DeviceAction instance. + """ + device = kwargs.pop("device") + format = kwargs.pop("format") + devicetree = self.storage.devicetree + + self.assertNotEqual(device.format, format) + self.assertEqual(devicetree.getDeviceByName(device.name), device) + action = storage.deviceaction.ActionCreateFormat(device, format) + devicetree.registerAction(action) + _device = devicetree.getDeviceByName(device.name) + self.assertEqual(_device.format, format) + return action + + def scheduleDestroyFormat(self, *args, **kwargs): + """ Schedule an action to remove a format from a device. + + Verify that the device is already in the tree and that the act + of registering/scheduling the action causes the new format to be + reflected in the tree. + + Return the DeviceAction instance. + """ + device = kwargs.pop("device") + devicetree = self.storage.devicetree + + self.assertEqual(devicetree.getDeviceByName(device.name), device) + action = storage.deviceaction.ActionDestroyFormat(device) + devicetree.registerAction(action) + _device = devicetree.getDeviceByName(device.name) + self.assertEqual(_device.format.type, None) + return action + + +class DeviceActionTestCase(StorageTestCase): + def setUp(self): + """ Create something like a preexisting autopart on two disks (sda,sdb). + + The other two disks (sdc,sdd) are left for individual tests to use. + """ + self.setUpAnaconda() + + for name in ["sda", "sdb", "sdc", "sdd"]: + disk = self.newDevice(device_class=DiskDevice, + name=name, size=100000) + disk.format = self.newFormat("disklabel", path=disk.path, + exists=True) + self.storage.devicetree._addDevice(disk) + + # create a layout similar to autopart as a starting point + sda = self.storage.devicetree.getDeviceByName("sda") + sdb = self.storage.devicetree.getDeviceByName("sdb") + + sda1 = self.newDevice(device_class=PartitionDevice, + exists=True, name="sda1", parents=[sda], size=500) + sda1.format = self.newFormat("ext4", mountpoint="/boot", + device_instance=sda1, + device=sda1.path, exists=True) + self.storage.devicetree._addDevice(sda1) + + sda2 = self.newDevice(device_class=PartitionDevice, + size=99500, name="sda2", parents=[sda], exists=True) + sda2.format = self.newFormat("lvmpv", device=sda2.path, exists=True) + self.storage.devicetree._addDevice(sda2) + + sdb1 = self.newDevice(device_class=PartitionDevice, + size=99999, name="sdb1", parents=[sdb], exists=True) + sdb1.format = self.newFormat("lvmpv", device=sdb1.path, exists=True) + self.storage.devicetree._addDevice(sdb1) + + vg = self.newDevice(device_class=LVMVolumeGroupDevice, + name="VolGroup", parents=[sda2, sdb1], + exists=True) + self.storage.devicetree._addDevice(vg) + + lv_root = self.newDevice(device_class=LVMLogicalVolumeDevice, + name="lv_root", vgdev=vg, size=160000, + exists=True) + lv_root.format = self.newFormat("ext4", mountpoint="/", + device_instance=lv_root, + device=lv_root.path, exists=True) + self.storage.devicetree._addDevice(lv_root) + + lv_swap = self.newDevice(device_class=LVMLogicalVolumeDevice, + name="lv_swap", vgdev=vg, size=4000, + exists=True) + lv_swap.format = self.newFormat("swap", device=lv_swap.path, + device_instance=lv_swap, + exists=True) + self.storage.devicetree._addDevice(lv_swap) + + def testActions(self, *args, **kwargs): + """ Verify correct management of actions. + + - action creation/registration/cancellation + - ActionCreateDevice adds device to tree + - ActionDestroyDevice removes device from tree + - ActionCreateFormat sets device.format in tree + - ActionDestroyFormat unsets device.format in tree + - cancelled action's registration side-effects reversed + - failure to register destruction of non-leaf device + - failure to register creation of device already in tree? + - failure to register destruction of device not in tree? + + - action pruning + - non-existent-device create..destroy cycles removed + - all actions on this device should get removed + - all actions pruned from to-be-destroyed devices + - resize, format, migrate, &c + - redundant resize/migrate/format actions pruned + - last one registered stays + + - action sorting + - destroy..resize..migrate..create + - creation + - leaves-last, including formatting + - destruction + - leaves-first + """ + devicetree = self.storage.devicetree + + # clear the disks + self.destroyAllDevices() + self.assertEqual(devicetree.getDevicesByType("lvmlv"), []) + self.assertEqual(devicetree.getDevicesByType("lvmvg"), []) + self.assertEqual(devicetree.getDevicesByType("partition"), []) + + sda = devicetree.getDeviceByName("sda") + self.assertNotEqual(sda, None, "failed to find disk 'sda'") + + sda1 = self.newDevice(device_class=PartitionDevice, + name="sda1", size=500, parents=[sda]) + self.scheduleCreateDevice(device=sda1) + + sda2 = self.newDevice(device_class=PartitionDevice, + name="sda2", size=100000, parents=[sda]) + self.scheduleCreateDevice(device=sda2) + format = self.newFormat("lvmpv", device=sda2.path) + self.scheduleCreateFormat(device=sda2, format=format) + + vg = self.newDevice(device_class=LVMVolumeGroupDevice, + name="vg", parents=[sda2]) + self.scheduleCreateDevice(device=vg) + + lv_root = self.newDevice(device_class=LVMLogicalVolumeDevice, + name="lv_root", vgdev=vg, size=60000) + self.scheduleCreateDevice(device=lv_root) + format = self.newFormat("ext4", device=lv_root.path, mountpoint="/") + self.scheduleCreateFormat(device=lv_root, format=format) + + lv_swap = self.newDevice(device_class=LVMLogicalVolumeDevice, + name="lv_swap", vgdev=vg, size=4000) + self.scheduleCreateDevice(device=lv_swap) + format = self.newFormat("swap", device=lv_swap.path) + self.scheduleCreateFormat(device=lv_swap, format=format) + + sda3 = self.newDevice(device_class=PartitionDevice, + name="sda3", parents=[sda], size=40000) + self.scheduleCreateDevice(device=sda3) + format = self.newFormat("mdmember", device=sda3.path) + self.scheduleCreateFormat(device=sda3, format=format) + + sdb = devicetree.getDeviceByName("sdb") + self.assertNotEqual(sdb, None, "failed to find disk 'sdb'") + + sdb1 = self.newDevice(device_class=PartitionDevice, + name="sdb1", parents=[sdb], size=40000) + self.scheduleCreateDevice(device=sdb1) + format = self.newFormat("mdmember", device=sdb1.path,) + self.scheduleCreateFormat(device=sdb1, format=format) + + md0 = self.newDevice(device_class=MDRaidArrayDevice, + name="md0", level="raid0", minor=0, size=80000, + memberDevices=2, totalDevices=2, + parents=[sdb1, sda3]) + self.scheduleCreateDevice(device=md0) + + format = self.newFormat("ext4", device=md0.path, mountpoint="/home") + self.scheduleCreateFormat(device=md0, format=format) + + format = self.newFormat("ext4", mountpoint="/boot", device=sda1.path) + self.scheduleCreateFormat(device=sda1, format=format) + + def testActionCreation(self, *args, **kwargs): + """ Verify correct operation of action class constructors. """ + # instantiation of device resize action for non-existent device should + # fail + # XXX resizable depends on existence, so this is covered implicitly + sdd = self.storage.devicetree.getDeviceByName("sdd") + p = self.newDevice(device_class=PartitionDevice, + name="sdd1", size=32768, parents=[sdd]) + self.failUnlessRaises(ValueError, + storage.deviceaction.ActionResizeDevice, + p, + p.size + 7232) + + # instantiation of device resize action for non-resizable device + # should fail + vg = self.storage.devicetree.getDeviceByName("VolGroup") + self.assertNotEqual(vg, None) + self.failUnlessRaises(ValueError, + storage.deviceaction.ActionResizeDevice, + vg, + vg.size + 32) + + # instantiation of format resize action for non-resizable format type + # should fail + lv_swap = self.storage.devicetree.getDeviceByName("VolGroup-lv_swap") + self.assertNotEqual(lv_swap, None) + self.failUnlessRaises(ValueError, + storage.deviceaction.ActionResizeFormat, + lv_swap, + lv_swap.size + 32) + + # instantiation of format resize action for non-existent format + # should fail + lv_root = self.storage.devicetree.getDeviceByName("VolGroup-lv_root") + self.assertNotEqual(lv_root, None) + lv_root.format.exists = False + self.failUnlessRaises(ValueError, + storage.deviceaction.ActionResizeFormat, + lv_root, + lv_root.size - 1000) + lv_root.format.exists = True + + # instantiation of format migrate action for non-migratable format + # type should fail + lv_swap = self.storage.devicetree.getDeviceByName("VolGroup-lv_swap") + self.assertNotEqual(lv_swap, None) + self.assertEqual(lv_swap.exists, True) + self.failUnlessRaises(ValueError, + storage.deviceaction.ActionMigrateFormat, + lv_swap) + + # instantiation of format migrate for non-existent format should fail + lv_root = self.storage.devicetree.getDeviceByName("VolGroup-lv_root") + self.assertNotEqual(lv_root, None) + orig_format = lv_root.format + lv_root.format = getFormat("ext3", device=lv_root.path) + self.failUnlessRaises(ValueError, + storage.deviceaction.ActionMigrateFormat, + lv_root) + lv_root.format = orig_format + + # instantiation of device create action for existing device should + # fail + lv_swap = self.storage.devicetree.getDeviceByName("VolGroup-lv_swap") + self.assertNotEqual(lv_swap, None) + self.assertEqual(lv_swap.exists, True) + self.failUnlessRaises(ValueError, + storage.deviceaction.ActionCreateDevice, + lv_swap) + + # instantiation of format destroy action for device causes device's + # format attribute to be a DeviceFormat instance + lv_swap = self.storage.devicetree.getDeviceByName("VolGroup-lv_swap") + self.assertNotEqual(lv_swap, None) + orig_format = lv_swap.format + self.assertEqual(lv_swap.format.type, "swap") + a = storage.deviceaction.ActionDestroyFormat(lv_swap) + self.assertEqual(lv_swap.format.type, None) + + # instantiation of format create action for device causes new format + # to be accessible via device's format attribute + new_format = getFormat("vfat", device=lv_swap.path) + a = storage.deviceaction.ActionCreateFormat(lv_swap, new_format) + self.assertEqual(lv_swap.format, new_format) + lv_swap.format = orig_format + + def testActionRegistration(self, *args, **kwargs): + """ Verify correct operation of action registration and cancelling. """ + # self.setUp has just been run, so we should have something like + # a preexisting autopart config in the devicetree. + + # registering a destroy action for a non-leaf device should fail + vg = self.storage.devicetree.getDeviceByName("VolGroup") + self.assertNotEqual(vg, None) + self.assertEqual(vg.isleaf, False) + a = storage.deviceaction.ActionDestroyDevice(vg) + self.failUnlessRaises(ValueError, + self.storage.devicetree.registerAction, + a) + + # registering any action other than create for a device that's not in + # the devicetree should fail + sdc = self.storage.devicetree.getDeviceByName("sdc") + self.assertNotEqual(sdc, None) + sdc1 = self.newDevice(device_class=PartitionDevice, + name="sdc1", size=100000, parents=[sdc], + exists=True) + + sdc1_format = self.newFormat("ext2", device=sdc1.path, mountpoint="/") + create_sdc1_format = ActionCreateFormat(sdc1, sdc1_format) + self.failUnlessRaises(storage.errors.DeviceTreeError, + self.storage.devicetree.registerAction, + create_sdc1_format) + + sdc1_format.exists = True + + migrate_sdc1 = ActionMigrateFormat(sdc1) + self.failUnlessRaises(storage.errors.DeviceTreeError, + self.storage.devicetree.registerAction, + migrate_sdc1) + migrate_sdc1.cancel() + + resize_sdc1_format = ActionResizeFormat(sdc1, sdc1.size - 10000) + self.failUnlessRaises(storage.errors.DeviceTreeError, + self.storage.devicetree.registerAction, + resize_sdc1_format) + + resize_sdc1 = ActionResizeDevice(sdc1, sdc1.size - 10000) + self.failUnlessRaises(storage.errors.DeviceTreeError, + self.storage.devicetree.registerAction, + resize_sdc1) + + resize_sdc1.cancel() + resize_sdc1_format.cancel() + + destroy_sdc1_format = ActionDestroyFormat(sdc1) + self.failUnlessRaises(storage.errors.DeviceTreeError, + self.storage.devicetree.registerAction, + destroy_sdc1_format) + + + destroy_sdc1 = ActionDestroyDevice(sdc1) + self.failUnlessRaises(storage.errors.DeviceTreeError, + self.storage.devicetree.registerAction, + resize_sdc1) + + # registering a device destroy action should cause the device to be + # removed from the devicetree + lv_root = self.storage.devicetree.getDeviceByName("VolGroup-lv_root") + self.assertNotEqual(lv_root, None) + a = ActionDestroyDevice(lv_root) + self.storage.devicetree.registerAction(a) + lv_root = self.storage.devicetree.getDeviceByName("VolGroup-lv_root") + self.assertEqual(lv_root, None) + self.storage.devicetree.cancelAction(a) + + # registering a device create action should cause the device to be + # added to the devicetree + sdd = self.storage.devicetree.getDeviceByName("sdd") + self.assertNotEqual(sdd, None) + sdd1 = self.storage.devicetree.getDeviceByName("sdd1") + self.assertEqual(sdd1, None) + sdd1 = self.newDevice(device_class=PartitionDevice, + name="sdd1", size=100000, parents=[sdd]) + a = ActionCreateDevice(sdd1) + self.storage.devicetree.registerAction(a) + sdd1 = self.storage.devicetree.getDeviceByName("sdd1") + self.assertNotEqual(sdd1, None) + + def testActionObsoletes(self, *args, **kwargs): + """ Verify correct operation of DeviceAction.obsoletes. """ + self.destroyAllDevices(disks=["sdc"]) + sdc = self.storage.devicetree.getDeviceByName("sdc") + self.assertNotEqual(sdc, None) + + sdc1 = self.newDevice(device_class=PartitionDevice, + name="sdc1", parents=[sdc], size=40000) + + # ActionCreateDevice + # + # - obsoletes other ActionCreateDevice instances w/ lower id and same + # device + create_device_1 = ActionCreateDevice(sdc1) + create_device_2 = ActionCreateDevice(sdc1) + self.assertEqual(create_device_2.obsoletes(create_device_1), True) + self.assertEqual(create_device_1.obsoletes(create_device_2), False) + + # ActionCreateFormat + # + # - obsoletes other ActionCreateFormat instances w/ lower id and same + # device + format_1 = self.newFormat("ext3", mountpoint="/home", device=sdc1.path) + format_2 = self.newFormat("ext3", mountpoint="/opt", device=sdc1.path) + create_format_1 = ActionCreateFormat(sdc1, format_1) + create_format_2 = ActionCreateFormat(sdc1, format_2) + self.assertEqual(create_format_2.obsoletes(create_format_1), True) + self.assertEqual(create_format_1.obsoletes(create_format_2), False) + + # ActionMigrateFormat + # + # - obsoletes other ActionMigrateFormat instances w/ lower id and same + # device + sdc1.format = self.newFormat("ext2", mountpoint="/", device=sdc1.path, + device_instance=sdc1, + exists=True) + migrate_1 = ActionMigrateFormat(sdc1) + migrate_2 = ActionMigrateFormat(sdc1) + self.assertEqual(migrate_2.obsoletes(migrate_1), True) + self.assertEqual(migrate_1.obsoletes(migrate_2), False) + + # ActionResizeFormat + # + # - obsoletes other ActionResizeFormat instances w/ lower id and same + # device + resize_format_1 = ActionResizeFormat(sdc1, sdc1.size - 1000) + resize_format_2 = ActionResizeFormat(sdc1, sdc1.size - 5000) + self.assertEqual(resize_format_2.obsoletes(resize_format_1), True) + self.assertEqual(resize_format_1.obsoletes(resize_format_2), False) + + # ActionCreateFormat + # + # - obsoletes migrate, resize format actions w/ lower id on same device + new_format = self.newFormat("ext4", mountpoint="/foo", device=sdc1.path) + create_format_3 = ActionCreateFormat(sdc1, new_format) + self.assertEqual(create_format_3.obsoletes(resize_format_1), True) + self.assertEqual(create_format_3.obsoletes(resize_format_2), True) + self.assertEqual(create_format_3.obsoletes(migrate_1), True) + self.assertEqual(create_format_3.obsoletes(migrate_2), True) + + # ActionResizeDevice + # + # - obsoletes other ActionResizeDevice instances w/ lower id and same + # device + sdc1.exists = True + sdc1.format.exists = True + resize_device_1 = ActionResizeDevice(sdc1, sdc1.size + 10000) + resize_device_2 = ActionResizeDevice(sdc1, sdc1.size - 10000) + self.assertEqual(resize_device_2.obsoletes(resize_device_1), True) + self.assertEqual(resize_device_1.obsoletes(resize_device_2), False) + sdc1.exists = False + sdc1.format.exists = False + + # ActionDestroyFormat + # + # - obsoletes all format actions w/ lower id on same device (including + # self if format does not exist) + destroy_format_1 = ActionDestroyFormat(sdc1) + self.assertEqual(destroy_format_1.obsoletes(create_format_1), True) + self.assertEqual(destroy_format_1.obsoletes(migrate_2), True) + self.assertEqual(destroy_format_1.obsoletes(resize_format_1), True) + self.assertEqual(destroy_format_1.obsoletes(destroy_format_1), True) + + # ActionDestroyDevice + # + # - obsoletes all actions w/ lower id that act on the same non-existent + # device (including self) + # sdc1 does not exist + destroy_sdc1 = ActionDestroyDevice(sdc1) + self.assertEqual(destroy_sdc1.obsoletes(create_format_2), True) + self.assertEqual(destroy_sdc1.obsoletes(migrate_1), True) + self.assertEqual(destroy_sdc1.obsoletes(resize_format_2), True) + self.assertEqual(destroy_sdc1.obsoletes(create_device_1), True) + self.assertEqual(destroy_sdc1.obsoletes(resize_device_1), True) + self.assertEqual(destroy_sdc1.obsoletes(destroy_sdc1), True) + + # ActionDestroyDevice + # + # - obsoletes all but ActionDestroyFormat actions w/ lower id on the + # same existing device + # sda1 exists + sda1 = self.storage.devicetree.getDeviceByName("sda1") + self.assertNotEqual(sda1, None) + resize_sda1_format = ActionResizeFormat(sda1, sda1.size - 50) + resize_sda1 = ActionResizeDevice(sda1, sda1.size - 50) + destroy_sda1_format = ActionDestroyFormat(sda1) + destroy_sda1 = ActionDestroyDevice(sda1) + self.assertEqual(destroy_sda1.obsoletes(resize_sda1_format), True) + self.assertEqual(destroy_sda1.obsoletes(resize_sda1), True) + self.assertEqual(destroy_sda1.obsoletes(destroy_sda1), False) + self.assertEqual(destroy_sda1.obsoletes(destroy_sda1_format), False) + + def testActionPruning(self, *args, **kwargs): + """ Verify correct functioning of action pruning. """ + self.destroyAllDevices() + + sda = self.storage.devicetree.getDeviceByName("sda") + self.assertNotEqual(sda, None, "failed to find disk 'sda'") + + sda1 = self.newDevice(device_class=PartitionDevice, + name="sda1", size=500, parents=[sda]) + self.scheduleCreateDevice(device=sda1) + + sda2 = self.newDevice(device_class=PartitionDevice, + name="sda2", size=100000, parents=[sda]) + self.scheduleCreateDevice(device=sda2) + format = self.newFormat("lvmpv", device=sda2.path) + self.scheduleCreateFormat(device=sda2, format=format) + + vg = self.newDevice(device_class=LVMVolumeGroupDevice, + name="vg", parents=[sda2]) + self.scheduleCreateDevice(device=vg) + + lv_root = self.newDevice(device_class=LVMLogicalVolumeDevice, + name="lv_root", vgdev=vg, size=60000) + self.scheduleCreateDevice(device=lv_root) + format = self.newFormat("ext4", device=lv_root.path, mountpoint="/") + self.scheduleCreateFormat(device=lv_root, format=format) + + lv_swap = self.newDevice(device_class=LVMLogicalVolumeDevice, + name="lv_swap", vgdev=vg, size=4000) + self.scheduleCreateDevice(device=lv_swap) + format = self.newFormat("swap", device=lv_swap.path) + self.scheduleCreateFormat(device=lv_swap, format=format) + + # we'll soon schedule destroy actions for these members and the array, + # which will test pruning. the whole mess should reduce to nothing + sda3 = self.newDevice(device_class=PartitionDevice, + name="sda3", parents=[sda], size=40000) + self.scheduleCreateDevice(device=sda3) + format = self.newFormat("mdmember", device=sda3.path) + self.scheduleCreateFormat(device=sda3, format=format) + + sdb = self.storage.devicetree.getDeviceByName("sdb") + self.assertNotEqual(sdb, None, "failed to find disk 'sdb'") + + sdb1 = self.newDevice(device_class=PartitionDevice, + name="sdb1", parents=[sdb], size=40000) + self.scheduleCreateDevice(device=sdb1) + format = self.newFormat("mdmember", device=sdb1.path,) + self.scheduleCreateFormat(device=sdb1, format=format) + + md0 = self.newDevice(device_class=MDRaidArrayDevice, + name="md0", level="raid0", minor=0, size=80000, + memberDevices=2, totalDevices=2, + parents=[sdb1, sda3]) + self.scheduleCreateDevice(device=md0) + + format = self.newFormat("ext4", device=md0.path, mountpoint="/home") + self.scheduleCreateFormat(device=md0, format=format) + + # now destroy the md and its components + self.scheduleDestroyFormat(device=md0) + self.scheduleDestroyDevice(device=md0) + self.scheduleDestroyDevice(device=sdb1) + self.scheduleDestroyDevice(device=sda3) + + format = self.newFormat("ext4", mountpoint="/boot", device=sda1.path) + self.scheduleCreateFormat(device=sda1, format=format) + + # verify the md actions are present prior to pruning + md0_actions = self.storage.devicetree.findActions(devid=md0.id) + self.assertNotEqual(len(md0_actions), 0) + + sdb1_actions = self.storage.devicetree.findActions(devid=sdb1.id) + self.assertNotEqual(len(sdb1_actions), 0) + + sda3_actions = self.storage.devicetree.findActions(devid=sda3.id) + self.assertNotEqual(len(sda3_actions), 0) + + self.storage.devicetree.pruneActions() + + # verify the md actions are gone after pruning + md0_actions = self.storage.devicetree.findActions(devid=md0.id) + self.assertEqual(len(md0_actions), 0) + + sdb1_actions = self.storage.devicetree.findActions(devid=sdb1.id) + self.assertEqual(len(sdb1_actions), 0) + + sda3_actions = self.storage.devicetree.findActions(sda3.id) + self.assertEqual(len(sda3_actions), 0) + + def testActionDependencies(self, *args, **kwargs): + """ Verify correct functioning of action dependencies. """ + # ActionResizeDevice + # an action that shrinks a device should require the action that + # shrinks the device's format + lv_root = self.storage.devicetree.getDeviceByName("VolGroup-lv_root") + self.assertNotEqual(lv_root, None) + shrink_format = ActionResizeFormat(lv_root, lv_root.size - 5000) + shrink_device = ActionResizeDevice(lv_root, lv_root.size - 5000) + self.assertEqual(shrink_device.requires(shrink_format), True) + self.assertEqual(shrink_format.requires(shrink_device), False) + shrink_format.cancel() + shrink_device.cancel() + + # ActionResizeDevice + # an action that grows a format should require the action that + # grows the device + orig_size = lv_root.currentSize + grow_device = ActionResizeDevice(lv_root, orig_size + 100) + grow_format = ActionResizeFormat(lv_root, orig_size + 100) + self.assertEqual(grow_format.requires(grow_device), True) + self.assertEqual(grow_device.requires(grow_format), False) + + # create something like uncommitted autopart + self.destroyAllDevices() + sda = self.storage.devicetree.getDeviceByName("sda") + sdb = self.storage.devicetree.getDeviceByName("sdb") + sda1 = self.newDevice(device_class=PartitionDevice, + name="sda1", size=500, parents=[sda]) + sda1_format = self.newFormat("ext4", mountpoint="/boot", + device=sda1.path) + self.scheduleCreateDevice(device=sda1) + self.scheduleCreateFormat(device=sda1, format=sda1_format) + + sda2 = self.newDevice(device_class=PartitionDevice, + name="sda2", size=99500, parents=[sda]) + sda2_format = self.newFormat("lvmpv", device=sda1.path) + self.scheduleCreateDevice(device=sda2) + self.scheduleCreateFormat(device=sda2, format=sda2_format) + + sdb1 = self.newDevice(device_class=PartitionDevice, + name="sdb1", size=100000, parents=[sdb]) + sdb1_format = self.newFormat("lvmpv", device=sdb1.path) + self.scheduleCreateDevice(device=sdb1) + self.scheduleCreateFormat(device=sdb1, format=sdb1_format) + + vg = self.newDevice(device_class=LVMVolumeGroupDevice, + name="VolGroup", parents=[sda2, sdb1]) + self.scheduleCreateDevice(device=vg) + + lv_root = self.newDevice(device_class=LVMLogicalVolumeDevice, + name="lv_root", vgdev=vg, size=160000) + self.scheduleCreateDevice(device=lv_root) + format = self.newFormat("ext4", device=lv_root.path, mountpoint="/") + self.scheduleCreateFormat(device=lv_root, format=format) + + lv_swap = self.newDevice(device_class=LVMLogicalVolumeDevice, + name="lv_swap", vgdev=vg, size=4000) + self.scheduleCreateDevice(device=lv_swap) + format = self.newFormat("swap", device=lv_swap.path) + self.scheduleCreateFormat(device=lv_swap, format=format) + + # ActionCreateDevice + # creation of an LV should require the actions that create the VG, + # its PVs, and the devices that contain the PVs + lv_root = self.storage.devicetree.getDeviceByName("VolGroup-lv_root") + self.assertNotEqual(lv_root, None) + actions = self.storage.devicetree.findActions(type="create", + object="device", + device=lv_root) + self.assertEqual(len(actions), 1, + "wrong number of device create actions for lv_root: " + "%d" % len(actions)) + create_lv_action = actions[0] + + vgs = [d for d in self.storage.vgs if d.name == "VolGroup"] + self.assertNotEqual(vgs, []) + vg = vgs[0] + actions = self.storage.devicetree.findActions(type="create", + object="device", + device=vg) + self.assertEqual(len(actions), 1, + "wrong number of device create actions for VolGroup") + create_vg_action = actions[0] + + self.assertEqual(create_lv_action.requires(create_vg_action), True) + + create_pv_actions = [] + pvs = [d for d in self.storage.pvs if d in vg.pvs] + self.assertNotEqual(pvs, []) + for pv in pvs: + # include device and format create actions for each pv + actions = self.storage.devicetree.findActions(type="create", + device=pv) + self.assertEqual(len(actions), 2, + "wrong number of device create actions for " + "pv %s" % pv.name) + create_pv_actions.append(actions[0]) + + for pv_action in create_pv_actions: + self.assertEqual(create_lv_action.requires(pv_action), True) + # also check that the vg create action requires the pv actions + self.assertEqual(create_vg_action.requires(pv_action), True) + + # ActionCreateDevice + # the higher numbered partition of two that are scheduled to be + # created on a single disk should require the action that creates the + # lower numbered of the two, eg: create sda2 before creating sda3 + sdc = self.storage.devicetree.getDeviceByName("sdc") + self.assertNotEqual(sdc, None) + + sdc1 = self.newDevice(device_class=PartitionDevice, + name="sdc1", parents=[sdc], size=50000) + create_sdc1 = self.scheduleCreateDevice(device=sdc1) + self.assertEqual(isinstance(create_sdc1, ActionCreateDevice), True) + + sdc2 = self.newDevice(device_class=PartitionDevice, + name="sdc2", parents=[sdc], size=50000) + create_sdc2 = self.scheduleCreateDevice(device=sdc2) + self.assertEqual(isinstance(create_sdc2, ActionCreateDevice), True) + + self.assertEqual(create_sdc2.requires(create_sdc1), True) + self.assertEqual(create_sdc1.requires(create_sdc2), False) + + # ActionCreateDevice + # actions that create partitions on two separate disks should not + # require each other, regardless of the partitions' numbers + sda1 = self.storage.devicetree.getDeviceByName("sda1") + self.assertNotEqual(sda1, None) + actions = self.storage.devicetree.findActions(type="create", + object="device", + device=sda1) + self.assertEqual(len(actions), 1, + "wrong number of create actions found for sda1") + create_sda1 = actions[0] + self.assertEqual(create_sdc2.requires(create_sda1), False) + self.assertEqual(create_sda1.requires(create_sdc1), False) + + # ActionDestroyDevice + # an action that destroys a device containing an mdmember format + # should require the action that destroys the md array it is a + # member of if an array is defined + self.destroyAllDevices(disks=["sdc", "sdd"]) + sdc = self.storage.devicetree.getDeviceByName("sdc") + self.assertNotEqual(sdc, None) + sdd = self.storage.devicetree.getDeviceByName("sdd") + self.assertNotEqual(sdd, None) + + sdc1 = self.newDevice(device_class=PartitionDevice, + name="sdc1", parents=[sdc], size=40000) + self.scheduleCreateDevice(device=sdc1) + format = self.newFormat("mdmember", device=sdc1.path) + self.scheduleCreateFormat(device=sdc1, format=format) + + sdd1 = self.newDevice(device_class=PartitionDevice, + name="sdd1", parents=[sdd], size=40000) + self.scheduleCreateDevice(device=sdd1) + format = self.newFormat("mdmember", device=sdd1.path,) + self.scheduleCreateFormat(device=sdd1, format=format) + + md0 = self.newDevice(device_class=MDRaidArrayDevice, + name="md0", level="raid0", minor=0, size=80000, + memberDevices=2, totalDevices=2, + parents=[sdc1, sdd1]) + self.scheduleCreateDevice(device=md0) + format = self.newFormat("ext4", device=md0.path, mountpoint="/home") + self.scheduleCreateFormat(device=md0, format=format) + + destroy_md0_format = self.scheduleDestroyFormat(device=md0) + destroy_md0 = self.scheduleDestroyDevice(device=md0) + destroy_members = [self.scheduleDestroyDevice(device=sdc1)] + destroy_members.append(self.scheduleDestroyDevice(device=sdd1)) + + for member in destroy_members: + # device and format destroy actions for md members should require + # both device and format destroy actions for the md array + for array in [destroy_md0_format, destroy_md0]: + self.assertEqual(member.requires(array), True) + + # ActionDestroyDevice + # when there are two actions that will each destroy a partition on the + # same disk, the action that will destroy the lower-numbered + # partition should require the action that will destroy the higher- + # numbered partition, eg: destroy sda2 before destroying sda1 + self.destroyAllDevices(disks=["sdc", "sdd"]) + sdc1 = self.newDevice(device_class=PartitionDevice, + name="sdc1", parents=[sdc], size=50000) + self.scheduleCreateDevice(device=sdc1) + + sdc2 = self.newDevice(device_class=PartitionDevice, + name="sdc2", parents=[sdc], size=40000) + self.scheduleCreateDevice(device=sdc2) + + destroy_sdc1 = self.scheduleDestroyDevice(device=sdc1) + destroy_sdc2 = self.scheduleDestroyDevice(device=sdc2) + self.assertEqual(destroy_sdc1.requires(destroy_sdc2), True) + self.assertEqual(destroy_sdc2.requires(destroy_sdc1), False) + + self.destroyAllDevices(disks=["sdc", "sdd"]) + sdc = self.storage.devicetree.getDeviceByName("sdc") + self.assertNotEqual(sdc, None) + sdd = self.storage.devicetree.getDeviceByName("sdd") + self.assertNotEqual(sdd, None) + + sdc1 = self.newDevice(device_class=PartitionDevice, + name="sdc1", parents=[sdc], size=50000) + create_pv = self.scheduleCreateDevice(device=sdc1) + format = self.newFormat("lvmpv", device=sdc1.path) + create_pv_format = self.scheduleCreateFormat(device=sdc1, format=format) + + testvg = self.newDevice(device_class=LVMVolumeGroupDevice, + name="testvg", parents=[sdc1], size=50000) + create_vg = self.scheduleCreateDevice(device=testvg) + testlv = self.newDevice(device_class=LVMLogicalVolumeDevice, + name="testlv", vgdev=testvg, size=30000) + create_lv = self.scheduleCreateDevice(device=testlv) + format = self.newFormat("ext4", device=testlv.path) + create_lv_format = self.scheduleCreateFormat(device=testlv, format=format) + + # ActionCreateFormat + # creation of a format on a non-existent device should require the + # action that creates the device + self.assertEqual(create_lv_format.requires(create_lv), True) + + # ActionCreateFormat + # an action that creates a format on a device should require an action + # that creates a device that the format's device depends on + self.assertEqual(create_lv_format.requires(create_pv), True) + self.assertEqual(create_lv_format.requires(create_vg), True) + + # ActionCreateFormat + # an action that creates a format on a device should require an action + # that creates a format on a device that the format's device depends on + self.assertEqual(create_lv_format.requires(create_pv_format), True) + + # XXX from here on, the devices are existing but not in the tree, so + # we instantiate and use actions directly + self.destroyAllDevices(disks=["sdc", "sdd"]) + sdc1 = self.newDevice(device_class=PartitionDevice, exists=True, + name="sdc1", parents=[sdc], size=50000) + sdc1.format = self.newFormat("lvmpv", device=sdc1.path, exists=True, + device_instance=sdc1) + testvg = self.newDevice(device_class=LVMVolumeGroupDevice, exists=True, + name="testvg", parents=[sdc1], size=50000) + testlv = self.newDevice(device_class=LVMLogicalVolumeDevice, + exists=True, + name="testlv", vgdev=testvg, size=30000) + testlv.format = self.newFormat("ext4", device=testlv.path, + exists=True, device_instance=testlv) + + # ActionResizeDevice + # an action that resizes a device should require an action that grows + # a device that the first action's device depends on, eg: grow + # device containing PV before resize of VG or LVs + tmp = sdc1.format + sdc1.format = None # since lvmpv format is not resizable + grow_pv = ActionResizeDevice(sdc1, sdc1.size + 10000) + grow_lv = ActionResizeDevice(testlv, testlv.size + 5000) + grow_lv_format = ActionResizeFormat(testlv, testlv.size + 5000) + + self.assertEqual(grow_lv.requires(grow_pv), True) + self.assertEqual(grow_pv.requires(grow_lv), False) + + # ActionResizeFormat + # an action that grows a format should require the action that grows + # the format's device + self.assertEqual(grow_lv_format.requires(grow_lv), True) + self.assertEqual(grow_lv.requires(grow_lv_format), False) + + # ActionResizeFormat + # an action that resizes a device's format should depend on an action + # that grows a device the first device depends on + self.assertEqual(grow_lv_format.requires(grow_pv), True) + self.assertEqual(grow_pv.requires(grow_lv_format), False) + + # ActionResizeFormat + # an action that resizes a device's format should depend on an action + # that grows a format on a device the first device depends on + # XXX resize of PV format is not allowed, so there's no real-life + # example of this to test + + grow_lv_format.cancel() + grow_lv.cancel() + grow_pv.cancel() + + # ActionResizeDevice + # an action that resizes a device should require an action that grows + # a format on a device that the first action's device depends on, eg: + # grow PV format before resize of VG or LVs + # XXX resize of PV format is not allowed, so there's no real-life + # example of this to test + + # ActionResizeDevice + # an action that resizes a device should require an action that + # shrinks a device that depends on the first action's device, eg: + # shrink LV before resizing VG or PV devices + shrink_lv = ActionResizeDevice(testlv, testlv.size - 10000) + shrink_pv = ActionResizeDevice(sdc1, sdc1.size - 5000) + + self.assertEqual(shrink_pv.requires(shrink_lv), True) + self.assertEqual(shrink_lv.requires(shrink_pv), False) + + # ActionResizeDevice + # an action that resizes a device should require an action that + # shrinks a format on a device that depends on the first action's + # device, eg: shrink LV format before resizing VG or PV devices + shrink_lv_format = ActionResizeFormat(testlv, testlv.size) + self.assertEqual(shrink_pv.requires(shrink_lv_format), True) + self.assertEqual(shrink_lv_format.requires(shrink_pv), False) + + # ActionResizeFormat + # an action that resizes a device's format should depend on an action + # that shrinks a device that depends on the first device + # XXX can't think of a real-world example of this since PVs and MD + # member devices are not resizable in anaconda + + # ActionResizeFormat + # an action that resizes a device's format should depend on an action + # that shrinks a format on a device that depends on the first device + # XXX can't think of a real-world example of this since PVs and MD + # member devices are not resizable in anaconda + + shrink_lv_format.cancel() + shrink_lv.cancel() + shrink_pv.cancel() + sdc1.format = tmp # restore pv's lvmpv format + + # ActionCreateFormat + # an action that creates a format on a device should require an action + # that resizes a device that the format's device depends on + # XXX Really? Is this always so? + + # ActionCreateFormat + # an action that creates a format on a device should require an action + # that resizes a format on a device that the format's device depends on + # XXX Same as above. + + # ActionCreateFormat + # an action that creates a format on a device should require an action + # that resizes the device that will contain the format + grow_lv = ActionResizeDevice(testlv, testlv.size + 1000) + format = self.newFormat("msdos", device=testlv.path) + format_lv = ActionCreateFormat(testlv, format) + self.assertEqual(format_lv.requires(grow_lv), True) + self.assertEqual(grow_lv.requires(format_lv), False) + + # ActionDestroyFormat + # an action that destroys a format should require an action that + # destroys a device that depends on the format's device + destroy_pv_format = ActionDestroyFormat(sdc1) + destroy_lv_format = ActionDestroyFormat(testlv) + destroy_lv = ActionDestroyDevice(testlv) + self.assertEqual(destroy_pv_format.requires(destroy_lv), True) + self.assertEqual(destroy_lv.requires(destroy_pv_format), False) + + # ActionDestroyFormat + # an action that destroys a format should require an action that + # destroys a format on a device that depends on the first format's + # device + self.assertEqual(destroy_pv_format.requires(destroy_lv_format), True) + self.assertEqual(destroy_lv_format.requires(destroy_pv_format), False) + + def testActionSorting(self, *args, **kwargs): + """ Verify correct functioning of action sorting. """ + pass + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(DeviceActionTestCase) + + +if __name__ == "__main__": + unittest.main() + -- 1.7.2.3 _______________________________________________ Anaconda-devel-list mailing list Anaconda-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/anaconda-devel-list