Jan Pokorny a25a6f
From 08f0e12c74e4c2ba25629fe92108283dd5ae3ff3 Mon Sep 17 00:00:00 2001
Jan Pokorny a25a6f
From: Vojtech Trefny <vtrefny@redhat.com>
Jan Pokorny a25a6f
Date: Thu, 30 Dec 2021 16:08:43 +0100
Jan Pokorny a25a6f
Subject: [PATCH 1/4] Add support for creating LVM cache pools
Jan Pokorny a25a6f
Jan Pokorny a25a6f
Resolves: rhbz#2055200
Jan Pokorny a25a6f
---
Jan Pokorny a25a6f
 blivet/blivet.py               |   9 +-
Jan Pokorny a25a6f
 blivet/devicelibs/lvm.py       |   9 ++
Jan Pokorny a25a6f
 blivet/devices/lvm.py          | 160 +++++++++++++++++++++++++++++++--
Jan Pokorny a25a6f
 tests/devices_test/lvm_test.py |  26 ++++++
Jan Pokorny a25a6f
 4 files changed, 196 insertions(+), 8 deletions(-)
Jan Pokorny a25a6f
Jan Pokorny a25a6f
diff --git a/blivet/blivet.py b/blivet/blivet.py
Jan Pokorny a25a6f
index c6908eb0..d29fadd0 100644
Jan Pokorny a25a6f
--- a/blivet/blivet.py
Jan Pokorny a25a6f
+++ b/blivet/blivet.py
Jan Pokorny a25a6f
@@ -576,6 +576,8 @@ class Blivet(object):
Jan Pokorny a25a6f
             :type vdo_pool: bool
Jan Pokorny a25a6f
             :keyword vdo_lv: whether to create a vdo lv
Jan Pokorny a25a6f
             :type vdo_lv: bool
Jan Pokorny a25a6f
+            :keyword cache_pool: whether to create a cache pool
Jan Pokorny a25a6f
+            :type cache_pool: bool
Jan Pokorny a25a6f
             :returns: the new device
Jan Pokorny a25a6f
             :rtype: :class:`~.devices.LVMLogicalVolumeDevice`
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
@@ -594,6 +596,7 @@ class Blivet(object):
Jan Pokorny a25a6f
         thin_pool = kwargs.pop("thin_pool", False)
Jan Pokorny a25a6f
         vdo_pool = kwargs.pop("vdo_pool", False)
Jan Pokorny a25a6f
         vdo_lv = kwargs.pop("vdo_lv", False)
Jan Pokorny a25a6f
+        cache_pool = kwargs.pop("cache_pool", False)
Jan Pokorny a25a6f
         parent = kwargs.get("parents", [None])[0]
Jan Pokorny a25a6f
         if (thin_volume or vdo_lv) and parent:
Jan Pokorny a25a6f
             # kwargs["parents"] will contain the pool device, so...
Jan Pokorny a25a6f
@@ -609,6 +612,8 @@ class Blivet(object):
Jan Pokorny a25a6f
             kwargs["seg_type"] = "vdo-pool"
Jan Pokorny a25a6f
         if vdo_lv:
Jan Pokorny a25a6f
             kwargs["seg_type"] = "vdo"
Jan Pokorny a25a6f
+        if cache_pool:
Jan Pokorny a25a6f
+            kwargs["seg_type"] = "cache-pool"
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
         mountpoint = kwargs.pop("mountpoint", None)
Jan Pokorny a25a6f
         if 'fmt_type' in kwargs:
Jan Pokorny a25a6f
@@ -640,7 +645,7 @@ class Blivet(object):
Jan Pokorny a25a6f
                 swap = False
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
             prefix = ""
Jan Pokorny a25a6f
-            if thin_pool or vdo_pool:
Jan Pokorny a25a6f
+            if thin_pool or vdo_pool or cache_pool:
Jan Pokorny a25a6f
                 prefix = "pool"
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
             name = self.suggest_device_name(parent=vg,
Jan Pokorny a25a6f
@@ -651,7 +656,7 @@ class Blivet(object):
Jan Pokorny a25a6f
         if "%s-%s" % (vg.name, name) in self.names:
Jan Pokorny a25a6f
             raise ValueError("name '%s' is already in use" % name)
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
-        if thin_pool or thin_volume or vdo_pool or vdo_lv:
Jan Pokorny a25a6f
+        if thin_pool or thin_volume or vdo_pool or vdo_lv or cache_pool:
Jan Pokorny a25a6f
             cache_req = kwargs.pop("cache_request", None)
Jan Pokorny a25a6f
             if cache_req:
Jan Pokorny a25a6f
                 raise ValueError("Creating cached thin and VDO volumes and pools is not supported")
Jan Pokorny a25a6f
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py
Jan Pokorny a25a6f
index bbde6303..23935009 100644
Jan Pokorny a25a6f
--- a/blivet/devicelibs/lvm.py
Jan Pokorny a25a6f
+++ b/blivet/devicelibs/lvm.py
Jan Pokorny a25a6f
@@ -54,6 +54,11 @@ LVM_THINP_MIN_CHUNK_SIZE = Size("64 KiB")
Jan Pokorny a25a6f
 LVM_THINP_MAX_CHUNK_SIZE = Size("1 GiB")
Jan Pokorny a25a6f
 LVM_THINP_ADDRESSABLE_CHUNK_SIZE = Size("17455015526400 B")  # 15.88 TiB
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
+# cache constants
Jan Pokorny a25a6f
+LVM_CACHE_MIN_METADATA_SIZE = Size("8 MiB")
Jan Pokorny a25a6f
+LVM_CACHE_MAX_METADATA_SIZE = Size("16 GiB")
Jan Pokorny a25a6f
+LVM_CACHE_DEFAULT_MODE = blockdev.LVMCacheMode.WRITETHROUGH
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
 raid_levels = raid.RAIDLevels(["linear", "striped", "raid1", "raid4", "raid5", "raid6", "raid10"])
Jan Pokorny a25a6f
 raid_seg_types = list(itertools.chain.from_iterable([level.names for level in raid_levels if level.name != "linear"]))
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
@@ -248,3 +253,7 @@ def recommend_thpool_chunk_size(thpool_size):
Jan Pokorny a25a6f
     # for every ~15.88 TiB of thinpool data size
Jan Pokorny a25a6f
     return min(math.ceil(thpool_size / LVM_THINP_ADDRESSABLE_CHUNK_SIZE) * LVM_THINP_MIN_CHUNK_SIZE,
Jan Pokorny a25a6f
                LVM_THINP_MAX_CHUNK_SIZE)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+def is_valid_cache_md_size(md_size):
Jan Pokorny a25a6f
+    return md_size >= LVM_CACHE_MIN_METADATA_SIZE and md_size <= LVM_CACHE_MAX_METADATA_SIZE
Jan Pokorny a25a6f
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
Jan Pokorny a25a6f
index a971da8e..7cb482ab 100644
Jan Pokorny a25a6f
--- a/blivet/devices/lvm.py
Jan Pokorny a25a6f
+++ b/blivet/devices/lvm.py
Jan Pokorny a25a6f
@@ -43,6 +43,7 @@ from .. import util
Jan Pokorny a25a6f
 from ..storage_log import log_method_call
Jan Pokorny a25a6f
 from .. import udev
Jan Pokorny a25a6f
 from ..size import Size, KiB, MiB, ROUND_UP, ROUND_DOWN
Jan Pokorny a25a6f
+from ..static_data.lvm_info import lvs_info
Jan Pokorny a25a6f
 from ..tasks import availability
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
 import logging
Jan Pokorny a25a6f
@@ -646,7 +647,7 @@ class LVMLogicalVolumeBase(DMDevice, RaidDevice):
Jan Pokorny a25a6f
                  percent=None, cache_request=None, pvs=None, from_lvs=None):
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
         if not exists:
Jan Pokorny a25a6f
-            if seg_type not in [None, "linear", "thin", "thin-pool", "cache", "vdo-pool", "vdo"] + lvm.raid_seg_types:
Jan Pokorny a25a6f
+            if seg_type not in [None, "linear", "thin", "thin-pool", "cache", "vdo-pool", "vdo", "cache-pool"] + lvm.raid_seg_types:
Jan Pokorny a25a6f
                 raise ValueError("Invalid or unsupported segment type: %s" % seg_type)
Jan Pokorny a25a6f
             if seg_type and seg_type in lvm.raid_seg_types and not pvs:
Jan Pokorny a25a6f
                 raise ValueError("List of PVs has to be given for every non-linear LV")
Jan Pokorny a25a6f
@@ -690,8 +691,8 @@ class LVMLogicalVolumeBase(DMDevice, RaidDevice):
Jan Pokorny a25a6f
             # we reserve space for it
Jan Pokorny a25a6f
             self._metadata_size = self.vg.pe_size
Jan Pokorny a25a6f
             self._size -= self._metadata_size
Jan Pokorny a25a6f
-        elif self.seg_type == "thin-pool":
Jan Pokorny a25a6f
-            # LVMThinPoolMixin sets self._metadata_size on its own
Jan Pokorny a25a6f
+        elif self.seg_type in ("thin-pool", "cache_pool"):
Jan Pokorny a25a6f
+            # LVMThinPoolMixin and LVMCachePoolMixin set self._metadata_size on their own
Jan Pokorny a25a6f
             if not self.exists and not from_lvs and not grow:
Jan Pokorny a25a6f
                 # a thin pool we are not going to grow -> lets calculate metadata
Jan Pokorny a25a6f
                 # size now if not given explicitly
Jan Pokorny a25a6f
@@ -1619,7 +1620,6 @@ class LVMThinPoolMixin(object):
Jan Pokorny a25a6f
         """ A list of this pool's LVs """
Jan Pokorny a25a6f
         return self._lvs[:]     # we don't want folks changing our list
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
-    @util.requires_property("is_thin_pool")
Jan Pokorny a25a6f
     def autoset_md_size(self, enforced=False):
Jan Pokorny a25a6f
         """ If self._metadata_size not set already, it calculates the recommended value
Jan Pokorny a25a6f
         and sets it while subtracting the size from self.size.
Jan Pokorny a25a6f
@@ -2032,9 +2032,142 @@ class LVMVDOLogicalVolumeMixin(object):
Jan Pokorny a25a6f
             self.pool._add_log_vol(self)
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
+class LVMCachePoolMixin(object):
Jan Pokorny a25a6f
+    def __init__(self, metadata_size, cache_mode=None):
Jan Pokorny a25a6f
+        self._metadata_size = metadata_size or Size(0)
Jan Pokorny a25a6f
+        self._cache_mode = cache_mode
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def _init_check(self):
Jan Pokorny a25a6f
+        if not self.is_cache_pool:
Jan Pokorny a25a6f
+            return
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        if self._metadata_size and not lvm.is_valid_cache_md_size(self._metadata_size):
Jan Pokorny a25a6f
+            raise ValueError("invalid metadatasize value")
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        if not self.exists and not self._pv_specs:
Jan Pokorny a25a6f
+            raise ValueError("at least one fast PV must be specified to create a cache pool")
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def _check_from_lvs(self):
Jan Pokorny a25a6f
+        if self._from_lvs:
Jan Pokorny a25a6f
+            if len(self._from_lvs) != 2:
Jan Pokorny a25a6f
+                raise errors.DeviceError("two LVs required to create a cache pool")
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def _convert_from_lvs(self):
Jan Pokorny a25a6f
+        data_lv, metadata_lv = self._from_lvs
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        data_lv.parent_lv = self  # also adds the LV to self._internal_lvs
Jan Pokorny a25a6f
+        data_lv.int_lv_type = LVMInternalLVtype.data
Jan Pokorny a25a6f
+        metadata_lv.parent_lv = self
Jan Pokorny a25a6f
+        metadata_lv.int_lv_type = LVMInternalLVtype.meta
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        self.size = data_lv.size
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    @property
Jan Pokorny a25a6f
+    def is_cache_pool(self):
Jan Pokorny a25a6f
+        return self.seg_type == "cache-pool"
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    @property
Jan Pokorny a25a6f
+    def profile(self):
Jan Pokorny a25a6f
+        return self._profile
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    @property
Jan Pokorny a25a6f
+    def type(self):
Jan Pokorny a25a6f
+        return "lvmcachepool"
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    @property
Jan Pokorny a25a6f
+    def resizable(self):
Jan Pokorny a25a6f
+        return False
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def read_current_size(self):
Jan Pokorny a25a6f
+        log_method_call(self, exists=self.exists, path=self.path,
Jan Pokorny a25a6f
+                        sysfs_path=self.sysfs_path)
Jan Pokorny a25a6f
+        if self.size != Size(0):
Jan Pokorny a25a6f
+            return self.size
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        if self.exists:
Jan Pokorny a25a6f
+            # cache pools are not active and don't have th device mapper mapping
Jan Pokorny a25a6f
+            # so we can't get this from sysfs
Jan Pokorny a25a6f
+            lv_info = lvs_info.cache.get(self.name)
Jan Pokorny a25a6f
+            if lv_info is None:
Jan Pokorny a25a6f
+                log.error("Failed to get size for existing cache pool '%s'", self.name)
Jan Pokorny a25a6f
+                return Size(0)
Jan Pokorny a25a6f
+            else:
Jan Pokorny a25a6f
+                return Size(lv_info.size)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        return Size(0)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def autoset_md_size(self, enforced=False):
Jan Pokorny a25a6f
+        """ If self._metadata_size not set already, it calculates the recommended value
Jan Pokorny a25a6f
+        and sets it while subtracting the size from self.size.
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        """
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        log.debug("Auto-setting cache pool metadata size")
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        if self._size <= Size(0):
Jan Pokorny a25a6f
+            log.debug("Cache pool size not bigger than 0, just setting metadata size to 0")
Jan Pokorny a25a6f
+            self._metadata_size = 0
Jan Pokorny a25a6f
+            return
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        old_md_size = self._metadata_size
Jan Pokorny a25a6f
+        if self._metadata_size == 0 or enforced:
Jan Pokorny a25a6f
+            self._metadata_size = blockdev.lvm.cache_get_default_md_size(self._size)
Jan Pokorny a25a6f
+            log.debug("Using recommended metadata size: %s", self._metadata_size)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        self._metadata_size = self.vg.align(self._metadata_size, roundup=True)
Jan Pokorny a25a6f
+        log.debug("Rounded metadata size to extents: %s MiB", self._metadata_size.convert_to("MiB"))
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        if self._metadata_size == old_md_size:
Jan Pokorny a25a6f
+            log.debug("Rounded metadata size unchanged")
Jan Pokorny a25a6f
+        else:
Jan Pokorny a25a6f
+            new_size = self.size - (self._metadata_size - old_md_size)
Jan Pokorny a25a6f
+            log.debug("Adjusting size from %s MiB to %s MiB",
Jan Pokorny a25a6f
+                      self.size.convert_to("MiB"), new_size.convert_to("MiB"))
Jan Pokorny a25a6f
+            self.size = new_size
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def _pre_create(self):
Jan Pokorny a25a6f
+        # make sure all the LVs this LV should be created from exist (if any)
Jan Pokorny a25a6f
+        if self._from_lvs and any(not lv.exists for lv in self._from_lvs):
Jan Pokorny a25a6f
+            raise errors.DeviceError("Component LVs need to be created first")
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def _create(self):
Jan Pokorny a25a6f
+        """ Create the device. """
Jan Pokorny a25a6f
+        log_method_call(self, self.name, status=self.status)
Jan Pokorny a25a6f
+        if self._cache_mode:
Jan Pokorny a25a6f
+            try:
Jan Pokorny a25a6f
+                cache_mode = blockdev.lvm.cache_get_mode_from_str(self._cache_mode)
Jan Pokorny a25a6f
+            except blockdev.LVMError as e:
Jan Pokorny a25a6f
+                raise errors.DeviceError from e
Jan Pokorny a25a6f
+        else:
Jan Pokorny a25a6f
+            cache_mode = lvm.LVM_CACHE_DEFAULT_MODE
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        if self._from_lvs:
Jan Pokorny a25a6f
+            extra = dict()
Jan Pokorny a25a6f
+            if self.mode:
Jan Pokorny a25a6f
+                # we need the string here, it will be passed directly to he lvm command
Jan Pokorny a25a6f
+                extra["cachemode"] = self._cache_mode
Jan Pokorny a25a6f
+            data_lv = six.next(lv for lv in self._internal_lvs if lv.int_lv_type == LVMInternalLVtype.data)
Jan Pokorny a25a6f
+            meta_lv = six.next(lv for lv in self._internal_lvs if lv.int_lv_type == LVMInternalLVtype.meta)
Jan Pokorny a25a6f
+            blockdev.lvm.cache_pool_convert(self.vg.name, data_lv.lvname, meta_lv.lvname, self.lvname, **extra)
Jan Pokorny a25a6f
+        else:
Jan Pokorny a25a6f
+            blockdev.lvm.cache_create_pool(self.vg.name, self.lvname, self.size,
Jan Pokorny a25a6f
+                                           self.metadata_size,
Jan Pokorny a25a6f
+                                           cache_mode,
Jan Pokorny a25a6f
+                                           0,
Jan Pokorny a25a6f
+                                           [spec.pv.path for spec in self._pv_specs])
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def dracut_setup_args(self):
Jan Pokorny a25a6f
+        return set()
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    @property
Jan Pokorny a25a6f
+    def direct(self):
Jan Pokorny a25a6f
+        """ Is this device directly accessible? """
Jan Pokorny a25a6f
+        return False
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
 class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin, LVMSnapshotMixin,
Jan Pokorny a25a6f
                              LVMThinPoolMixin, LVMThinLogicalVolumeMixin, LVMVDOPoolMixin,
Jan Pokorny a25a6f
-                             LVMVDOLogicalVolumeMixin):
Jan Pokorny a25a6f
+                             LVMVDOLogicalVolumeMixin, LVMCachePoolMixin):
Jan Pokorny a25a6f
     """ An LVM Logical Volume """
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
     # generally resizable, see :property:`resizable` for details
Jan Pokorny a25a6f
@@ -2046,7 +2179,7 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
Jan Pokorny a25a6f
                  parent_lv=None, int_type=None, origin=None, vorigin=False,
Jan Pokorny a25a6f
                  metadata_size=None, chunk_size=None, profile=None, from_lvs=None,
Jan Pokorny a25a6f
                  compression=False, deduplication=False, index_memory=0,
Jan Pokorny a25a6f
-                 write_policy=None):
Jan Pokorny a25a6f
+                 write_policy=None, cache_mode=None):
Jan Pokorny a25a6f
         """
Jan Pokorny a25a6f
             :param name: the device name (generally a device node's basename)
Jan Pokorny a25a6f
             :type name: str
Jan Pokorny a25a6f
@@ -2116,6 +2249,13 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
Jan Pokorny a25a6f
             :keyword write_policy: write policy for the volume or None for default
Jan Pokorny a25a6f
             :type write_policy: str
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
+            For cache pools only:
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+            :keyword metadata_size: the size of the metadata LV
Jan Pokorny a25a6f
+            :type metadata_size: :class:`~.size.Size`
Jan Pokorny a25a6f
+            :keyword cache_mode: mode for the cache or None for default (writethrough)
Jan Pokorny a25a6f
+            :type cache_mode: str
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
         """
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
         if isinstance(parents, (list, ParentList)):
Jan Pokorny a25a6f
@@ -2133,6 +2273,7 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
Jan Pokorny a25a6f
         LVMSnapshotMixin.__init__(self, origin, vorigin)
Jan Pokorny a25a6f
         LVMThinPoolMixin.__init__(self, metadata_size, chunk_size, profile)
Jan Pokorny a25a6f
         LVMThinLogicalVolumeMixin.__init__(self)
Jan Pokorny a25a6f
+        LVMCachePoolMixin.__init__(self, metadata_size, cache_mode)
Jan Pokorny a25a6f
         LVMLogicalVolumeBase.__init__(self, name, parents, size, uuid, seg_type,
Jan Pokorny a25a6f
                                       fmt, exists, sysfs_path, grow, maxsize,
Jan Pokorny a25a6f
                                       percent, cache_request, pvs, from_lvs)
Jan Pokorny a25a6f
@@ -2144,6 +2285,7 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
Jan Pokorny a25a6f
         LVMSnapshotMixin._init_check(self)
Jan Pokorny a25a6f
         LVMThinPoolMixin._init_check(self)
Jan Pokorny a25a6f
         LVMThinLogicalVolumeMixin._init_check(self)
Jan Pokorny a25a6f
+        LVMCachePoolMixin._init_check(self)
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
         if self._from_lvs:
Jan Pokorny a25a6f
             self._check_from_lvs()
Jan Pokorny a25a6f
@@ -2169,6 +2311,8 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
Jan Pokorny a25a6f
             ret.append(LVMVDOPoolMixin)
Jan Pokorny a25a6f
         if self.is_vdo_lv:
Jan Pokorny a25a6f
             ret.append(LVMVDOLogicalVolumeMixin)
Jan Pokorny a25a6f
+        if self.is_cache_pool:
Jan Pokorny a25a6f
+            ret.append(LVMCachePoolMixin)
Jan Pokorny a25a6f
         return ret
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
     def _try_specific_call(self, name, *args, **kwargs):
Jan Pokorny a25a6f
@@ -2552,6 +2696,10 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
         return True
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
+    @type_specific
Jan Pokorny a25a6f
+    def autoset_md_size(self, enforced=False):
Jan Pokorny a25a6f
+        pass
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
     def attach_cache(self, cache_pool_lv):
Jan Pokorny a25a6f
         if self.is_thin_lv or self.is_snapshot_lv or self.is_internal_lv:
Jan Pokorny a25a6f
             raise errors.DeviceError("Cannot attach a cache pool to the '%s' LV" % self.name)
Jan Pokorny a25a6f
diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py
Jan Pokorny a25a6f
index 59c027da..0105bcae 100644
Jan Pokorny a25a6f
--- a/tests/devices_test/lvm_test.py
Jan Pokorny a25a6f
+++ b/tests/devices_test/lvm_test.py
Jan Pokorny a25a6f
@@ -868,3 +868,29 @@ class BlivetLVMVDODependenciesTest(unittest.TestCase):
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
                 vdo_supported = devicefactory.is_supported_device_type(devicefactory.DEVICE_TYPE_LVM_VDO)
Jan Pokorny a25a6f
                 self.assertFalse(vdo_supported)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+@unittest.skipUnless(not any(x.unavailable_type_dependencies() for x in DEVICE_CLASSES), "some unsupported device classes required for this test")
Jan Pokorny a25a6f
+class BlivetNewLVMCachePoolDeviceTest(unittest.TestCase):
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def test_new_cache_pool(self):
Jan Pokorny a25a6f
+        b = blivet.Blivet()
Jan Pokorny a25a6f
+        pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
Jan Pokorny a25a6f
+                           size=Size("10 GiB"), exists=True)
Jan Pokorny a25a6f
+        vg = LVMVolumeGroupDevice("testvg", parents=[pv], exists=True)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        for dev in (pv, vg):
Jan Pokorny a25a6f
+            b.devicetree._add_device(dev)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        # check that all the above devices are in the expected places
Jan Pokorny a25a6f
+        self.assertEqual(set(b.devices), {pv, vg})
Jan Pokorny a25a6f
+        self.assertEqual(set(b.vgs), {vg})
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        self.assertEqual(vg.size, Size("10236 MiB"))
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        cachepool = b.new_lv(name="cachepool", cache_pool=True,
Jan Pokorny a25a6f
+                             parents=[vg], pvs=[pv])
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        b.create_device(cachepool)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        self.assertEqual(cachepool.type, "lvmcachepool")
Jan Pokorny a25a6f
-- 
Jan Pokorny a25a6f
2.34.3
Jan Pokorny a25a6f
Jan Pokorny a25a6f
Jan Pokorny a25a6f
From bfb0e71a92f46baae098370207640962c97d8e77 Mon Sep 17 00:00:00 2001
Jan Pokorny a25a6f
From: Vojtech Trefny <vtrefny@redhat.com>
Jan Pokorny a25a6f
Date: Thu, 30 Dec 2021 16:09:04 +0100
Jan Pokorny a25a6f
Subject: [PATCH 2/4] examples: Add LVM cache pool example
Jan Pokorny a25a6f
Jan Pokorny a25a6f
Related: rhbz#2055200
Jan Pokorny a25a6f
---
Jan Pokorny a25a6f
 examples/lvm_cachepool.py | 59 +++++++++++++++++++++++++++++++++++++++
Jan Pokorny a25a6f
 1 file changed, 59 insertions(+)
Jan Pokorny a25a6f
 create mode 100644 examples/lvm_cachepool.py
Jan Pokorny a25a6f
Jan Pokorny a25a6f
diff --git a/examples/lvm_cachepool.py b/examples/lvm_cachepool.py
Jan Pokorny a25a6f
new file mode 100644
Jan Pokorny a25a6f
index 00000000..ab2e8a72
Jan Pokorny a25a6f
--- /dev/null
Jan Pokorny a25a6f
+++ b/examples/lvm_cachepool.py
Jan Pokorny a25a6f
@@ -0,0 +1,59 @@
Jan Pokorny a25a6f
+import os
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+import blivet
Jan Pokorny a25a6f
+from blivet.size import Size
Jan Pokorny a25a6f
+from blivet.util import set_up_logging, create_sparse_tempfile
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+set_up_logging()
Jan Pokorny a25a6f
+b = blivet.Blivet()   # create an instance of Blivet (don't add system devices)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+# create a disk image file on which to create new devices
Jan Pokorny a25a6f
+disk1_file = create_sparse_tempfile("disk1", Size("100GiB"))
Jan Pokorny a25a6f
+b.disk_images["disk1"] = disk1_file
Jan Pokorny a25a6f
+disk2_file = create_sparse_tempfile("disk2", Size("100GiB"))
Jan Pokorny a25a6f
+b.disk_images["disk2"] = disk2_file
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+b.reset()
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+try:
Jan Pokorny a25a6f
+    disk1 = b.devicetree.get_device_by_name("disk1")
Jan Pokorny a25a6f
+    disk2 = b.devicetree.get_device_by_name("disk2")
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    b.initialize_disk(disk1)
Jan Pokorny a25a6f
+    b.initialize_disk(disk2)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    pv = b.new_partition(size=Size("50GiB"), fmt_type="lvmpv", parents=[disk1])
Jan Pokorny a25a6f
+    b.create_device(pv)
Jan Pokorny a25a6f
+    pv2 = b.new_partition(size=Size("50GiB"), fmt_type="lvmpv", parents=[disk2])
Jan Pokorny a25a6f
+    b.create_device(pv2)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    # allocate the partitions (decide where and on which disks they'll reside)
Jan Pokorny a25a6f
+    blivet.partitioning.do_partitioning(b)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    vg = b.new_vg(parents=[pv, pv2])
Jan Pokorny a25a6f
+    b.create_device(vg)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    # new lv with base size 5GiB and growth up to 15GiB and an ext4 filesystem
Jan Pokorny a25a6f
+    lv = b.new_lv(fmt_type="ext4", size=Size("5GiB"), parents=[vg], name="cached")
Jan Pokorny a25a6f
+    b.create_device(lv)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    # new cache pool
Jan Pokorny a25a6f
+    cpool = b.new_lv(size=Size("1 GiB"), parents=[vg], pvs=[pv2], cache_pool=True, name="fastlv")
Jan Pokorny a25a6f
+    b.create_device(cpool)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    # write the new partitions to disk and format them as specified
Jan Pokorny a25a6f
+    b.do_it()
Jan Pokorny a25a6f
+    print(b.devicetree)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    # attach the newly created cache pool to the "slow" LV
Jan Pokorny a25a6f
+    lv.attach_cache(cpool)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    b.reset()
Jan Pokorny a25a6f
+    print(b.devicetree)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    input("Check the state and hit ENTER to trigger cleanup")
Jan Pokorny a25a6f
+finally:
Jan Pokorny a25a6f
+    b.devicetree.teardown_disk_images()
Jan Pokorny a25a6f
+    os.unlink(disk1_file)
Jan Pokorny a25a6f
+    os.unlink(disk2_file)
Jan Pokorny a25a6f
-- 
Jan Pokorny a25a6f
2.34.3
Jan Pokorny a25a6f
Jan Pokorny a25a6f
Jan Pokorny a25a6f
From 1fece0e7f15f7b0d457d3db876d23c3272df09bd Mon Sep 17 00:00:00 2001
Jan Pokorny a25a6f
From: Vojtech Trefny <vtrefny@redhat.com>
Jan Pokorny a25a6f
Date: Thu, 30 Dec 2021 16:13:33 +0100
Jan Pokorny a25a6f
Subject: [PATCH 3/4] lvm: Use blivet static data when checking if the VG is
Jan Pokorny a25a6f
 active
Jan Pokorny a25a6f
Jan Pokorny a25a6f
Instead of calling 'lvs' again in LVMVolumeGroupDevice.status
Jan Pokorny a25a6f
Jan Pokorny a25a6f
Related: rhbz#2055200
Jan Pokorny a25a6f
---
Jan Pokorny a25a6f
 blivet/devices/lvm.py | 9 ++-------
Jan Pokorny a25a6f
 1 file changed, 2 insertions(+), 7 deletions(-)
Jan Pokorny a25a6f
Jan Pokorny a25a6f
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
Jan Pokorny a25a6f
index 7cb482ab..12d3d073 100644
Jan Pokorny a25a6f
--- a/blivet/devices/lvm.py
Jan Pokorny a25a6f
+++ b/blivet/devices/lvm.py
Jan Pokorny a25a6f
@@ -220,13 +220,8 @@ class LVMVolumeGroupDevice(ContainerDevice):
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
         # special handling for incomplete VGs
Jan Pokorny a25a6f
         if not self.complete:
Jan Pokorny a25a6f
-            try:
Jan Pokorny a25a6f
-                lvs_info = blockdev.lvm.lvs(vg_name=self.name)
Jan Pokorny a25a6f
-            except blockdev.LVMError:
Jan Pokorny a25a6f
-                lvs_info = []
Jan Pokorny a25a6f
-
Jan Pokorny a25a6f
-            for lv_info in lvs_info:
Jan Pokorny a25a6f
-                if lv_info.attr and lv_info.attr[4] == 'a':
Jan Pokorny a25a6f
+            for lv_info in lvs_info.cache.values():
Jan Pokorny a25a6f
+                if lv_info.vg_name == self.name and lv_info.attr and lv_info.attr[4] == 'a':
Jan Pokorny a25a6f
                     return True
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
             return False
Jan Pokorny a25a6f
-- 
Jan Pokorny a25a6f
2.34.3
Jan Pokorny a25a6f
Jan Pokorny a25a6f
Jan Pokorny a25a6f
From 8d957f04c2d5f56386b978d1bf890450f38ad108 Mon Sep 17 00:00:00 2001
Jan Pokorny a25a6f
From: Vojtech Trefny <vtrefny@redhat.com>
Jan Pokorny a25a6f
Date: Mon, 30 May 2022 17:02:43 +0200
Jan Pokorny a25a6f
Subject: [PATCH 4/4] Add option to attach a newly created cache pool to
Jan Pokorny a25a6f
 existing LV
Jan Pokorny a25a6f
Jan Pokorny a25a6f
Because we do not have action for attaching the cache pool, we
Jan Pokorny a25a6f
cannot schedule both adding the fast PV to the VG and attaching
Jan Pokorny a25a6f
the cache pool to existing LV. This hack allows to schedule the
Jan Pokorny a25a6f
attach to happen after the cache pool is created.
Jan Pokorny a25a6f
Jan Pokorny a25a6f
Related: rhbz#2055200
Jan Pokorny a25a6f
---
Jan Pokorny a25a6f
 blivet/devices/lvm.py | 38 +++++++++++++++++++++++++++++++++++---
Jan Pokorny a25a6f
 1 file changed, 35 insertions(+), 3 deletions(-)
Jan Pokorny a25a6f
Jan Pokorny a25a6f
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
Jan Pokorny a25a6f
index 12d3d073..feb92f2e 100644
Jan Pokorny a25a6f
--- a/blivet/devices/lvm.py
Jan Pokorny a25a6f
+++ b/blivet/devices/lvm.py
Jan Pokorny a25a6f
@@ -2028,9 +2028,10 @@ class LVMVDOLogicalVolumeMixin(object):
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
 class LVMCachePoolMixin(object):
Jan Pokorny a25a6f
-    def __init__(self, metadata_size, cache_mode=None):
Jan Pokorny a25a6f
+    def __init__(self, metadata_size, cache_mode=None, attach_to=None):
Jan Pokorny a25a6f
         self._metadata_size = metadata_size or Size(0)
Jan Pokorny a25a6f
         self._cache_mode = cache_mode
Jan Pokorny a25a6f
+        self._attach_to = attach_to
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
     def _init_check(self):
Jan Pokorny a25a6f
         if not self.is_cache_pool:
Jan Pokorny a25a6f
@@ -2042,6 +2043,9 @@ class LVMCachePoolMixin(object):
Jan Pokorny a25a6f
         if not self.exists and not self._pv_specs:
Jan Pokorny a25a6f
             raise ValueError("at least one fast PV must be specified to create a cache pool")
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
+        if self._attach_to and not self._attach_to.exists:
Jan Pokorny a25a6f
+            raise ValueError("cache pool can be attached only to an existing LV")
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
     def _check_from_lvs(self):
Jan Pokorny a25a6f
         if self._from_lvs:
Jan Pokorny a25a6f
             if len(self._from_lvs) != 2:
Jan Pokorny a25a6f
@@ -2150,6 +2154,31 @@ class LVMCachePoolMixin(object):
Jan Pokorny a25a6f
                                            cache_mode,
Jan Pokorny a25a6f
                                            0,
Jan Pokorny a25a6f
                                            [spec.pv.path for spec in self._pv_specs])
Jan Pokorny a25a6f
+        if self._attach_to:
Jan Pokorny a25a6f
+            self._attach_to.attach_cache(self)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def _post_create(self):
Jan Pokorny a25a6f
+        if self._attach_to:
Jan Pokorny a25a6f
+            # post_create tries to activate the LV and after attaching it no longer exists
Jan Pokorny a25a6f
+            return
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        # pylint: disable=bad-super-call
Jan Pokorny a25a6f
+        super(LVMLogicalVolumeBase, self)._post_create()
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def add_hook(self, new=True):
Jan Pokorny a25a6f
+        if self._attach_to:
Jan Pokorny a25a6f
+            self._attach_to._cache = LVMCache(self._attach_to, size=self.size, exists=False,
Jan Pokorny a25a6f
+                                              pvs=self._pv_specs, mode=self._cache_mode)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        # pylint: disable=bad-super-call
Jan Pokorny a25a6f
+        super(LVMLogicalVolumeBase, self).add_hook(new=new)
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+    def remove_hook(self, modparent=True):
Jan Pokorny a25a6f
+        if self._attach_to:
Jan Pokorny a25a6f
+            self._attach_to._cache = None
Jan Pokorny a25a6f
+
Jan Pokorny a25a6f
+        # pylint: disable=bad-super-call
Jan Pokorny a25a6f
+        super(LVMLogicalVolumeBase, self).remove_hook(modparent=modparent)
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
     def dracut_setup_args(self):
Jan Pokorny a25a6f
         return set()
Jan Pokorny a25a6f
@@ -2174,7 +2203,7 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
Jan Pokorny a25a6f
                  parent_lv=None, int_type=None, origin=None, vorigin=False,
Jan Pokorny a25a6f
                  metadata_size=None, chunk_size=None, profile=None, from_lvs=None,
Jan Pokorny a25a6f
                  compression=False, deduplication=False, index_memory=0,
Jan Pokorny a25a6f
-                 write_policy=None, cache_mode=None):
Jan Pokorny a25a6f
+                 write_policy=None, cache_mode=None, attach_to=None):
Jan Pokorny a25a6f
         """
Jan Pokorny a25a6f
             :param name: the device name (generally a device node's basename)
Jan Pokorny a25a6f
             :type name: str
Jan Pokorny a25a6f
@@ -2250,6 +2279,9 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
Jan Pokorny a25a6f
             :type metadata_size: :class:`~.size.Size`
Jan Pokorny a25a6f
             :keyword cache_mode: mode for the cache or None for default (writethrough)
Jan Pokorny a25a6f
             :type cache_mode: str
Jan Pokorny a25a6f
+            :keyword attach_to: for non-existing cache pools a logical volume the pool should
Jan Pokorny a25a6f
+                                be attached to when created
Jan Pokorny a25a6f
+            :type attach_to: :class:`LVMLogicalVolumeDevice`
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
         """
Jan Pokorny a25a6f
 
Jan Pokorny a25a6f
@@ -2268,7 +2300,7 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
Jan Pokorny a25a6f
         LVMSnapshotMixin.__init__(self, origin, vorigin)
Jan Pokorny a25a6f
         LVMThinPoolMixin.__init__(self, metadata_size, chunk_size, profile)
Jan Pokorny a25a6f
         LVMThinLogicalVolumeMixin.__init__(self)
Jan Pokorny a25a6f
-        LVMCachePoolMixin.__init__(self, metadata_size, cache_mode)
Jan Pokorny a25a6f
+        LVMCachePoolMixin.__init__(self, metadata_size, cache_mode, attach_to)
Jan Pokorny a25a6f
         LVMLogicalVolumeBase.__init__(self, name, parents, size, uuid, seg_type,
Jan Pokorny a25a6f
                                       fmt, exists, sysfs_path, grow, maxsize,
Jan Pokorny a25a6f
                                       percent, cache_request, pvs, from_lvs)
Jan Pokorny a25a6f
-- 
Jan Pokorny a25a6f
2.34.3
Jan Pokorny a25a6f