From c2ffce46b9283e0ef4ede61ee6f0eb5f66f00ec6 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Thu, 20 Apr 2023 12:35:30 +0200 Subject: [PATCH 1/3] 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. --- 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 bec464d73..680632016 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -97,7 +97,8 @@ def get_supported_pe_sizes(): 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 @@ def __init__(self, name, parents=None, size=None, free=None, :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 @@ def __init__(self, name, parents=None, size=None, free=None, 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: @@ -257,11 +264,23 @@ def _create(self): """ Create the device. """ log_method_call(self, self.name, status=self.status) pv_list = [pv.path for pv in self.parents] + extra = dict() + if self._shared: + extra["shared"] = "" try: - blockdev.lvm.vgcreate(self.name, pv_list, self.pe_size) + blockdev.lvm.vgcreate(self.name, pv_list, self.pe_size, **extra) except blockdev.LVMError as err: raise errors.LVMError(err) + 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 super(LVMVolumeGroupDevice, self)._post_create() @@ -682,7 +701,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: @@ -711,6 +730,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None, 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) @@ -2375,7 +2395,8 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None, 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 @@ -2406,6 +2427,8 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None, :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: @@ -2481,7 +2504,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None, 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) @@ -2714,7 +2737,13 @@ def _setup(self, orig=False): controllable=self.controllable) ignore_skip_activation = self.is_snapshot_lv or self.ignore_skip_activation > 0 try: - 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) except blockdev.LVMError as err: raise errors.LVMError(err) @@ -2754,6 +2783,9 @@ def _create(self): if self._stripe_size: extra["stripesize"] = str(int(self._stripe_size.convert_to("KiB"))) + if self._shared: + extra["activate"] = "sy" + try: blockdev.lvm.lvcreate(self.vg.name, self._name, self.size, type=self.seg_type, pv_list=pvs, **extra) diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py index 39b7f4c04..4b947d6cd 100644 --- a/blivet/tasks/availability.py +++ b/blivet/tasks/availability.py @@ -429,6 +429,14 @@ def available_resource(name): 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 | @@ -469,6 +477,7 @@ def available_resource(name): 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 83f003c0e..ff38a2f47 100644 --- a/tests/unit_tests/devices_test/lvm_test.py +++ b/tests/unit_tests/devices_test/lvm_test.py @@ -477,6 +477,31 @@ def test_skip_activate(self): 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")) From 8447b5033f74a3a543aead9e9199d25f032ffbe9 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Thu, 31 Aug 2023 10:51:59 +0200 Subject: [PATCH 2/3] When creating a shared LVM VG skip pvcreate When creating the first shared VG (and using sanlock lock manager) vgcreate has to be given non-PV device otherwise VG creation will fail. --- blivet/devices/lvm.py | 4 ++++ blivet/formats/lvmpv.py | 5 +++++ tests/unit_tests/devices_test/lvm_test.py | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index 680632016..7661bbc81 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -164,6 +164,10 @@ def __init__(self, name, parents=None, size=None, free=None, # >0 is fixed self.size_policy = self.size + if self._shared: + for pv in self.parents: + pv.format._vg_shared = True + def __repr__(self): s = super(LVMVolumeGroupDevice, self).__repr__() s += (" free = %(free)s PE Size = %(pe_size)s PE Count = %(pe_count)s\n" diff --git a/blivet/formats/lvmpv.py b/blivet/formats/lvmpv.py index b72ea519e..929595902 100644 --- a/blivet/formats/lvmpv.py +++ b/blivet/formats/lvmpv.py @@ -95,6 +95,7 @@ def __init__(self, **kwargs): self.pe_start = kwargs.get("pe_start", lvm.LVM_PE_START) self.data_alignment = kwargs.get("data_alignment", Size(0)) self._free = kwargs.get("free") # None means unknown + self._vg_shared = False self.inconsistent_vg = False @@ -155,6 +156,10 @@ def _create(self, **kwargs): type=self.type, status=self.status) lvm.lvm_devices_add(self.device) + if self._vg_shared: + log.info("Shared VG: skipping pvcreate, PV format will be created by vgcreate") + return + ea_yes = blockdev.ExtraArg.new("-y", "") if lvm.HAVE_LVMDEVICES: diff --git a/tests/unit_tests/devices_test/lvm_test.py b/tests/unit_tests/devices_test/lvm_test.py index ff38a2f47..9b39bd4dc 100644 --- a/tests/unit_tests/devices_test/lvm_test.py +++ b/tests/unit_tests/devices_test/lvm_test.py @@ -497,6 +497,11 @@ def test_vg_create_shared(self): size=Size("1 GiB"), exists=True) vg = LVMVolumeGroupDevice("testvg", parents=[pv], shared=True) + self.assertTrue(pv.format._vg_shared) + with patch("blivet.devices.lvm.blockdev.lvm") as lvm: + pv.format._create() + lvm.pvcreate.assert_not_called() + with patch("blivet.devices.lvm.blockdev.lvm") as lvm: vg._create() lvm.vgcreate.assert_called_with(vg.name, [pv.path], Size("4 MiB"), shared="") From 29d412f90a0f5ef2567f90d497f3ef3b4b7b6564 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Thu, 31 Aug 2023 10:57:43 +0200 Subject: [PATCH 3/3] Force command line based libblockdev LVM plugin We need to be able to skip pvcreate when creating a shared VG and that is not possible with LVM DBusD. --- blivet/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/blivet/__init__.py b/blivet/__init__.py index f36ed5746..e57d68454 100644 --- a/blivet/__init__.py +++ b/blivet/__init__.py @@ -63,15 +63,20 @@ def log_bd_message(level, msg): from gi.repository import GLib from gi.repository import BlockDev as blockdev if arch.is_s390(): - _REQUESTED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "s390", "nvdimm")) + _REQUESTED_PLUGIN_NAMES = set(("btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "s390", "nvdimm")) else: - _REQUESTED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "nvdimm")) + _REQUESTED_PLUGIN_NAMES = set(("btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "nvdimm")) # nvme plugin is not generally available if hasattr(blockdev.Plugin, "NVME"): _REQUESTED_PLUGIN_NAMES.add("nvme") _requested_plugins = blockdev.plugin_specs_from_names(_REQUESTED_PLUGIN_NAMES) +# XXX force non-dbus LVM plugin +lvm_plugin = blockdev.PluginSpec() +lvm_plugin.name = blockdev.Plugin.LVM +lvm_plugin.so_name = "libbd_lvm.so.3" +_requested_plugins.append(lvm_plugin) try: succ_, avail_plugs = blockdev.try_reinit(require_plugins=_requested_plugins, reload=False, log_func=log_bd_message) except GLib.GError as err: