From c20296b2df89a9edc4ea9cc41f94df89a8fbfd26 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny 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