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