[PATCH 6/9] Add action classes for container member set management.

David Lehman dlehman at redhat.com
Thu Mar 20 17:14:10 UTC 2014


---
 blivet/deviceaction.py | 184 ++++++++++++++++++++++++++++++++++++++++++++++---
 tests/action_test.py   |  96 ++++++++++++++++++++++++++
 2 files changed, 271 insertions(+), 9 deletions(-)

diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py
index d9a6a87..569377a 100644
--- a/blivet/deviceaction.py
+++ b/blivet/deviceaction.py
@@ -1,8 +1,7 @@
 # deviceaction.py
-# Device modification action classes for anaconda's storage configuration
-# module.
+# Device modification action classes.
 #
-# Copyright (C) 2009  Red Hat, Inc.
+# Copyright (C) 2009-2014  Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -49,19 +48,25 @@ ACTION_TYPE_NONE = 0
 ACTION_TYPE_DESTROY = 1000
 ACTION_TYPE_RESIZE = 500
 ACTION_TYPE_CREATE = 100
+ACTION_TYPE_ADD = 50
+ACTION_TYPE_REMOVE = 10
 
 action_strings = {ACTION_TYPE_NONE: "None",
                   ACTION_TYPE_DESTROY: "Destroy",
                   ACTION_TYPE_RESIZE: "Resize",
-                  ACTION_TYPE_CREATE: "Create"}
+                  ACTION_TYPE_CREATE: "Create",
+                  ACTION_TYPE_ADD: "Add",
+                  ACTION_TYPE_REMOVE: "Remove"}
 
 ACTION_OBJECT_NONE = 0
 ACTION_OBJECT_FORMAT = 1
 ACTION_OBJECT_DEVICE = 2
+ACTION_OBJECT_CONTAINER = 3
 
 object_strings = {ACTION_OBJECT_NONE: "None",
                   ACTION_OBJECT_FORMAT: "Format",
-                  ACTION_OBJECT_DEVICE: "Device"}
+                  ACTION_OBJECT_DEVICE: "Device",
+                  ACTION_OBJECT_CONTAINER: "Container"}
 
 RESIZE_SHRINK = 88
 RESIZE_GROW = 89
@@ -148,6 +153,7 @@ class DeviceAction(util.ObjectID):
         if not isinstance(device, StorageDevice):
             raise ValueError("arg 1 must be a StorageDevice instance")
         self.device = device
+        self.container = getattr(self.device, "container", None)
 
     def execute(self):
         """ perform the action """
@@ -178,10 +184,22 @@ class DeviceAction(util.ObjectID):
         return (self.type == ACTION_TYPE_RESIZE and self.dir == RESIZE_GROW)
 
     @property
+    def isAdd(self):
+        return self.type == ACTION_TYPE_ADD
+
+    @property
+    def isRemove(self):
+        return self.type == ACTION_TYPE_REMOVE
+
+    @property
     def isDevice(self):
         return self.obj == ACTION_OBJECT_DEVICE
 
     @property
+    def isContainer(self):
+        return self.obj == ACTION_OBJECT_CONTAINER
+
+    @property
     def isFormat(self):
         return self.obj == ACTION_OBJECT_FORMAT
 
@@ -223,7 +241,7 @@ class DeviceAction(util.ObjectID):
         return _(self.typeDescStr)
 
     def __str__(self):
-        s = "[%d] %s %s" % (self.id, self.typeString, self.objectString)
+        s = "[%d] %s" % (self.id, self.typeDescStr)
         if self.isResize:
             s += " (%s)" % self.resizeString
         if self.isFormat:
@@ -273,6 +291,7 @@ class ActionCreateDevice(DeviceAction):
                 - this action's device depends on the other action's device
                 - both actions are partition create actions on the same disk
                   and this partition has a higher number
+                - the other action adds a member to this device's container
         """
         rc = False
         if self.device.dependsOn(action.device):
@@ -292,6 +311,9 @@ class ActionCreateDevice(DeviceAction):
               self.device.vg == action.device.vg and
               action.device.singlePV and not self.device.singlePV):
             rc = True
+        elif (action.isAdd and action.container == self.container):
+            rc = True
+
         return rc
 
 
@@ -325,6 +347,7 @@ class ActionDestroyDevice(DeviceAction):
                 - the other action's device depends on this action's device
                 - both actions are partition create actions on the same disk
                   and this partition has a lower number
+                - the other action removes this action's device from a container
         """
         rc = False
         if action.device.dependsOn(self.device) and action.isDestroy:
@@ -342,6 +365,8 @@ class ActionDestroyDevice(DeviceAction):
               action.device.id == self.device.id):
             # device destruction comes after destruction of device's format
             rc = True
+        elif (action.isRemove and action.device == self.device):
+            rc = True
         return rc
 
     def obsoletes(self, action):
@@ -352,6 +377,10 @@ class ActionDestroyDevice(DeviceAction):
 
             - obsoletes all but ActionDestroyFormat actions w/ lower id on the
               same device if device exists
+
+            - obsoletes all actions that add a member to this action's
+              (container) device
+
         """
         rc = False
         if action.device.id == self.device.id:
@@ -361,6 +390,10 @@ class ActionDestroyDevice(DeviceAction):
                  self.device.exists and \
                  not (action.isDestroy and action.isFormat):
                 rc = True
+            elif action.isAdd and (action.device == self.device):
+                rc = True
+        elif action.isAdd and (action.container == self.device):
+            rc = True
 
         return rc
 
@@ -413,6 +446,8 @@ class ActionResizeDevice(DeviceAction):
                   this action's device depends on
                 - the other action shrinks a device (or format it contains)
                   that depends on this action's device
+                - the other action removes this action's device from a container
+                - the other action adds a member to this device's container
         """
         retval = False
         if action.isResize:
@@ -424,6 +459,10 @@ class ActionResizeDevice(DeviceAction):
                 retval = True
             elif action.isShrink and action.device.dependsOn(self.device):
                 retval = True
+        elif (action.isRemove and action.device == self.device):
+            rc = True
+        elif (action.isAdd and action.container == self.container):
+            rc = True
 
         return retval
 
@@ -488,12 +527,14 @@ class ActionCreateFormat(DeviceAction):
             Format create action can require another action if:
 
                 - this action's device depends on the other action's device
-                  and the other action is not a device destroy action
+                  and the other action is not a device destroy action or a
+                  container action
                 - the other action is a create or resize of this action's
                   device
         """
         return ((self.device.dependsOn(action.device) and
-                 not (action.isDestroy and action.isDevice)) or
+                 not ((action.isDestroy and action.isDevice) or
+                      action.isContainer)) or
                 (action.isDevice and (action.isCreate or action.isResize) and
                  self.device.id == action.device.id))
 
@@ -545,8 +586,15 @@ class ActionDestroyFormat(DeviceAction):
 
                 - the other action's device depends on this action's device
                   and the other action is a destroy action
+                - the other action removes this action's device from a container
         """
-        return action.device.dependsOn(self.device) and action.isDestroy
+        retval = False
+        if action.device.dependsOn(self.device) and action.isDestroy:
+            retval = True
+        elif (action.isRemove and action.device == self.device):
+            rc = True
+
+        return retval
 
     def obsoletes(self, action):
         """ Return True if this action obsoletes action.
@@ -611,6 +659,7 @@ class ActionResizeFormat(DeviceAction):
                   that depends on this action's device
                 - the other action grows a device (or format) that this
                   action's device depends on
+                - the other action removes this action's device from a container
         """
         retval = False
         if action.isResize:
@@ -622,5 +671,122 @@ class ActionResizeFormat(DeviceAction):
                 retval = True
             elif action.isGrow and self.device.dependsOn(action.device):
                 retval = True
+        elif (action.isRemove and action.device == self.device):
+            rc = True
+
+        return retval
+
+
+class ActionAddMember(DeviceAction):
+    """ An action representing addition of a member device to a container. """
+    type = ACTION_TYPE_ADD
+    obj = ACTION_OBJECT_CONTAINER
+    typeDescStr = N_("add container member")
+
+    def __init__(self, container, device):
+        super(ActionAddMember, self).__init__(device)
+        self.container = container
+        container._addMember(device)
+
+    def cancel(self):
+        self.container._removeMember(self.device)
+
+    def execute(self):
+        self.container.add(self.device)
+
+    def requires(self, action):
+        """
+            requires
+                - create/resize the same device
+
+            required by
+                - any create/grow action on a device in the same container
+        """
+        retval = False
+        if ((action.isCreate or action.isResize) and
+            action.device == self.device):
+            retval = True
+
+        return retval
+
+    def obsoletes(self, action):
+        """
+            obsoletes
+                - remove same member from same container
+                - add same member to same container w/ higher id
+
+            obsoleted by
+                - destroy the container
+                - destroy the device
+                - remove same member from same container
+        """
+        retval = False
+        if (action.isRemove and
+            action.device == self.device and
+            action.container == self.container):
+            retval = True
+        elif (action.isAdd and
+              action.device == self.device and
+              action.container == self.container and
+              action.id > self.id):
+            retval = True
+
+        return retval
+
+
+class ActionRemoveMember(DeviceAction):
+    """ An action representing removal of a member device from a container. """
+    type = ACTION_TYPE_REMOVE
+    obj = ACTION_OBJECT_CONTAINER
+    typeDescStr = N_("remove container member")
+
+    def __init__(self, container, device):
+        super(ActionRemoveMember, self).__init__(device)
+        self.container = container
+        container._removeMember(device)
+
+    def cancel(self):
+        self.container._addMember(self.device)
+
+    def execute(self):
+        self.container.remove(self.device)
+
+    def requires(self, action):
+        """
+            requires
+                - any destroy/shrink action on a device in the same container
+                - any add action on this container
+
+            required by
+                - any destroy/resize action on the device
+        """
+        retval = False
+        if ((action.isShrink or action.isDestroy) and
+            action.device.container == self.container):
+            retval = True
+        elif action.isAdd and action.container == self.container:
+            retval = True
+
+        return retval
+
+    def obsoletes(self, action):
+        """
+            obsoletes
+                - add same member to same container
+                - remove same member from same container w/ higher id
+
+            obsoleted by
+                - add same member to same container
+        """
+        retval = False
+        if (action.isAdd and
+            action.device == self.device and
+            action.container == self.container):
+            retval = True
+        elif (action.isRemove and
+              action.device == self.device and
+              action.container == self.container and
+              action.id > self.id):
+            retval = True
 
         return retval
diff --git a/tests/action_test.py b/tests/action_test.py
index 9c1bac8..e063fba 100644
--- a/tests/action_test.py
+++ b/tests/action_test.py
@@ -914,6 +914,102 @@ class DeviceActionTestCase(StorageTestCase):
         self.assertEqual(destroy_pv_format.requires(destroy_lv_format), True)
         self.assertEqual(destroy_lv_format.requires(destroy_pv_format), False)
 
+    def testContainerActions(self):
+        self.destroyAllDevices()
+        sda = self.storage.devicetree.getDeviceByName("sda")
+        sdb = self.storage.devicetree.getDeviceByName("sdb")
+
+        #
+        # create something like an existing lvm autopart layout across two disks
+        #
+        sda1 = self.newDevice(device_class=PartitionDevice,
+                              exists=True, name="sda1", parents=[sda],
+                              size=Size(spec="500 MiB"))
+        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=Size(spec="99.5 GiB"), 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=Size(spec="99.999 GiB"), 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", parents=[vg],
+                                 size=Size(spec="160 GiB"), 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", parents=[vg],
+                                 size=Size(spec="4000 MiB"), exists=True)
+        lv_swap.format = self.newFormat("swap", device=lv_swap.path,
+                                        device_instance=lv_swap,
+                                        exists=True)
+        self.storage.devicetree._addDevice(lv_swap)
+
+        #
+        # test some modifications to the VG
+        #
+        sdc = self.storage.devicetree.getDeviceByName("sdc")
+        sdc1 = self.newDevice(device_class=PartitionDevice, name="sdc1",
+                              size=Size(spec="50 GiB"), parents=[sdc])
+        sdc1_format = self.newFormat("lvmpv", device=sdc1.path)
+        create_sdc1 = self.scheduleCreateDevice(device=sdc1)
+        create_sdc1_format = self.scheduleCreateFormat(device=sdc1,
+                                                       format=sdc1_format)
+
+        self.assertEqual(len(vg.parents), 2)
+
+        add_sdc1 = ActionAddMember(vg, sdc1)
+        self.assertEqual(len(vg.parents), 3)
+
+        self.assertEqual(add_sdc1.requires(create_sdc1), True)
+        self.assertEqual(add_sdc1.requires(create_sdc1_format), True)
+
+        new_lv = self.newDevice(device_class=LVMLogicalVolumeDevice,
+                                name="newlv", parents=[vg],
+                                size=Size(spec="20 GiB"))
+        create_new_lv = self.scheduleCreateDevice(device=new_lv)
+        new_lv_format = self.newFormat("xfs", device=new_lv.path)
+        create_new_lv_format = self.scheduleCreateFormat(device=new_lv,
+                                                         format=new_lv_format)
+
+        self.assertEqual(create_new_lv.requires(add_sdc1), True)
+        self.assertEqual(create_new_lv_format.requires(add_sdc1), False)
+
+        self.storage.devicetree.cancelAction(create_new_lv_format)
+        self.storage.devicetree.cancelAction(create_new_lv)
+
+        remove_sdb1 = ActionRemoveMember(vg, sdb1)
+        self.assertEqual(len(vg.parents), 2)
+
+        self.assertEqual(remove_sdb1.requires(add_sdc1), True)
+
+        vg._addMember(sdb1)
+        remove_sdb1_2 = ActionRemoveMember(vg, sdb1)
+        self.assertEqual(remove_sdb1_2.obsoletes(remove_sdb1), False)
+        self.assertEqual(remove_sdb1.obsoletes(remove_sdb1_2), True)
+
+        remove_sdc1 = ActionRemoveMember(vg, sdc1)
+        self.assertEqual(remove_sdc1.obsoletes(add_sdc1), True)
+        self.assertEqual(add_sdc1.obsoletes(remove_sdc1), True)
+
     def testActionSorting(self, *args, **kwargs):
         """ Verify correct functioning of action sorting. """
         pass
-- 
1.8.5.3



More information about the anaconda-patches mailing list