667f11
From 3f6bbf52442609b8e6e3919a3fdd8c5af64923e6 Mon Sep 17 00:00:00 2001
667f11
From: Vojtech Trefny <vtrefny@redhat.com>
667f11
Date: Tue, 12 May 2020 12:48:41 +0200
667f11
Subject: [PATCH 1/3] Add basic support for LVM VDO devices
667f11
667f11
This adds support for LVM VDO devices detection during populate
667f11
and allows removing both VDO LVs and VDO pools using actions.
667f11
---
667f11
 blivet/devices/lvm.py           | 150 +++++++++++++++++++++++++++++++-
667f11
 blivet/populator/helpers/lvm.py |  16 +++-
667f11
 tests/action_test.py            |  39 +++++++++
667f11
 tests/devices_test/lvm_test.py  |  34 ++++++++
667f11
 tests/storagetestcase.py        |  11 ++-
667f11
 5 files changed, 245 insertions(+), 5 deletions(-)
667f11
667f11
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
667f11
index 97de6acd..d9e24a33 100644
667f11
--- a/blivet/devices/lvm.py
667f11
+++ b/blivet/devices/lvm.py
667f11
@@ -1789,8 +1789,132 @@ class LVMThinLogicalVolumeMixin(object):
667f11
         data.pool_name = self.pool.lvname
667f11
 
667f11
 
667f11
+class LVMVDOPoolMixin(object):
667f11
+    def __init__(self):
667f11
+        self._lvs = []
667f11
+
667f11
+    @property
667f11
+    def is_vdo_pool(self):
667f11
+        return self.seg_type == "vdo-pool"
667f11
+
667f11
+    @property
667f11
+    def type(self):
667f11
+        return "lvmvdopool"
667f11
+
667f11
+    @property
667f11
+    def resizable(self):
667f11
+        return False
667f11
+
667f11
+    @util.requires_property("is_vdo_pool")
667f11
+    def _add_log_vol(self, lv):
667f11
+        """ Add an LV to this VDO pool. """
667f11
+        if lv in self._lvs:
667f11
+            raise ValueError("lv is already part of this VDO pool")
667f11
+
667f11
+        self.vg._add_log_vol(lv)
667f11
+        log.debug("Adding %s/%s to %s", lv.name, lv.size, self.name)
667f11
+        self._lvs.append(lv)
667f11
+
667f11
+    @util.requires_property("is_vdo_pool")
667f11
+    def _remove_log_vol(self, lv):
667f11
+        """ Remove an LV from this VDO pool. """
667f11
+        if lv not in self._lvs:
667f11
+            raise ValueError("specified lv is not part of this VDO pool")
667f11
+
667f11
+        self._lvs.remove(lv)
667f11
+        self.vg._remove_log_vol(lv)
667f11
+
667f11
+    @property
667f11
+    @util.requires_property("is_vdo_pool")
667f11
+    def lvs(self):
667f11
+        """ A list of this VDO pool's LVs """
667f11
+        return self._lvs[:]     # we don't want folks changing our list
667f11
+
667f11
+    @property
667f11
+    def direct(self):
667f11
+        """ Is this device directly accessible? """
667f11
+        return False
667f11
+
667f11
+    def _create(self):
667f11
+        """ Create the device. """
667f11
+        raise NotImplementedError
667f11
+
667f11
+
667f11
+class LVMVDOLogicalVolumeMixin(object):
667f11
+    def __init__(self):
667f11
+        pass
667f11
+
667f11
+    def _init_check(self):
667f11
+        pass
667f11
+
667f11
+    def _check_parents(self):
667f11
+        """Check that this device has parents as expected"""
667f11
+        if isinstance(self.parents, (list, ParentList)):
667f11
+            if len(self.parents) != 1:
667f11
+                raise ValueError("constructor requires a single vdo-pool LV")
667f11
+
667f11
+            container = self.parents[0]
667f11
+        else:
667f11
+            container = self.parents
667f11
+
667f11
+        if not container or not isinstance(container, LVMLogicalVolumeDevice) or not container.is_vdo_pool:
667f11
+            raise ValueError("constructor requires a vdo-pool LV")
667f11
+
667f11
+    @property
667f11
+    def vg_space_used(self):
667f11
+        return Size(0)    # the pool's size is already accounted for in the vg
667f11
+
667f11
+    @property
667f11
+    def is_vdo_lv(self):
667f11
+        return self.seg_type == "vdo"
667f11
+
667f11
+    @property
667f11
+    def vg(self):
667f11
+        # parents[0] is the pool, not the VG so set the VG here
667f11
+        return self.pool.vg
667f11
+
667f11
+    @property
667f11
+    def type(self):
667f11
+        return "vdolv"
667f11
+
667f11
+    @property
667f11
+    def resizable(self):
667f11
+        return False
667f11
+
667f11
+    @property
667f11
+    @util.requires_property("is_vdo_lv")
667f11
+    def pool(self):
667f11
+        return self.parents[0]
667f11
+
667f11
+    def _create(self):
667f11
+        """ Create the device. """
667f11
+        raise NotImplementedError
667f11
+
667f11
+    def _destroy(self):
667f11
+        # nothing to do here, VDO LV is destroyed automatically together with
667f11
+        # the VDO pool
667f11
+        pass
667f11
+
667f11
+    def remove_hook(self, modparent=True):
667f11
+        if modparent:
667f11
+            self.pool._remove_log_vol(self)
667f11
+
667f11
+        # pylint: disable=bad-super-call
667f11
+        super(LVMLogicalVolumeBase, self).remove_hook(modparent=modparent)
667f11
+
667f11
+    def add_hook(self, new=True):
667f11
+        # pylint: disable=bad-super-call
667f11
+        super(LVMLogicalVolumeBase, self).add_hook(new=new)
667f11
+        if new:
667f11
+            return
667f11
+
667f11
+        if self not in self.pool.lvs:
667f11
+            self.pool._add_log_vol(self)
667f11
+
667f11
+
667f11
 class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin, LVMSnapshotMixin,
667f11
-                             LVMThinPoolMixin, LVMThinLogicalVolumeMixin):
667f11
+                             LVMThinPoolMixin, LVMThinLogicalVolumeMixin, LVMVDOPoolMixin,
667f11
+                             LVMVDOLogicalVolumeMixin):
667f11
     """ An LVM Logical Volume """
667f11
 
667f11
     # generally resizable, see :property:`resizable` for details
667f11
@@ -1879,6 +2003,8 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
667f11
         LVMLogicalVolumeBase.__init__(self, name, parents, size, uuid, seg_type,
667f11
                                       fmt, exists, sysfs_path, grow, maxsize,
667f11
                                       percent, cache_request, pvs, from_lvs)
667f11
+        LVMVDOPoolMixin.__init__(self)
667f11
+        LVMVDOLogicalVolumeMixin.__init__(self)
667f11
 
667f11
         LVMInternalLogicalVolumeMixin._init_check(self)
667f11
         LVMSnapshotMixin._init_check(self)
667f11
@@ -1905,6 +2031,10 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
667f11
             ret.append(LVMThinPoolMixin)
667f11
         if self.is_thin_lv:
667f11
             ret.append(LVMThinLogicalVolumeMixin)
667f11
+        if self.is_vdo_pool:
667f11
+            ret.append(LVMVDOPoolMixin)
667f11
+        if self.is_vdo_lv:
667f11
+            ret.append(LVMVDOLogicalVolumeMixin)
667f11
         return ret
667f11
 
667f11
     def _try_specific_call(self, name, *args, **kwargs):
667f11
@@ -2066,6 +2196,11 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
667f11
     def display_lv_name(self):
667f11
         return self.lvname
667f11
 
667f11
+    @property
667f11
+    @type_specific
667f11
+    def pool(self):
667f11
+        return super(LVMLogicalVolumeDevice, self).pool
667f11
+
667f11
     def _setup(self, orig=False):
667f11
         """ Open, or set up, a device. """
667f11
         log_method_call(self, self.name, orig=orig, status=self.status,
667f11
@@ -2167,6 +2302,19 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
667f11
         udev.settle()
667f11
         blockdev.lvm.lvresize(self.vg.name, self._name, self.size)
667f11
 
667f11
+    @type_specific
667f11
+    def _add_log_vol(self, lv):
667f11
+        pass
667f11
+
667f11
+    @type_specific
667f11
+    def _remove_log_vol(self, lv):
667f11
+        pass
667f11
+
667f11
+    @property
667f11
+    @type_specific
667f11
+    def lvs(self):
667f11
+        return []
667f11
+
667f11
     @property
667f11
     @type_specific
667f11
     def direct(self):
667f11
diff --git a/blivet/populator/helpers/lvm.py b/blivet/populator/helpers/lvm.py
667f11
index 4b674fac..ff8bf59f 100644
667f11
--- a/blivet/populator/helpers/lvm.py
667f11
+++ b/blivet/populator/helpers/lvm.py
667f11
@@ -211,9 +211,6 @@ class LVMFormatPopulator(FormatPopulator):
667f11
                     origin = self._devicetree.get_device_by_name(origin_device_name)
667f11
 
667f11
                 lv_kwargs["origin"] = origin
667f11
-            elif lv_attr[0] == 'v':
667f11
-                # skip vorigins
667f11
-                return
667f11
             elif lv_attr[0] in 'IrielTCo' and lv_name.endswith(']'):
667f11
                 # an internal LV, add the an instance of the appropriate class
667f11
                 # to internal_lvs for later processing when non-internal LVs are
667f11
@@ -237,6 +234,19 @@ class LVMFormatPopulator(FormatPopulator):
667f11
                     origin = self._devicetree.get_device_by_name(origin_device_name)
667f11
                     lv_kwargs["origin"] = origin
667f11
 
667f11
+                lv_parents = [self._devicetree.get_device_by_name(pool_device_name)]
667f11
+            elif lv_attr[0] == 'd':
667f11
+                # vdo pool
667f11
+                # nothing to do here
667f11
+                pass
667f11
+            elif lv_attr[0] == 'v':
667f11
+                if lv_type != "vdo":
667f11
+                    # skip vorigins
667f11
+                    return
667f11
+                pool_name = blockdev.lvm.vdolvpoolname(vg_name, lv_name)
667f11
+                pool_device_name = "%s-%s" % (vg_name, pool_name)
667f11
+                add_required_lv(pool_device_name, "failed to look up VDO pool")
667f11
+
667f11
                 lv_parents = [self._devicetree.get_device_by_name(pool_device_name)]
667f11
             elif lv_name.endswith(']'):
667f11
                 # unrecognized Internal LVM2 device
667f11
diff --git a/tests/action_test.py b/tests/action_test.py
667f11
index 90c1b312..8f9a7424 100644
667f11
--- a/tests/action_test.py
667f11
+++ b/tests/action_test.py
667f11
@@ -1252,6 +1252,45 @@ class DeviceActionTestCase(StorageTestCase):
667f11
         self.assertEqual(set(self.storage.lvs), {pool})
667f11
         self.assertEqual(set(pool._internal_lvs), {lv1, lv2})
667f11
 
667f11
+    def test_lvm_vdo_destroy(self):
667f11
+        self.destroy_all_devices()
667f11
+        sdc = self.storage.devicetree.get_device_by_name("sdc")
667f11
+        sdc1 = self.new_device(device_class=PartitionDevice, name="sdc1",
667f11
+                               size=Size("50 GiB"), parents=[sdc],
667f11
+                               fmt=blivet.formats.get_format("lvmpv"))
667f11
+        self.schedule_create_device(sdc1)
667f11
+
667f11
+        vg = self.new_device(device_class=LVMVolumeGroupDevice,
667f11
+                             name="vg", parents=[sdc1])
667f11
+        self.schedule_create_device(vg)
667f11
+
667f11
+        pool = self.new_device(device_class=LVMLogicalVolumeDevice,
667f11
+                               name="data", parents=[vg],
667f11
+                               size=Size("10 GiB"),
667f11
+                               seg_type="vdo-pool", exists=True)
667f11
+        self.storage.devicetree._add_device(pool)
667f11
+        lv = self.new_device(device_class=LVMLogicalVolumeDevice,
667f11
+                             name="meta", parents=[pool],
667f11
+                             size=Size("50 GiB"),
667f11
+                             seg_type="vdo", exists=True)
667f11
+        self.storage.devicetree._add_device(lv)
667f11
+
667f11
+        remove_lv = self.schedule_destroy_device(lv)
667f11
+        self.assertListEqual(pool.lvs, [])
667f11
+        self.assertNotIn(lv, vg.lvs)
667f11
+
667f11
+        # cancelling the action should put lv back to both vg and pool lvs
667f11
+        self.storage.devicetree.actions.remove(remove_lv)
667f11
+        self.assertListEqual(pool.lvs, [lv])
667f11
+        self.assertIn(lv, vg.lvs)
667f11
+
667f11
+        # can't remove non-leaf pool
667f11
+        with self.assertRaises(ValueError):
667f11
+            self.schedule_destroy_device(pool)
667f11
+
667f11
+        self.schedule_destroy_device(lv)
667f11
+        self.schedule_destroy_device(pool)
667f11
+
667f11
 
667f11
 class ConfigurationActionsTest(unittest.TestCase):
667f11
 
667f11
diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py
667f11
index 9e701d18..204cb99a 100644
667f11
--- a/tests/devices_test/lvm_test.py
667f11
+++ b/tests/devices_test/lvm_test.py
667f11
@@ -405,6 +405,40 @@ class LVMDeviceTest(unittest.TestCase):
667f11
                                exists=False)
667f11
         self.assertFalse(vg.is_empty)
667f11
 
667f11
+    def test_lvm_vdo_pool(self):
667f11
+        pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
667f11
+                           size=Size("1 GiB"), exists=True)
667f11
+        vg = LVMVolumeGroupDevice("testvg", parents=[pv])
667f11
+        pool = LVMLogicalVolumeDevice("testpool", parents=[vg], size=Size("512 MiB"),
667f11
+                                      seg_type="vdo-pool", exists=True)
667f11
+        self.assertTrue(pool.is_vdo_pool)
667f11
+
667f11
+        free = vg.free_space
667f11
+        lv = LVMLogicalVolumeDevice("testlv", parents=[pool], size=Size("2 GiB"),
667f11
+                                    seg_type="vdo", exists=True)
667f11
+        self.assertTrue(lv.is_vdo_lv)
667f11
+        self.assertEqual(lv.vg, vg)
667f11
+        self.assertEqual(lv.pool, pool)
667f11
+
667f11
+        # free space in the vg shouldn't be affected by the vdo lv
667f11
+        self.assertEqual(lv.vg_space_used, 0)
667f11
+        self.assertEqual(free, vg.free_space)
667f11
+
667f11
+        self.assertListEqual(pool.lvs, [lv])
667f11
+
667f11
+        # now try to destroy both the pool and the vdo lv
667f11
+        # for the lv this should be a no-op, destroying the pool should destroy both
667f11
+        with patch("blivet.devices.lvm.blockdev.lvm") as lvm:
667f11
+            lv.destroy()
667f11
+            lv.remove_hook()
667f11
+            self.assertFalse(lv.exists)
667f11
+            self.assertFalse(lvm.lvremove.called)
667f11
+            self.assertListEqual(pool.lvs, [])
667f11
+
667f11
+            pool.destroy()
667f11
+            self.assertFalse(pool.exists)
667f11
+            self.assertTrue(lvm.lvremove.called)
667f11
+
667f11
 
667f11
 class TypeSpecificCallsTest(unittest.TestCase):
667f11
     def test_type_specific_calls(self):
667f11
diff --git a/tests/storagetestcase.py b/tests/storagetestcase.py
667f11
index e581bca6..1844dec5 100644
667f11
--- a/tests/storagetestcase.py
667f11
+++ b/tests/storagetestcase.py
667f11
@@ -96,7 +96,16 @@ class StorageTestCase(unittest.TestCase):
667f11
     def new_device(self, *args, **kwargs):
667f11
         """ Return a new Device instance suitable for testing. """
667f11
         device_class = kwargs.pop("device_class")
667f11
-        exists = kwargs.pop("exists", False)
667f11
+
667f11
+        # we intentionally don't pass the "exists" kwarg to the constructor
667f11
+        # becauses this causes issues with some devices (especially partitions)
667f11
+        # but we still need it for some LVs like VDO because we can't create
667f11
+        # those so we need to fake their existence even for the constructor
667f11
+        if device_class is blivet.devices.LVMLogicalVolumeDevice:
667f11
+            exists = kwargs.get("exists", False)
667f11
+        else:
667f11
+            exists = kwargs.pop("exists", False)
667f11
+
667f11
         part_type = kwargs.pop("part_type", parted.PARTITION_NORMAL)
667f11
         device = device_class(*args, **kwargs)
667f11
 
667f11
-- 
667f11
2.26.2
667f11
667f11
667f11
From f05a66e1bed1ca1f3cd7d7ffecd6693ab4d7f32a Mon Sep 17 00:00:00 2001
667f11
From: Vojtech Trefny <vtrefny@redhat.com>
667f11
Date: Tue, 12 May 2020 12:52:47 +0200
667f11
Subject: [PATCH 2/3] Fix checking for filesystem support in action_test
667f11
667f11
---
667f11
 tests/action_test.py | 2 +-
667f11
 1 file changed, 1 insertion(+), 1 deletion(-)
667f11
667f11
diff --git a/tests/action_test.py b/tests/action_test.py
667f11
index 8f9a7424..228eb97a 100644
667f11
--- a/tests/action_test.py
667f11
+++ b/tests/action_test.py
667f11
@@ -56,7 +56,7 @@ FORMAT_CLASSES = [
667f11
 
667f11
 
667f11
 @unittest.skipUnless(not any(x.unavailable_type_dependencies() for x in DEVICE_CLASSES), "some unsupported device classes required for this test")
667f11
-@unittest.skipUnless(not any(x().utils_available for x in FORMAT_CLASSES), "some unsupported format classes required for this test")
667f11
+@unittest.skipUnless(all(x().utils_available for x in FORMAT_CLASSES), "some unsupported format classes required for this test")
667f11
 class DeviceActionTestCase(StorageTestCase):
667f11
 
667f11
     """ DeviceActionTestSuite """
667f11
-- 
667f11
2.26.2
667f11
667f11
667f11
From 69bd2e69e21c8779377a6f54b3d83cb35138867a Mon Sep 17 00:00:00 2001
667f11
From: Vojtech Trefny <vtrefny@redhat.com>
667f11
Date: Tue, 12 May 2020 12:54:03 +0200
667f11
Subject: [PATCH 3/3] Fix LV min size for resize in test_action_dependencies
667f11
667f11
We've recently changed min size for all filesystems so we can't
667f11
resize the LV to the device minimal size.
667f11
This was overlooked in the original change because these tests
667f11
were skipped.
667f11
---
667f11
 tests/action_test.py | 2 +-
667f11
 1 file changed, 1 insertion(+), 1 deletion(-)
667f11
667f11
diff --git a/tests/action_test.py b/tests/action_test.py
667f11
index 228eb97a..77176f46 100644
667f11
--- a/tests/action_test.py
667f11
+++ b/tests/action_test.py
667f11
@@ -870,7 +870,7 @@ class DeviceActionTestCase(StorageTestCase):
667f11
                                   name="testlv2", parents=[testvg])
667f11
         testlv2.format = self.new_format("ext4", device=testlv2.path,
667f11
                                          exists=True, device_instance=testlv2)
667f11
-        shrink_lv2 = ActionResizeDevice(testlv2, testlv2.size - Size("10 GiB"))
667f11
+        shrink_lv2 = ActionResizeDevice(testlv2, testlv2.size - Size("10 GiB") + Ext4FS._min_size)
667f11
         shrink_lv2.apply()
667f11
 
667f11
         self.assertTrue(grow_lv.requires(shrink_lv2))
667f11
-- 
667f11
2.26.2
667f11