neil / rpms / python-blivet

Forked from rpms/python-blivet a year ago
Clone
Blob Blame History Raw
From 43c5a6ef094e5f333a6dd47c467a1516488e2097 Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Mon, 24 May 2021 13:35:39 +0200
Subject: [PATCH 1/7] Remove action device from LVM reject list

Because the device doesn't depend on itself the existing code
won't remove the device we are trying to modify from the list.

Resolves: rhbz#1955942
---
 blivet/actionlist.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/blivet/actionlist.py b/blivet/actionlist.py
index d03e32b9..2de3fed3 100644
--- a/blivet/actionlist.py
+++ b/blivet/actionlist.py
@@ -260,6 +260,7 @@ class ActionList(object):
             log.debug("action: %s", action)
 
             # Remove lvm filters for devices we are operating on
+            lvm.lvm_cc_removeFilterRejectRegexp(action.device.name)
             for device in (d for d in devices if d.depends_on(action.device)):
                 lvm.lvm_cc_removeFilterRejectRegexp(device.name)
 
-- 
2.31.1


From 2db8aa0aa6ea03c182f7e8e08cd1371ded13b71c Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Mon, 24 May 2021 14:49:12 +0200
Subject: [PATCH 2/7] Convert LVM filter lists to sets

To prevent devices being added multiple times and removed only
once.

Related: rhbz#1955942
---
 blivet/devicelibs/lvm.py | 12 ++++++------
 tests/devicetree_test.py |  6 +++---
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py
index 121797ce..9e396cca 100644
--- a/blivet/devicelibs/lvm.py
+++ b/blivet/devicelibs/lvm.py
@@ -72,8 +72,8 @@ safe_name_characters = "0-9a-zA-Z._-"
 # Theoretically we can handle all that can be handled with the LVM --config
 # argument.  For every time we call an lvm_cc (lvm compose config) funciton
 # we regenerate the config_args with all global info.
-config_args_data = {"filterRejects": [],    # regular expressions to reject.
-                    "filterAccepts": []}   # regexp to accept
+config_args_data = {"filterRejects": set(),    # regular expressions to reject.
+                    "filterAccepts": set()}    # regexp to accept
 
 
 def _set_global_config():
@@ -125,7 +125,7 @@ def needs_config_refresh(fn):
 def lvm_cc_addFilterRejectRegexp(regexp):
     """ Add a regular expression to the --config string."""
     log.debug("lvm filter: adding %s to the reject list", regexp)
-    config_args_data["filterRejects"].append(regexp)
+    config_args_data["filterRejects"].add(regexp)
 
 
 @needs_config_refresh
@@ -134,15 +134,15 @@ def lvm_cc_removeFilterRejectRegexp(regexp):
     log.debug("lvm filter: removing %s from the reject list", regexp)
     try:
         config_args_data["filterRejects"].remove(regexp)
-    except ValueError:
+    except KeyError:
         log.debug("%s wasn't in the reject list", regexp)
         return
 
 
 @needs_config_refresh
 def lvm_cc_resetFilter():
-    config_args_data["filterRejects"] = []
-    config_args_data["filterAccepts"] = []
+    config_args_data["filterRejects"] = set()
+    config_args_data["filterAccepts"] = set()
 
 
 def determine_parent_lv(internal_lv, lvs, lv_info):
diff --git a/tests/devicetree_test.py b/tests/devicetree_test.py
index d1f4d8f3..ef163c0a 100644
--- a/tests/devicetree_test.py
+++ b/tests/devicetree_test.py
@@ -125,7 +125,7 @@ class DeviceTreeTestCase(unittest.TestCase):
         dt.actions._actions.append(Mock(name="fake action"))
 
         lvm.lvm_cc_addFilterRejectRegexp("xxx")
-        lvm.config_args_data["filterAccepts"].append("yyy")
+        lvm.config_args_data["filterAccepts"].add("yyy")
 
         dt.ignored_disks.append(names[0])
         dt.exclusive_disks.append(names[1])
@@ -144,8 +144,8 @@ class DeviceTreeTestCase(unittest.TestCase):
 
         self.assertEqual(dt._hidden, empty_list)
 
-        self.assertEqual(lvm.config_args_data["filterAccepts"], empty_list)
-        self.assertEqual(lvm.config_args_data["filterRejects"], empty_list)
+        self.assertEqual(lvm.config_args_data["filterAccepts"], set())
+        self.assertEqual(lvm.config_args_data["filterRejects"], set())
 
         self.assertEqual(dt.exclusive_disks, empty_list)
         self.assertEqual(dt.ignored_disks, empty_list)
-- 
2.31.1


From e2540422945586ca45848a663e391a91b2fdd714 Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Tue, 27 Jul 2021 14:07:05 +0200
Subject: [PATCH 3/7] Switch LVM devices filter from "reject" to "accept" by
 default

We currently use the LVM reject filter to filter out hidden and
ignored devices, this commit changes the behaviour to reject all
devices by default and accept only physical volumes that are not
hidden or ignored. This is preparation for the switch from the
existing lvm.conf based filtering to the new devices file based
filtering introduced in LVM 2.03.12 which allows only listing
"accepted" devices. This allows us to support both the "old" and
"new" style filtering using the same code.
---
 blivet/actionlist.py                  |  5 +--
 blivet/devicelibs/lvm.py              | 62 +++++++++++----------------
 blivet/devices/lvm.py                 |  4 +-
 blivet/devicetree.py                  |  8 ++--
 blivet/formats/lvmpv.py               |  2 +
 blivet/populator/helpers/lvm.py       |  6 +++
 blivet/populator/helpers/partition.py |  8 ----
 blivet/populator/populator.py         |  4 +-
 tests/devicetree_test.py              | 37 ++++++++++++++--
 tests/populator_test.py               |  6 ++-
 10 files changed, 81 insertions(+), 61 deletions(-)

diff --git a/blivet/actionlist.py b/blivet/actionlist.py
index 2de3fed3..f3977401 100644
--- a/blivet/actionlist.py
+++ b/blivet/actionlist.py
@@ -259,10 +259,9 @@ class ActionList(object):
         for action in self._actions:
             log.debug("action: %s", action)
 
-            # Remove lvm filters for devices we are operating on
-            lvm.lvm_cc_removeFilterRejectRegexp(action.device.name)
             for device in (d for d in devices if d.depends_on(action.device)):
-                lvm.lvm_cc_removeFilterRejectRegexp(device.name)
+                if device.format.type == "lvmpv":
+                    lvm.lvm_devices_add(device.path)
 
     def _post_process(self, devices=None):
         """ Clean up relics from action queue execution. """
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py
index 9e396cca..96d037b8 100644
--- a/blivet/devicelibs/lvm.py
+++ b/blivet/devicelibs/lvm.py
@@ -67,40 +67,29 @@ LVMETAD_SOCKET_PATH = "/run/lvm/lvmetad.socket"
 
 safe_name_characters = "0-9a-zA-Z._-"
 
-# Start config_args handling code
-#
-# Theoretically we can handle all that can be handled with the LVM --config
-# argument.  For every time we call an lvm_cc (lvm compose config) funciton
-# we regenerate the config_args with all global info.
-config_args_data = {"filterRejects": set(),    # regular expressions to reject.
-                    "filterAccepts": set()}    # regexp to accept
+# list of devices that LVM is allowed to use
+# with LVM >= 2.0.13 we'll use this for the --devices option and when creating
+# the /etc/lvm/devices/system.devices file
+# with older versions of LVM we will use this for the --config based filtering
+_lvm_devices = set()
 
 
 def _set_global_config():
     """lvm command accepts lvm.conf type arguments preceded by --config. """
 
-    filter_string = ""
-    rejects = config_args_data["filterRejects"]
-    for reject in rejects:
-        filter_string += ("\"r|/%s$|\"," % reject)
+    device_string = ""
+
+    # now explicitly "accept" all LVM devices
+    for device in _lvm_devices:
+        device_string += "\"a|%s$|\"," % device
 
-    if filter_string:
-        filter_string = "filter=[%s]" % filter_string.strip(",")
+    # now add all devices to the "reject" filter
+    device_string += "\"r|.*|\""
 
-    # XXX consider making /tmp/blivet.lvm.XXXXX, writing an lvm.conf there, and
-    #     setting LVM_SYSTEM_DIR
-    devices_string = 'preferred_names=["^/dev/mapper/", "^/dev/md/", "^/dev/sd"]'
-    if filter_string:
-        devices_string += " %s" % filter_string
+    filter_string = "filter=[%s]" % device_string
 
-    # for now ignore the LVM devices file and rely on our filters
-    if availability.LVMDEVICES.available:
-        devices_string += " use_devicesfile=0"
+    config_string = " devices { %s } " % filter_string
 
-    # devices_string can have (inside the brackets) "dir", "scan",
-    # "preferred_names", "filter", "cache_dir", "write_cache_state",
-    # "types", "sysfs_scan", "md_component_detection".  see man lvm.conf.
-    config_string = " devices { %s } " % (devices_string)  # strings can be added
     if not flags.lvm_metadata_backup:
         config_string += "backup {backup=0 archive=0} "
     if flags.debug:
@@ -122,27 +111,26 @@ def needs_config_refresh(fn):
 
 
 @needs_config_refresh
-def lvm_cc_addFilterRejectRegexp(regexp):
-    """ Add a regular expression to the --config string."""
-    log.debug("lvm filter: adding %s to the reject list", regexp)
-    config_args_data["filterRejects"].add(regexp)
+def lvm_devices_add(path):
+    """ Add a device (PV) to the list of devices LVM is allowed to use """
+    log.debug("lvm filter: device %s added to the list of allowed devices")
+    _lvm_devices.add(path)
 
 
 @needs_config_refresh
-def lvm_cc_removeFilterRejectRegexp(regexp):
-    """ Remove a regular expression from the --config string."""
-    log.debug("lvm filter: removing %s from the reject list", regexp)
+def lvm_devices_remove(path):
+    """ Remove a device (PV) to the list of devices LVM is allowed to use """
+    log.debug("lvm filter: device %s removed from the list of allowed devices")
     try:
-        config_args_data["filterRejects"].remove(regexp)
+        _lvm_devices.remove(path)
     except KeyError:
-        log.debug("%s wasn't in the reject list", regexp)
+        log.debug("%s wasn't in the devices list", path)
         return
 
 
 @needs_config_refresh
-def lvm_cc_resetFilter():
-    config_args_data["filterRejects"] = set()
-    config_args_data["filterAccepts"] = set()
+def lvm_devices_reset():
+    _lvm_devices.clear()
 
 
 def determine_parent_lv(internal_lv, lvs, lv_info):
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
index c61eeb4b..9c230f1b 100644
--- a/blivet/devices/lvm.py
+++ b/blivet/devices/lvm.py
@@ -273,8 +273,8 @@ class LVMVolumeGroupDevice(ContainerDevice):
         log_method_call(self, self.name, status=self.status)
         if not self.complete:
             for pv in self.pvs:
-                # Remove the PVs from the ignore filter so we can wipe them.
-                lvm.lvm_cc_removeFilterRejectRegexp(pv.name)
+                # add PVS to the list of LVM devices so we can wipe them.
+                lvm.lvm_devices_add(pv.path)
 
             # Don't run vgremove or vgreduce since there may be another VG with
             # the same name that we want to keep/use.
diff --git a/blivet/devicetree.py b/blivet/devicetree.py
index f4ae1968..c6c1b440 100644
--- a/blivet/devicetree.py
+++ b/blivet/devicetree.py
@@ -96,7 +96,7 @@ class DeviceTreeBase(object):
 
         self._hidden = []
 
-        lvm.lvm_cc_resetFilter()
+        lvm.lvm_devices_reset()
 
         self.exclusive_disks = exclusive_disks or []
         self.ignored_disks = ignored_disks or []
@@ -879,7 +879,8 @@ class DeviceTreeBase(object):
         self._remove_device(device, force=True, modparent=False)
 
         self._hidden.append(device)
-        lvm.lvm_cc_addFilterRejectRegexp(device.name)
+        if device.format.type == "lvmpv":
+            lvm.lvm_devices_remove(device.path)
 
     def unhide(self, device):
         """ Restore a device's visibility.
@@ -905,7 +906,8 @@ class DeviceTreeBase(object):
                 self._hidden.remove(hidden)
                 self._devices.append(hidden)
                 hidden.add_hook(new=False)
-                lvm.lvm_cc_removeFilterRejectRegexp(hidden.name)
+                if hidden.format.type == "lvmpv":
+                    lvm.lvm_devices_add(hidden.path)
 
     def expand_taglist(self, taglist):
         """ Expands tags in input list into devices.
diff --git a/blivet/formats/lvmpv.py b/blivet/formats/lvmpv.py
index ea84e9e4..3b00951f 100644
--- a/blivet/formats/lvmpv.py
+++ b/blivet/formats/lvmpv.py
@@ -124,6 +124,7 @@ class LVMPhysicalVolume(DeviceFormat):
     def _create(self, **kwargs):
         log_method_call(self, device=self.device,
                         type=self.type, status=self.status)
+        lvm.lvm_devices_add(self.device)
 
         lvm._set_global_config()
 
@@ -138,6 +139,7 @@ class LVMPhysicalVolume(DeviceFormat):
         except blockdev.LVMError:
             DeviceFormat._destroy(self, **kwargs)
         finally:
+            lvm.lvm_devices_remove(self.device)
             udev.settle()
 
     @property
diff --git a/blivet/populator/helpers/lvm.py b/blivet/populator/helpers/lvm.py
index c7adfa4e..9e7e4630 100644
--- a/blivet/populator/helpers/lvm.py
+++ b/blivet/populator/helpers/lvm.py
@@ -87,6 +87,12 @@ class LVMFormatPopulator(FormatPopulator):
     def _get_kwargs(self):
         kwargs = super(LVMFormatPopulator, self)._get_kwargs()
 
+        # new PV, add it to the LVM devices list and re-run pvs/lvs/vgs
+        lvm.lvm_devices_add(self.device.path)
+        pvs_info.drop_cache()
+        vgs_info.drop_cache()
+        lvs_info.drop_cache()
+
         pv_info = pvs_info.cache.get(self.device.path, None)
 
         name = udev.device_get_name(self.data)
diff --git a/blivet/populator/helpers/partition.py b/blivet/populator/helpers/partition.py
index f00323d1..8659bd48 100644
--- a/blivet/populator/helpers/partition.py
+++ b/blivet/populator/helpers/partition.py
@@ -24,7 +24,6 @@ import copy
 import six
 
 from ... import udev
-from ...devicelibs import lvm
 from ...devices import PartitionDevice
 from ...errors import DeviceError
 from ...formats import get_format
@@ -66,7 +65,6 @@ class PartitionDevicePopulator(DevicePopulator):
         if disk is None:
             # if the disk is still not in the tree something has gone wrong
             log.error("failure finding disk for %s", name)
-            lvm.lvm_cc_addFilterRejectRegexp(name)
             return
 
         if not disk.partitioned or not disk.format.supported:
@@ -78,12 +76,6 @@ class PartitionDevicePopulator(DevicePopulator):
             # and instantiate a PartitionDevice so our view of the layout is
             # complete.
             if not disk.partitionable or disk.format.type == "iso9660" or disk.format.hidden:
-                # there's no need to filter partitions on members of multipaths or
-                # fwraid members from lvm since multipath and dmraid are already
-                # active and lvm should therefore know to ignore them
-                if not disk.format.hidden:
-                    lvm.lvm_cc_addFilterRejectRegexp(name)
-
                 log.debug("ignoring partition %s on %s", name, disk.format.type)
                 return
 
diff --git a/blivet/populator/populator.py b/blivet/populator/populator.py
index 75bb1741..958593ec 100644
--- a/blivet/populator/populator.py
+++ b/blivet/populator/populator.py
@@ -317,10 +317,10 @@ class PopulatorMixin(object):
                 continue
 
             # Make sure lvm doesn't get confused by PVs that belong to
-            # incomplete VGs. We will remove the PVs from the reject list when/if
+            # incomplete VGs. We will add the PVs to the accept list when/if
             # the time comes to remove the incomplete VG and its PVs.
             for pv in vg.pvs:
-                lvm.lvm_cc_addFilterRejectRegexp(pv.name)
+                lvm.lvm_devices_remove(pv.path)
 
     def set_disk_images(self, images):
         """ Set the disk images and reflect them in exclusive_disks.
diff --git a/tests/devicetree_test.py b/tests/devicetree_test.py
index ef163c0a..3be4d572 100644
--- a/tests/devicetree_test.py
+++ b/tests/devicetree_test.py
@@ -124,8 +124,7 @@ class DeviceTreeTestCase(unittest.TestCase):
 
         dt.actions._actions.append(Mock(name="fake action"))
 
-        lvm.lvm_cc_addFilterRejectRegexp("xxx")
-        lvm.config_args_data["filterAccepts"].add("yyy")
+        lvm.lvm_devices_add("xxx")
 
         dt.ignored_disks.append(names[0])
         dt.exclusive_disks.append(names[1])
@@ -144,8 +143,7 @@ class DeviceTreeTestCase(unittest.TestCase):
 
         self.assertEqual(dt._hidden, empty_list)
 
-        self.assertEqual(lvm.config_args_data["filterAccepts"], set())
-        self.assertEqual(lvm.config_args_data["filterRejects"], set())
+        self.assertEqual(lvm._lvm_devices, set())
 
         self.assertEqual(dt.exclusive_disks, empty_list)
         self.assertEqual(dt.ignored_disks, empty_list)
@@ -438,6 +436,37 @@ class DeviceTreeTestCase(unittest.TestCase):
         self.assertEqual(tree.get_related_disks(sda), set([sda, sdb]))
         self.assertEqual(tree.get_related_disks(sdb), set([sda, sdb]))
 
+    def test_lvm_filter_hide_unhide(self):
+        tree = DeviceTree()
+
+        sda = DiskDevice("sda", size=Size("30 GiB"))
+        sdb = DiskDevice("sdb", size=Size("30 GiB"))
+
+        tree._add_device(sda)
+        tree._add_device(sdb)
+
+        self.assertTrue(sda in tree.devices)
+        self.assertTrue(sdb in tree.devices)
+
+        sda.format = get_format("lvmpv", device=sda.path)
+        sdb.format = get_format("lvmpv", device=sdb.path)
+
+        # LVMPhysicalVolume._create would do this
+        lvm.lvm_devices_add(sda.path)
+        lvm.lvm_devices_add(sdb.path)
+
+        self.assertSetEqual(lvm._lvm_devices, {sda.path, sdb.path})
+
+        tree.hide(sda)
+        self.assertSetEqual(lvm._lvm_devices, {sdb.path})
+        tree.hide(sdb)
+        self.assertSetEqual(lvm._lvm_devices, set())
+
+        tree.unhide(sda)
+        self.assertSetEqual(lvm._lvm_devices, {sda.path})
+        tree.unhide(sdb)
+        self.assertSetEqual(lvm._lvm_devices, {sda.path, sdb.path})
+
 
 class DeviceTreeIgnoredExclusiveMultipathTestCase(unittest.TestCase):
 
diff --git a/tests/populator_test.py b/tests/populator_test.py
index 2a8532f0..dd36c16a 100644
--- a/tests/populator_test.py
+++ b/tests/populator_test.py
@@ -13,6 +13,7 @@ from gi.repository import BlockDev as blockdev
 from blivet.devices import DiskDevice, DMDevice, FileDevice, LoopDevice
 from blivet.devices import MDRaidArrayDevice, MultipathDevice, OpticalDevice
 from blivet.devices import PartitionDevice, StorageDevice, NVDIMMNamespaceDevice
+from blivet.devicelibs import lvm
 from blivet.devicetree import DeviceTree
 from blivet.formats import get_device_format_class, get_format, DeviceFormat
 from blivet.formats.disklabel import DiskLabel
@@ -393,8 +394,7 @@ class PartitionDevicePopulatorTestCase(PopulatorHelperTestCase):
     @patch.object(DiskLabel, "parted_disk")
     @patch.object(DiskLabel, "parted_device")
     @patch.object(PartitionDevice, "probe")
-    # TODO: fix the naming of the lvm filter functions
-    @patch("blivet.devicelibs.lvm.lvm_cc_addFilterRejectRegexp")
+    @patch("blivet.devicelibs.lvm.lvm_devices_add")
     @patch("blivet.udev.device_get_major", return_value=88)
     @patch("blivet.udev.device_get_minor", return_value=19)
     @patch.object(DeviceTree, "get_device_by_name")
@@ -973,6 +973,8 @@ class LVMFormatPopulatorTestCase(FormatPopulatorTestCase):
                     self.assertTrue(vg_device is not None)
                     devicetree._remove_device(vg_device)
 
+                    self.assertIn(device.path, lvm._lvm_devices)
+
         get_device_by_uuid.reset_mock()
 
         # pv belongs to a valid vg not in the tree with two lvs
-- 
2.31.1


From 15a63b01bd2b6e7fe197fade849f28b83407c166 Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Fri, 30 Jul 2021 14:01:04 +0200
Subject: [PATCH 4/7] Use LVM devices for filtering LVM devices with LVM >=
 2.02.13

---
 blivet/devicelibs/lvm.py | 38 +++++++++++++++++++++++++++++---------
 tests/populator_test.py  |  9 ++++-----
 2 files changed, 33 insertions(+), 14 deletions(-)

diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py
index 96d037b8..3ab1540b 100644
--- a/blivet/devicelibs/lvm.py
+++ b/blivet/devicelibs/lvm.py
@@ -67,6 +67,16 @@ LVMETAD_SOCKET_PATH = "/run/lvm/lvmetad.socket"
 
 safe_name_characters = "0-9a-zA-Z._-"
 
+if hasattr(blockdev.LVMTech, "DEVICES"):
+    try:
+        blockdev.lvm.is_tech_avail(blockdev.LVMTech.DEVICES, 0)  # pylint: disable=no-member
+    except blockdev.LVMError:
+        HAVE_LVMDEVICES = False
+    else:
+        HAVE_LVMDEVICES = True
+else:
+    HAVE_LVMDEVICES = False
+
 # list of devices that LVM is allowed to use
 # with LVM >= 2.0.13 we'll use this for the --devices option and when creating
 # the /etc/lvm/devices/system.devices file
@@ -79,25 +89,34 @@ def _set_global_config():
 
     device_string = ""
 
-    # now explicitly "accept" all LVM devices
-    for device in _lvm_devices:
-        device_string += "\"a|%s$|\"," % device
+    if not HAVE_LVMDEVICES:
+        # now explicitly "accept" all LVM devices
+        for device in _lvm_devices:
+            device_string += "\"a|%s$|\"," % device
 
-    # now add all devices to the "reject" filter
-    device_string += "\"r|.*|\""
+        # now add all devices to the "reject" filter
+        device_string += "\"r|.*|\""
 
-    filter_string = "filter=[%s]" % device_string
+        filter_string = "filter=[%s]" % device_string
 
-    config_string = " devices { %s } " % filter_string
+        config_string = " devices { %s } " % filter_string
+    else:
+        config_string = " "
 
     if not flags.lvm_metadata_backup:
         config_string += "backup {backup=0 archive=0} "
-    if flags.debug:
-        config_string += "log {level=7 file=/tmp/lvm.log syslog=0}"
+    config_string += "log {level=7 file=/tmp/lvm.log syslog=0}"
 
     blockdev.lvm.set_global_config(config_string)
 
 
+def _set_lvm_devices():
+    if not HAVE_LVMDEVICES:
+        return
+
+    blockdev.lvm.set_devices_filter(list(_lvm_devices))
+
+
 def needs_config_refresh(fn):
     if not availability.BLOCKDEV_LVM_PLUGIN.available:
         return lambda *args, **kwargs: None
@@ -105,6 +124,7 @@ def needs_config_refresh(fn):
     def fn_with_refresh(*args, **kwargs):
         ret = fn(*args, **kwargs)
         _set_global_config()
+        _set_lvm_devices()
         return ret
 
     return fn_with_refresh
diff --git a/tests/populator_test.py b/tests/populator_test.py
index dd36c16a..a9584319 100644
--- a/tests/populator_test.py
+++ b/tests/populator_test.py
@@ -897,6 +897,7 @@ class LVMFormatPopulatorTestCase(FormatPopulatorTestCase):
         device = Mock()
         device.parents = []
         device.size = Size("10g")
+        device.path = "/dev/sda1"
         devicetree._add_device(device)
 
         # pylint: disable=attribute-defined-outside-init
@@ -924,15 +925,13 @@ class LVMFormatPopulatorTestCase(FormatPopulatorTestCase):
         pv_info.pe_start = 0
         pv_info.pv_free = 0
 
-        device.path = sentinel.pv_path
-
         vg_device = Mock()
         vg_device.parents = []
         vg_device.lvs = []
         get_device_by_uuid.return_value = vg_device
 
         with patch("blivet.static_data.lvm_info.PVsInfo.cache", new_callable=PropertyMock) as mock_pvs_cache:
-            mock_pvs_cache.return_value = {sentinel.pv_path: pv_info}
+            mock_pvs_cache.return_value = {device.path: pv_info}
             with patch("blivet.udev.device_get_format", return_value=self.udev_type):
                 helper = self.helper_class(devicetree, data, device)
                 self.assertFalse(device in vg_device.parents)
@@ -957,7 +956,7 @@ class LVMFormatPopulatorTestCase(FormatPopulatorTestCase):
         pv_info.vg_pv_count = 1
 
         with patch("blivet.static_data.lvm_info.PVsInfo.cache", new_callable=PropertyMock) as mock_pvs_cache:
-            mock_pvs_cache.return_value = {sentinel.pv_path: pv_info}
+            mock_pvs_cache.return_value = {device.path: pv_info}
             with patch("blivet.static_data.lvm_info.VGsInfo.cache", new_callable=PropertyMock) as mock_vgs_cache:
                 mock_vgs_cache.return_value = {pv_info.vg_uuid: Mock()}
                 with patch("blivet.udev.device_get_format", return_value=self.udev_type):
@@ -1007,7 +1006,7 @@ class LVMFormatPopulatorTestCase(FormatPopulatorTestCase):
         get_device_by_uuid.side_effect = gdbu
 
         with patch("blivet.static_data.lvm_info.PVsInfo.cache", new_callable=PropertyMock) as mock_pvs_cache:
-            mock_pvs_cache.return_value = {sentinel.pv_path: pv_info}
+            mock_pvs_cache.return_value = {device.path: pv_info}
             with patch("blivet.static_data.lvm_info.VGsInfo.cache", new_callable=PropertyMock) as mock_vgs_cache:
                 mock_vgs_cache.return_value = {pv_info.vg_uuid: Mock()}
                 with patch("blivet.static_data.lvm_info.LVsInfo.cache", new_callable=PropertyMock) as mock_lvs_cache:
-- 
2.31.1


From d4e1395de3691f30196b6b0e3b2c82e83b27afaf Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Fri, 30 Jul 2021 14:01:43 +0200
Subject: [PATCH 5/7] Make sure PVs are added/deleted to/from the LVM device
 file

We are using the --devices option when running LVM commands which
mean the newly created PV won't be added to the device list by
pvcreate so we need to do that manually.
---
 blivet/formats/lvmpv.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/blivet/formats/lvmpv.py b/blivet/formats/lvmpv.py
index 3b00951f..71ec699f 100644
--- a/blivet/formats/lvmpv.py
+++ b/blivet/formats/lvmpv.py
@@ -131,6 +131,9 @@ class LVMPhysicalVolume(DeviceFormat):
         ea_yes = blockdev.ExtraArg.new("-y", "")
         blockdev.lvm.pvcreate(self.device, data_alignment=self.data_alignment, extra=[ea_yes])
 
+        if lvm.HAVE_LVMDEVICES:
+            blockdev.lvm.devices_add(self.device)
+
     def _destroy(self, **kwargs):
         log_method_call(self, device=self.device,
                         type=self.type, status=self.status)
@@ -141,6 +144,8 @@ class LVMPhysicalVolume(DeviceFormat):
         finally:
             lvm.lvm_devices_remove(self.device)
             udev.settle()
+            if lvm.HAVE_LVMDEVICES:
+                blockdev.lvm.devices_delete(self.device)
 
     @property
     def destroyable(self):
-- 
2.31.1


From c221d313bde21fb2cba701b93fe0c57336cba8ec Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 14 Oct 2021 15:32:24 +0200
Subject: [PATCH 6/7] Ignore errors for LVM devices file actions

The LVM devices file feature might be disabled either locally or
globally by LVM config.
---
 blivet/formats/lvmpv.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/blivet/formats/lvmpv.py b/blivet/formats/lvmpv.py
index 71ec699f..b27213cc 100644
--- a/blivet/formats/lvmpv.py
+++ b/blivet/formats/lvmpv.py
@@ -132,7 +132,10 @@ class LVMPhysicalVolume(DeviceFormat):
         blockdev.lvm.pvcreate(self.device, data_alignment=self.data_alignment, extra=[ea_yes])
 
         if lvm.HAVE_LVMDEVICES:
-            blockdev.lvm.devices_add(self.device)
+            try:
+                blockdev.lvm.devices_add(self.device)
+            except blockdev.LVMError as e:
+                log.debug("Failed to add newly created PV %s to the LVM devices file: %s", self.device, str(e))
 
     def _destroy(self, **kwargs):
         log_method_call(self, device=self.device,
@@ -145,7 +148,10 @@ class LVMPhysicalVolume(DeviceFormat):
             lvm.lvm_devices_remove(self.device)
             udev.settle()
             if lvm.HAVE_LVMDEVICES:
-                blockdev.lvm.devices_delete(self.device)
+                try:
+                    blockdev.lvm.devices_delete(self.device)
+                except blockdev.LVMError as e:
+                    log.debug("Failed to remove PV %s from the LVM devices file: %s", self.device, str(e))
 
     @property
     def destroyable(self):
-- 
2.31.1


From 6b96d4ead6890fffd95840b8935f71ecd9e310ef Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Tue, 19 Oct 2021 14:27:05 +0200
Subject: [PATCH 7/7] Add public functions to add/remove PV to/from the LVM
 system.devices

Anaconda needs to be able to add preexisting PVs to the file
during installation.
---
 blivet/formats/lvmpv.py | 28 ++++++++++++++++++++--------
 1 file changed, 20 insertions(+), 8 deletions(-)

diff --git a/blivet/formats/lvmpv.py b/blivet/formats/lvmpv.py
index b27213cc..3fef667e 100644
--- a/blivet/formats/lvmpv.py
+++ b/blivet/formats/lvmpv.py
@@ -121,6 +121,24 @@ class LVMPhysicalVolume(DeviceFormat):
     def supported(self):
         return super(LVMPhysicalVolume, self).supported and self._plugin.available
 
+    def lvmdevices_add(self):
+        if not lvm.HAVE_LVMDEVICES:
+            raise PhysicalVolumeError("LVM devices file feature is not supported")
+
+        try:
+            blockdev.lvm.devices_add(self.device)
+        except blockdev.LVMError as e:
+            log.debug("Failed to add PV %s to the LVM devices file: %s", self.device, str(e))
+
+    def lvmdevices_remove(self):
+        if not lvm.HAVE_LVMDEVICES:
+            raise PhysicalVolumeError("LVM devices file feature is not supported")
+
+        try:
+            blockdev.lvm.devices_delete(self.device)
+        except blockdev.LVMError as e:
+            log.debug("Failed to remove PV %s from the LVM devices file: %s", self.device, str(e))
+
     def _create(self, **kwargs):
         log_method_call(self, device=self.device,
                         type=self.type, status=self.status)
@@ -132,10 +150,7 @@ class LVMPhysicalVolume(DeviceFormat):
         blockdev.lvm.pvcreate(self.device, data_alignment=self.data_alignment, extra=[ea_yes])
 
         if lvm.HAVE_LVMDEVICES:
-            try:
-                blockdev.lvm.devices_add(self.device)
-            except blockdev.LVMError as e:
-                log.debug("Failed to add newly created PV %s to the LVM devices file: %s", self.device, str(e))
+            self.lvmdevices_add()
 
     def _destroy(self, **kwargs):
         log_method_call(self, device=self.device,
@@ -148,10 +163,7 @@ class LVMPhysicalVolume(DeviceFormat):
             lvm.lvm_devices_remove(self.device)
             udev.settle()
             if lvm.HAVE_LVMDEVICES:
-                try:
-                    blockdev.lvm.devices_delete(self.device)
-                except blockdev.LVMError as e:
-                    log.debug("Failed to remove PV %s from the LVM devices file: %s", self.device, str(e))
+                self.lvmdevices_remove()
 
     @property
     def destroyable(self):
-- 
2.31.1