Blob Blame History Raw
From c20296b2df89a9edc4ea9cc41f94df89a8fbfd26 Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 20 Apr 2023 12:35:30 +0200
Subject: [PATCH] Add support for creating shared LVM setups

This feature is requested by GFS2 for the storage role. This adds
support for creating shared VGs and activating LVs in shared mode.

Resolves: RHEL-324
---
 blivet/devices/lvm.py                     | 44 +++++++++++++++++++----
 blivet/tasks/availability.py              |  9 +++++
 tests/unit_tests/devices_test/lvm_test.py | 25 +++++++++++++
 3 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
index ca45c4b5..068c5368 100644
--- a/blivet/devices/lvm.py
+++ b/blivet/devices/lvm.py
@@ -97,7 +97,8 @@ class LVMVolumeGroupDevice(ContainerDevice):
 
     def __init__(self, name, parents=None, size=None, free=None,
                  pe_size=None, pe_count=None, pe_free=None, pv_count=None,
-                 uuid=None, exists=False, sysfs_path='', exported=False):
+                 uuid=None, exists=False, sysfs_path='', exported=False,
+                 shared=False):
         """
             :param name: the device name (generally a device node's basename)
             :type name: str
@@ -124,6 +125,11 @@ class LVMVolumeGroupDevice(ContainerDevice):
             :type pv_count: int
             :keyword uuid: the VG UUID
             :type uuid: str
+
+            For non-existing VGs only:
+
+            :keyword shared: whether to create this VG as shared
+            :type shared: bool
         """
         # These attributes are used by _add_parent, so they must be initialized
         # prior to instantiating the superclass.
@@ -137,6 +143,7 @@ class LVMVolumeGroupDevice(ContainerDevice):
         self.pe_count = util.numeric_type(pe_count)
         self.pe_free = util.numeric_type(pe_free)
         self.exported = exported
+        self._shared = shared
 
         # TODO: validate pe_size if given
         if not self.pe_size:
@@ -254,7 +261,19 @@ class LVMVolumeGroupDevice(ContainerDevice):
         """ Create the device. """
         log_method_call(self, self.name, status=self.status)
         pv_list = [pv.path for pv in self.parents]
-        blockdev.lvm.vgcreate(self.name, pv_list, self.pe_size)
+        extra = dict()
+        if self._shared:
+            extra["shared"] = ""
+        blockdev.lvm.vgcreate(self.name, pv_list, self.pe_size, **extra)
+
+        if self._shared:
+            if availability.BLOCKDEV_LVM_PLUGIN_SHARED.available:
+                try:
+                    blockdev.lvm.vglock_start(self.name)
+                except blockdev.LVMError as err:
+                    raise errors.LVMError(err)
+            else:
+                raise errors.LVMError("Shared LVM is not fully supported: %s" % ",".join(availability.BLOCKDEV_LVM_PLUGIN_SHARED.availability_errors))
 
     def _post_create(self):
         self._complete = True
@@ -661,7 +680,7 @@ class LVMLogicalVolumeBase(DMDevice, RaidDevice):
     def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
                  fmt=None, exists=False, sysfs_path='', grow=None, maxsize=None,
                  percent=None, cache_request=None, pvs=None, from_lvs=None,
-                 stripe_size=0):
+                 stripe_size=0, shared=False):
 
         if not exists:
             if seg_type not in [None, "linear", "thin", "thin-pool", "cache", "vdo-pool", "vdo", "cache-pool"] + lvm.raid_seg_types:
@@ -690,6 +709,7 @@ class LVMLogicalVolumeBase(DMDevice, RaidDevice):
         self.seg_type = seg_type or "linear"
         self._raid_level = None
         self.ignore_skip_activation = 0
+        self._shared = shared
 
         self.req_grow = None
         self.req_max_size = Size(0)
@@ -2306,7 +2326,8 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
                  parent_lv=None, int_type=None, origin=None, vorigin=False,
                  metadata_size=None, chunk_size=None, profile=None, from_lvs=None,
                  compression=False, deduplication=False, index_memory=0,
-                 write_policy=None, cache_mode=None, attach_to=None, stripe_size=0):
+                 write_policy=None, cache_mode=None, attach_to=None, stripe_size=0,
+                 shared=False):
         """
             :param name: the device name (generally a device node's basename)
             :type name: str
@@ -2337,6 +2358,8 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
             :type cache_request: :class:`~.devices.lvm.LVMCacheRequest`
             :keyword pvs: list of PVs to allocate extents from (size could be specified for each PV)
             :type pvs: list of :class:`~.devices.StorageDevice` or :class:`LVPVSpec` objects (tuples)
+            :keyword shared: whether to activate the newly create LV in shared mode
+            :type shared: bool
 
             For internal LVs only:
 
@@ -2412,7 +2435,7 @@ 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,
-                                      stripe_size)
+                                      stripe_size, shared)
         LVMVDOPoolMixin.__init__(self, compression, deduplication, index_memory,
                                  write_policy)
         LVMVDOLogicalVolumeMixin.__init__(self)
@@ -2634,7 +2657,13 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
         log_method_call(self, self.name, orig=orig, status=self.status,
                         controllable=self.controllable)
         ignore_skip_activation = self.is_snapshot_lv or self.ignore_skip_activation > 0
-        blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=ignore_skip_activation)
+        if self._shared:
+            if availability.BLOCKDEV_LVM_PLUGIN_SHARED.available:
+                blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=ignore_skip_activation, shared=True)
+            else:
+                raise errors.LVMError("Shared LVM is not fully supported: %s" % ",".join(availability.BLOCKDEV_LVM_PLUGIN_SHARED.availability_errors))
+        else:
+            blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=ignore_skip_activation)
 
     @type_specific
     def _pre_create(self):
@@ -2672,6 +2701,9 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
             if self._stripe_size:
                 extra["stripesize"] = str(int(self._stripe_size.convert_to("KiB")))
 
+            if self._shared:
+                extra["activate"] = "sy"
+
             blockdev.lvm.lvcreate(self.vg.name, self._name, self.size,
                                   type=self.seg_type, pv_list=pvs, **extra)
         else:
diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py
index bba1ba84..85945c77 100644
--- a/blivet/tasks/availability.py
+++ b/blivet/tasks/availability.py
@@ -435,6 +435,14 @@ if hasattr(blockdev.LVMTech, "VDO"):
 else:
     BLOCKDEV_LVM_TECH_VDO = _UnavailableMethod(error_msg="Installed version of libblockdev doesn't support LVM VDO technology")
 
+if hasattr(blockdev.LVMTech, "SHARED"):
+    BLOCKDEV_LVM_SHARED = BlockDevTechInfo(plugin_name="lvm",
+                                           check_fn=blockdev.lvm_is_tech_avail,
+                                           technologies={blockdev.LVMTech.SHARED: blockdev.LVMTechMode.MODIFY})  # pylint: disable=no-member
+    BLOCKDEV_LVM_TECH_SHARED = BlockDevMethod(BLOCKDEV_LVM_SHARED)
+else:
+    BLOCKDEV_LVM_TECH_SHARED = _UnavailableMethod(error_msg="Installed version of libblockdev doesn't support shared LVM technology")
+
 # libblockdev mdraid plugin required technologies and modes
 BLOCKDEV_MD_ALL_MODES = (blockdev.MDTechMode.CREATE |
                          blockdev.MDTechMode.DELETE |
@@ -476,6 +484,7 @@ BLOCKDEV_DM_PLUGIN_RAID = blockdev_plugin("libblockdev dm plugin (raid technolog
 BLOCKDEV_LOOP_PLUGIN = blockdev_plugin("libblockdev loop plugin", BLOCKDEV_LOOP_TECH)
 BLOCKDEV_LVM_PLUGIN = blockdev_plugin("libblockdev lvm plugin", BLOCKDEV_LVM_TECH)
 BLOCKDEV_LVM_PLUGIN_VDO = blockdev_plugin("libblockdev lvm plugin (vdo technology)", BLOCKDEV_LVM_TECH_VDO)
+BLOCKDEV_LVM_PLUGIN_SHARED = blockdev_plugin("libblockdev lvm plugin (shared LVM technology)", BLOCKDEV_LVM_TECH_SHARED)
 BLOCKDEV_MDRAID_PLUGIN = blockdev_plugin("libblockdev mdraid plugin", BLOCKDEV_MD_TECH)
 BLOCKDEV_MPATH_PLUGIN = blockdev_plugin("libblockdev mpath plugin", BLOCKDEV_MPATH_TECH)
 BLOCKDEV_SWAP_PLUGIN = blockdev_plugin("libblockdev swap plugin", BLOCKDEV_SWAP_TECH)
diff --git a/tests/unit_tests/devices_test/lvm_test.py b/tests/unit_tests/devices_test/lvm_test.py
index d7b55224..e645309f 100644
--- a/tests/unit_tests/devices_test/lvm_test.py
+++ b/tests/unit_tests/devices_test/lvm_test.py
@@ -476,6 +476,31 @@ class LVMDeviceTest(unittest.TestCase):
                 lv.setup()
                 lvm.lvactivate.assert_called_with(vg.name, lv.lvname, ignore_skip=False)
 
+    @patch("blivet.tasks.availability.BLOCKDEV_LVM_PLUGIN_SHARED",
+           new=blivet.tasks.availability.ExternalResource(blivet.tasks.availability.AvailableMethod, ""))
+    def test_lv_activate_shared(self):
+        pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
+                           size=Size("1 GiB"), exists=True)
+        vg = LVMVolumeGroupDevice("testvg", parents=[pv], exists=True)
+        lv = LVMLogicalVolumeDevice("data_lv", parents=[vg], size=Size("500 MiB"), exists=True, shared=True)
+
+        with patch("blivet.devices.lvm.blockdev.lvm") as lvm:
+            with patch.object(lv, "_pre_setup"):
+                lv.setup()
+                lvm.lvactivate.assert_called_with(vg.name, lv.lvname, ignore_skip=False, shared=True)
+
+    @patch("blivet.tasks.availability.BLOCKDEV_LVM_PLUGIN_SHARED",
+           new=blivet.tasks.availability.ExternalResource(blivet.tasks.availability.AvailableMethod, ""))
+    def test_vg_create_shared(self):
+        pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
+                           size=Size("1 GiB"), exists=True)
+        vg = LVMVolumeGroupDevice("testvg", parents=[pv], shared=True)
+
+        with patch("blivet.devices.lvm.blockdev.lvm") as lvm:
+            vg._create()
+            lvm.vgcreate.assert_called_with(vg.name, [pv.path], Size("4 MiB"), shared="")
+            lvm.vglock_start.assert_called_with(vg.name)
+
     def test_vg_is_empty(self):
         pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
                            size=Size("1024 MiB"))
-- 
2.41.0