f01d01
From 18f05802f07f580ed31f38931b1103842397d598 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Mon, 2 Nov 2020 14:19:52 +0100
f01d01
Subject: [PATCH 01/17] Fix type of LVM VDO logical volumes
f01d01
f01d01
We should use "lvmvdolv" to make it similar to other "lvmXYZ"
f01d01
types.
f01d01
---
f01d01
 blivet/devices/lvm.py | 2 +-
f01d01
 1 file changed, 1 insertion(+), 1 deletion(-)
f01d01
f01d01
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
f01d01
index d9e24a33..9639256d 100644
f01d01
--- a/blivet/devices/lvm.py
f01d01
+++ b/blivet/devices/lvm.py
f01d01
@@ -1875,7 +1875,7 @@ def vg(self):
f01d01
 
f01d01
     @property
f01d01
     def type(self):
f01d01
-        return "vdolv"
f01d01
+        return "lvmvdolv"
f01d01
 
f01d01
     @property
f01d01
     def resizable(self):
f01d01
f01d01
From 7f4815e14075550f55f2afb44bfba461eacea1c4 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Mon, 2 Nov 2020 14:21:33 +0100
f01d01
Subject: [PATCH 02/17] Add VDO pool data LV to internal LVs during populate
f01d01
f01d01
---
f01d01
 blivet/devices/lvm.py           | 9 ++++++++-
f01d01
 blivet/populator/helpers/lvm.py | 2 +-
f01d01
 2 files changed, 9 insertions(+), 2 deletions(-)
f01d01
f01d01
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
f01d01
index 9639256d..d0957d6a 100644
f01d01
--- a/blivet/devices/lvm.py
f01d01
+++ b/blivet/devices/lvm.py
f01d01
@@ -1119,7 +1119,7 @@ class LVMInternalLVtype(Enum):
f01d01
 
f01d01
     @classmethod
f01d01
     def get_type(cls, lv_attr, lv_name):  # pylint: disable=unused-argument
f01d01
-        attr_letters = {cls.data: ("T", "C"),
f01d01
+        attr_letters = {cls.data: ("T", "C", "D"),
f01d01
                         cls.meta: ("e",),
f01d01
                         cls.log: ("l", "L"),
f01d01
                         cls.image: ("i", "I"),
f01d01
@@ -1824,6 +1824,13 @@ def _remove_log_vol(self, lv):
f01d01
         self._lvs.remove(lv)
f01d01
         self.vg._remove_log_vol(lv)
f01d01
 
f01d01
+    @property
f01d01
+    @util.requires_property("is_vdo_pool")
f01d01
+    def _vdopool_data_lv(self):
f01d01
+        if not self._internal_lvs:
f01d01
+            return None
f01d01
+        return self._internal_lvs[0]
f01d01
+
f01d01
     @property
f01d01
     @util.requires_property("is_vdo_pool")
f01d01
     def lvs(self):
f01d01
diff --git a/blivet/populator/helpers/lvm.py b/blivet/populator/helpers/lvm.py
f01d01
index ff8bf59f..b1626306 100644
f01d01
--- a/blivet/populator/helpers/lvm.py
f01d01
+++ b/blivet/populator/helpers/lvm.py
f01d01
@@ -211,7 +211,7 @@ def add_lv(lv):
f01d01
                     origin = self._devicetree.get_device_by_name(origin_device_name)
f01d01
 
f01d01
                 lv_kwargs["origin"] = origin
f01d01
-            elif lv_attr[0] in 'IrielTCo' and lv_name.endswith(']'):
f01d01
+            elif lv_attr[0] in 'IrielTCoD' and lv_name.endswith(']'):
f01d01
                 # an internal LV, add the an instance of the appropriate class
f01d01
                 # to internal_lvs for later processing when non-internal LVs are
f01d01
                 # processed
f01d01
f01d01
From c164864955e371aef78b5020f28bf0c9d235ac7c Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Mon, 2 Nov 2020 14:22:12 +0100
f01d01
Subject: [PATCH 03/17] Add availability functions for LVM VDO
f01d01
f01d01
VDO is currently available only on RHEL/CentOS so we need a
f01d01
separate availability check for LVM VDO devices.
f01d01
---
f01d01
 blivet/devices/lvm.py        | 6 ++++++
f01d01
 blivet/tasks/availability.py | 8 ++++++++
f01d01
 2 files changed, 14 insertions(+)
f01d01
f01d01
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
f01d01
index d0957d6a..ffc65dcd 100644
f01d01
--- a/blivet/devices/lvm.py
f01d01
+++ b/blivet/devices/lvm.py
f01d01
@@ -1790,6 +1790,9 @@ def populate_ksdata(self, data):
f01d01
 
f01d01
 
f01d01
 class LVMVDOPoolMixin(object):
f01d01
+
f01d01
+    _external_dependencies = [availability.BLOCKDEV_LVM_PLUGIN, availability.BLOCKDEV_LVM_PLUGIN_VDO]
f01d01
+
f01d01
     def __init__(self):
f01d01
         self._lvs = []
f01d01
 
f01d01
@@ -1848,6 +1851,9 @@ def _create(self):
f01d01
 
f01d01
 
f01d01
 class LVMVDOLogicalVolumeMixin(object):
f01d01
+
f01d01
+    _external_dependencies = [availability.BLOCKDEV_LVM_PLUGIN, availability.BLOCKDEV_LVM_PLUGIN_VDO]
f01d01
+
f01d01
     def __init__(self):
f01d01
         pass
f01d01
 
f01d01
diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py
f01d01
index f3b76650..b107428e 100644
f01d01
--- a/blivet/tasks/availability.py
f01d01
+++ b/blivet/tasks/availability.py
f01d01
@@ -372,6 +372,13 @@ def available_resource(name):
f01d01
                                                                            blockdev.LVMTechMode.MODIFY)})
f01d01
 BLOCKDEV_LVM_TECH = BlockDevMethod(BLOCKDEV_LVM)
f01d01
 
f01d01
+BLOCKDEV_LVM_VDO = BlockDevTechInfo(plugin_name="lvm",
f01d01
+                                    check_fn=blockdev.lvm_is_tech_avail,
f01d01
+                                    technologies={blockdev.LVMTech.VDO: (blockdev.LVMTechMode.CREATE |
f01d01
+                                                                         blockdev.LVMTechMode.REMOVE |
f01d01
+                                                                         blockdev.LVMTechMode.QUERY)})
f01d01
+BLOCKDEV_LVM_TECH_VDO = BlockDevMethod(BLOCKDEV_LVM_VDO)
f01d01
+
f01d01
 # libblockdev mdraid plugin required technologies and modes
f01d01
 BLOCKDEV_MD_ALL_MODES = (blockdev.MDTechMode.CREATE |
f01d01
                          blockdev.MDTechMode.DELETE |
f01d01
@@ -410,6 +417,7 @@ def available_resource(name):
f01d01
 BLOCKDEV_DM_PLUGIN_RAID = blockdev_plugin("dm", BLOCKDEV_DM_TECH_RAID)
f01d01
 BLOCKDEV_LOOP_PLUGIN = blockdev_plugin("loop", BLOCKDEV_LOOP_TECH)
f01d01
 BLOCKDEV_LVM_PLUGIN = blockdev_plugin("lvm", BLOCKDEV_LVM_TECH)
f01d01
+BLOCKDEV_LVM_PLUGIN_VDO = blockdev_plugin("lvm", BLOCKDEV_LVM_TECH_VDO)
f01d01
 BLOCKDEV_MDRAID_PLUGIN = blockdev_plugin("mdraid", BLOCKDEV_MD_TECH)
f01d01
 BLOCKDEV_MPATH_PLUGIN = blockdev_plugin("mpath", BLOCKDEV_MPATH_TECH)
f01d01
 BLOCKDEV_SWAP_PLUGIN = blockdev_plugin("swap", BLOCKDEV_SWAP_TECH)
f01d01
f01d01
From d782620129d47a7b79b0e6b80455e6d93f8bcc88 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Mon, 2 Nov 2020 14:27:55 +0100
f01d01
Subject: [PATCH 04/17] Read the LVM VDO pool current size from the internal
f01d01
 data LV
f01d01
f01d01
The pool device mapper device size is always 512k when active.
f01d01
---
f01d01
 blivet/devices/lvm.py | 9 +++++++++
f01d01
 1 file changed, 9 insertions(+)
f01d01
f01d01
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
f01d01
index ffc65dcd..73743fa8 100644
f01d01
--- a/blivet/devices/lvm.py
f01d01
+++ b/blivet/devices/lvm.py
f01d01
@@ -1845,6 +1845,15 @@ def direct(self):
f01d01
         """ Is this device directly accessible? """
f01d01
         return False
f01d01
 
f01d01
+    def read_current_size(self):
f01d01
+        log_method_call(self, exists=self.exists, path=self.path,
f01d01
+                        sysfs_path=self.sysfs_path)
f01d01
+        if self.size != Size(0):
f01d01
+            return self.size
f01d01
+        if self._vdopool_data_lv:
f01d01
+            return self._vdopool_data_lv.read_current_size()
f01d01
+        return Size(0)
f01d01
+
f01d01
     def _create(self):
f01d01
         """ Create the device. """
f01d01
         raise NotImplementedError
f01d01
f01d01
From 2da48ae84f4eac84e8cf998ee2402249a5a52626 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Mon, 2 Nov 2020 14:29:43 +0100
f01d01
Subject: [PATCH 05/17] Add "vdo_lv" property to LVMVDOPoolMixin
f01d01
f01d01
---
f01d01
 blivet/devices/lvm.py | 7 +++++++
f01d01
 1 file changed, 7 insertions(+)
f01d01
f01d01
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
f01d01
index 73743fa8..2f93fa22 100644
f01d01
--- a/blivet/devices/lvm.py
f01d01
+++ b/blivet/devices/lvm.py
f01d01
@@ -1840,6 +1840,13 @@ def lvs(self):
f01d01
         """ A list of this VDO pool's LVs """
f01d01
         return self._lvs[:]     # we don't want folks changing our list
f01d01
 
f01d01
+    @property
f01d01
+    @util.requires_property("is_vdo_pool")
f01d01
+    def vdo_lv(self):
f01d01
+        if not self._lvs:
f01d01
+            return None
f01d01
+        return self._lvs[0]
f01d01
+
f01d01
     @property
f01d01
     def direct(self):
f01d01
         """ Is this device directly accessible? """
f01d01
f01d01
From bbfa2cbdc6cb85d405b895c66eb4867cea4218b4 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Mon, 2 Nov 2020 14:30:37 +0100
f01d01
Subject: [PATCH 06/17] Add support for creating LVM VDO pools and LVM VDO
f01d01
 volumes
f01d01
f01d01
The pool and the volume are created by one call but these can have
f01d01
different properties (like size) and are in fact two block devices
f01d01
when created, we also need to create two devices and add them to
f01d01
the devicetree. The pool device must be always created first and
f01d01
the _create function for the VDO volume is a no-op.
f01d01
---
f01d01
 blivet/devices/lvm.py | 63 +++++++++++++++++++++++++++++++++++++------
f01d01
 1 file changed, 55 insertions(+), 8 deletions(-)
f01d01
f01d01
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
f01d01
index 2f93fa22..0802e2de 100644
f01d01
--- a/blivet/devices/lvm.py
f01d01
+++ b/blivet/devices/lvm.py
f01d01
@@ -311,7 +311,7 @@ def _add_log_vol(self, lv):
f01d01
 
f01d01
         # verify we have the space, then add it
f01d01
         # do not verify for growing vg (because of ks)
f01d01
-        if not lv.exists and not self.growable and not lv.is_thin_lv and lv.size > self.free_space:
f01d01
+        if not lv.exists and not self.growable and not (lv.is_thin_lv or lv.is_vdo_lv) and lv.size > self.free_space:
f01d01
             raise errors.DeviceError("new lv is too large to fit in free space", self.name)
f01d01
 
f01d01
         log.debug("Adding %s/%s to %s", lv.name, lv.size, self.name)
f01d01
@@ -639,7 +639,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
f01d01
                  percent=None, cache_request=None, pvs=None, from_lvs=None):
f01d01
 
f01d01
         if not exists:
f01d01
-            if seg_type not in [None, "linear", "thin", "thin-pool", "cache"] + lvm.raid_seg_types:
f01d01
+            if seg_type not in [None, "linear", "thin", "thin-pool", "cache", "vdo-pool", "vdo"] + lvm.raid_seg_types:
f01d01
                 raise ValueError("Invalid or unsupported segment type: %s" % seg_type)
f01d01
             if seg_type and seg_type in lvm.raid_seg_types and not pvs:
f01d01
                 raise ValueError("List of PVs has to be given for every non-linear LV")
f01d01
@@ -1793,7 +1793,11 @@ class LVMVDOPoolMixin(object):
f01d01
 
f01d01
     _external_dependencies = [availability.BLOCKDEV_LVM_PLUGIN, availability.BLOCKDEV_LVM_PLUGIN_VDO]
f01d01
 
f01d01
-    def __init__(self):
f01d01
+    def __init__(self, compression=True, deduplication=True, index_memory=0, write_policy=None):
f01d01
+        self.compression = compression
f01d01
+        self.deduplication = deduplication
f01d01
+        self.index_memory = index_memory
f01d01
+        self.write_policy = write_policy
f01d01
         self._lvs = []
f01d01
 
f01d01
     @property
f01d01
@@ -1863,7 +1867,19 @@ def read_current_size(self):
f01d01
 
f01d01
     def _create(self):
f01d01
         """ Create the device. """
f01d01
-        raise NotImplementedError
f01d01
+
f01d01
+        if not self.vdo_lv:
f01d01
+            raise errors.DeviceError("Cannot create new VDO pool without a VDO LV.")
f01d01
+
f01d01
+        if self.write_policy:
f01d01
+            write_policy = blockdev.lvm_get_vdo_write_policy_str(self.write_policy)
f01d01
+        else:
f01d01
+            write_policy = blockdev.LVMVDOWritePolicy.AUTO
f01d01
+
f01d01
+        blockdev.lvm.vdo_pool_create(self.vg.name, self.vdo_lv.lvname, self.lvname,
f01d01
+                                     self.size, self.vdo_lv.size, self.index_memory,
f01d01
+                                     self.compression, self.deduplication,
f01d01
+                                     write_policy)
f01d01
 
f01d01
 
f01d01
 class LVMVDOLogicalVolumeMixin(object):
f01d01
@@ -1915,9 +1931,26 @@ def resizable(self):
f01d01
     def pool(self):
f01d01
         return self.parents[0]
f01d01
 
f01d01
+    def _set_size(self, newsize):
f01d01
+        if not isinstance(newsize, Size):
f01d01
+            raise AttributeError("new size must of type Size")
f01d01
+
f01d01
+        newsize = self.vg.align(newsize)
f01d01
+        newsize = self.vg.align(util.numeric_type(newsize))
f01d01
+        # just make sure the size is set (no VG size/free space check needed for
f01d01
+        # a VDO LV)
f01d01
+        DMDevice._set_size(self, newsize)
f01d01
+
f01d01
+    def _pre_create(self):
f01d01
+        # skip LVMLogicalVolumeDevice's _pre_create() method as it checks for a
f01d01
+        # free space in a VG which doesn't make sense for a VDO LV and causes a
f01d01
+        # bug by limitting the VDO LV's size to VG free space which is nonsense
f01d01
+        super(LVMLogicalVolumeBase, self)._pre_create()  # pylint: disable=bad-super-call
f01d01
+
f01d01
     def _create(self):
f01d01
-        """ Create the device. """
f01d01
-        raise NotImplementedError
f01d01
+        # nothing to do here, VDO LV is created automatically together with
f01d01
+        # the VDO pool
f01d01
+        pass
f01d01
 
f01d01
     def _destroy(self):
f01d01
         # nothing to do here, VDO LV is destroyed automatically together with
f01d01
@@ -1953,7 +1986,9 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
f01d01
                  fmt=None, exists=False, sysfs_path='', grow=None, maxsize=None,
f01d01
                  percent=None, cache_request=None, pvs=None,
f01d01
                  parent_lv=None, int_type=None, origin=None, vorigin=False,
f01d01
-                 metadata_size=None, chunk_size=None, profile=None, from_lvs=None):
f01d01
+                 metadata_size=None, chunk_size=None, profile=None, from_lvs=None,
f01d01
+                 compression=False, deduplication=False, index_memory=0,
f01d01
+                 write_policy=None):
f01d01
         """
f01d01
             :param name: the device name (generally a device node's basename)
f01d01
             :type name: str
f01d01
@@ -2012,6 +2047,17 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
f01d01
             :keyword from_lvs: LVs to create the new LV from (in the (data_lv, metadata_lv) order)
f01d01
             :type from_lvs: tuple of :class:`LVMLogicalVolumeDevice`
f01d01
 
f01d01
+            For VDO pools only:
f01d01
+
f01d01
+            :keyword compression: whether to enable compression on the VDO pool
f01d01
+            :type compression: bool
f01d01
+            :keyword dudplication: whether to enable dudplication on the VDO pool
f01d01
+            :type dudplication: bool
f01d01
+            :keyword index_memory: amount of index memory (in bytes) or 0 for default
f01d01
+            :type index_memory: int
f01d01
+            :keyword write_policy: write policy for the volume or None for default
f01d01
+            :type write_policy: str
f01d01
+
f01d01
         """
f01d01
 
f01d01
         if isinstance(parents, (list, ParentList)):
f01d01
@@ -2032,7 +2078,8 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None,
f01d01
         LVMLogicalVolumeBase.__init__(self, name, parents, size, uuid, seg_type,
f01d01
                                       fmt, exists, sysfs_path, grow, maxsize,
f01d01
                                       percent, cache_request, pvs, from_lvs)
f01d01
-        LVMVDOPoolMixin.__init__(self)
f01d01
+        LVMVDOPoolMixin.__init__(self, compression, deduplication, index_memory,
f01d01
+                                 write_policy)
f01d01
         LVMVDOLogicalVolumeMixin.__init__(self)
f01d01
 
f01d01
         LVMInternalLogicalVolumeMixin._init_check(self)
f01d01
f01d01
From 2d1593b50dc6232e213b4df86dfbf5cf6d282dcd Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Mon, 2 Nov 2020 14:31:35 +0100
f01d01
Subject: [PATCH 07/17] Allow creating LVM VDO pools and volumes using
f01d01
 "blivet.new_lv"
f01d01
f01d01
The steps to create the VDO devices would typically look like:
f01d01
f01d01
pool = b.new_lv(vdo_pool=True, parents=[data], size=Size("8 GiB"))
f01d01
vdolv = b.new_lv(vdo_lv=True, parents=[pool], size=Size("40 GiB"))
f01d01
b.create_device(pool)
f01d01
b.create_device(vdolv)
f01d01
b.do_it()
f01d01
---
f01d01
 blivet/blivet.py               | 18 ++++++++++++++----
f01d01
 tests/devices_test/lvm_test.py | 31 +++++++++++++++++++++++++++++++
f01d01
 2 files changed, 45 insertions(+), 4 deletions(-)
f01d01
f01d01
diff --git a/blivet/blivet.py b/blivet/blivet.py
f01d01
index e7dbd37b..754eb152 100644
f01d01
--- a/blivet/blivet.py
f01d01
+++ b/blivet/blivet.py
f01d01
@@ -573,6 +573,10 @@ def new_lv(self, *args, **kwargs):
f01d01
             :type thin_pool: bool
f01d01
             :keyword thin_volume: whether to create a thin volume
f01d01
             :type thin_volume: bool
f01d01
+            :keyword vdo_pool: whether to create a vdo pool
f01d01
+            :type vdo_pool: bool
f01d01
+            :keyword vdo_lv: whether to create a vdo lv
f01d01
+            :type vdo_lv: bool
f01d01
             :returns: the new device
f01d01
             :rtype: :class:`~.devices.LVMLogicalVolumeDevice`
f01d01
 
f01d01
@@ -589,8 +593,10 @@ def new_lv(self, *args, **kwargs):
f01d01
         """
f01d01
         thin_volume = kwargs.pop("thin_volume", False)
f01d01
         thin_pool = kwargs.pop("thin_pool", False)
f01d01
+        vdo_pool = kwargs.pop("vdo_pool", False)
f01d01
+        vdo_lv = kwargs.pop("vdo_lv", False)
f01d01
         parent = kwargs.get("parents", [None])[0]
f01d01
-        if thin_volume and parent:
f01d01
+        if (thin_volume or vdo_lv) and parent:
f01d01
             # kwargs["parents"] will contain the pool device, so...
f01d01
             vg = parent.vg
f01d01
         else:
f01d01
@@ -600,6 +606,10 @@ def new_lv(self, *args, **kwargs):
f01d01
             kwargs["seg_type"] = "thin"
f01d01
         if thin_pool:
f01d01
             kwargs["seg_type"] = "thin-pool"
f01d01
+        if vdo_pool:
f01d01
+            kwargs["seg_type"] = "vdo-pool"
f01d01
+        if vdo_lv:
f01d01
+            kwargs["seg_type"] = "vdo"
f01d01
 
f01d01
         mountpoint = kwargs.pop("mountpoint", None)
f01d01
         if 'fmt_type' in kwargs:
f01d01
@@ -625,7 +635,7 @@ def new_lv(self, *args, **kwargs):
f01d01
                 swap = False
f01d01
 
f01d01
             prefix = ""
f01d01
-            if thin_pool:
f01d01
+            if thin_pool or vdo_pool:
f01d01
                 prefix = "pool"
f01d01
 
f01d01
             name = self.suggest_device_name(parent=vg,
f01d01
@@ -636,10 +646,10 @@ def new_lv(self, *args, **kwargs):
f01d01
         if "%s-%s" % (vg.name, name) in self.names:
f01d01
             raise ValueError("name already in use")
f01d01
 
f01d01
-        if thin_pool or thin_volume:
f01d01
+        if thin_pool or thin_volume or vdo_pool or vdo_lv:
f01d01
             cache_req = kwargs.pop("cache_request", None)
f01d01
             if cache_req:
f01d01
-                raise ValueError("Creating cached thin volumes and pools is not supported")
f01d01
+                raise ValueError("Creating cached thin and VDO volumes and pools is not supported")
f01d01
 
f01d01
         return LVMLogicalVolumeDevice(name, *args, **kwargs)
f01d01
 
f01d01
diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py
f01d01
index 204cb99a..493d3ba1 100644
f01d01
--- a/tests/devices_test/lvm_test.py
f01d01
+++ b/tests/devices_test/lvm_test.py
f01d01
@@ -689,3 +689,34 @@ def test_new_lv_from_non_existing_lvs(self):
f01d01
             with patch.object(pool, "_pre_create"):
f01d01
                 pool.create()
f01d01
                 self.assertTrue(lvm.thpool_convert.called)
f01d01
+
f01d01
+    def test_new_vdo_pool(self):
f01d01
+        b = blivet.Blivet()
f01d01
+        pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
f01d01
+                           size=Size("10 GiB"), exists=True)
f01d01
+        vg = LVMVolumeGroupDevice("testvg", parents=[pv], exists=True)
f01d01
+
f01d01
+        for dev in (pv, vg):
f01d01
+            b.devicetree._add_device(dev)
f01d01
+
f01d01
+        # check that all the above devices are in the expected places
f01d01
+        self.assertEqual(set(b.devices), {pv, vg})
f01d01
+        self.assertEqual(set(b.vgs), {vg})
f01d01
+
f01d01
+        self.assertEqual(vg.size, Size("10236 MiB"))
f01d01
+
f01d01
+        vdopool = b.new_lv(name="vdopool", vdo_pool=True,
f01d01
+                           parents=[vg], compression=True,
f01d01
+                           deduplication=True,
f01d01
+                           size=blivet.size.Size("8 GiB"))
f01d01
+
f01d01
+        vdolv = b.new_lv(name="vdolv", vdo_lv=True,
f01d01
+                         parents=[vdopool],
f01d01
+                         size=blivet.size.Size("40 GiB"))
f01d01
+
f01d01
+        b.create_device(vdopool)
f01d01
+        b.create_device(vdolv)
f01d01
+
f01d01
+        self.assertEqual(vdopool.children[0], vdolv)
f01d01
+        self.assertEqual(vdolv.parents[0], vdopool)
f01d01
+        self.assertListEqual(vg.lvs, [vdopool, vdolv])
f01d01
f01d01
From 31ec429ad7bd0857a768e2dfebe1de088dafc144 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Mon, 2 Nov 2020 14:32:47 +0100
f01d01
Subject: [PATCH 08/17] Add LVM VDO device factory
f01d01
f01d01
---
f01d01
 blivet/devicefactory.py     | 100 +++++++++++++++++++++++++++-
f01d01
 tests/devicefactory_test.py | 128 +++++++++++++++++++++++++++++++++---
f01d01
 2 files changed, 218 insertions(+), 10 deletions(-)
f01d01
f01d01
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
f01d01
index 9214ad54..c95037cc 100644
f01d01
--- a/blivet/devicefactory.py
f01d01
+++ b/blivet/devicefactory.py
f01d01
@@ -27,7 +27,7 @@
f01d01
 from .devices import BTRFSDevice, DiskDevice
f01d01
 from .devices import LUKSDevice, LVMLogicalVolumeDevice
f01d01
 from .devices import PartitionDevice, MDRaidArrayDevice
f01d01
-from .devices.lvm import DEFAULT_THPOOL_RESERVE
f01d01
+from .devices.lvm import LVMVDOPoolMixin, DEFAULT_THPOOL_RESERVE
f01d01
 from .formats import get_format
f01d01
 from .devicelibs import btrfs
f01d01
 from .devicelibs import mdraid
f01d01
@@ -58,6 +58,7 @@
f01d01
 DEVICE_TYPE_BTRFS = 3
f01d01
 DEVICE_TYPE_DISK = 4
f01d01
 DEVICE_TYPE_LVM_THINP = 5
f01d01
+DEVICE_TYPE_LVM_VDO = 6
f01d01
 
f01d01
 
f01d01
 def is_supported_device_type(device_type):
f01d01
@@ -69,6 +70,9 @@ def is_supported_device_type(device_type):
f01d01
         :returns: True if this device type is supported
f01d01
         :rtype: bool
f01d01
     """
f01d01
+    if device_type == DEVICE_TYPE_LVM_VDO:
f01d01
+        return not any(e for e in LVMVDOPoolMixin._external_dependencies if not e.available)
f01d01
+
f01d01
     devices = []
f01d01
     if device_type == DEVICE_TYPE_BTRFS:
f01d01
         devices = [BTRFSDevice]
f01d01
@@ -96,7 +100,7 @@ def get_supported_raid_levels(device_type):
f01d01
     pkg = None
f01d01
     if device_type == DEVICE_TYPE_BTRFS:
f01d01
         pkg = btrfs
f01d01
-    elif device_type in (DEVICE_TYPE_LVM, DEVICE_TYPE_LVM_THINP):
f01d01
+    elif device_type in (DEVICE_TYPE_LVM, DEVICE_TYPE_LVM_THINP, DEVICE_TYPE_LVM_VDO):
f01d01
         pkg = lvm
f01d01
     elif device_type == DEVICE_TYPE_MD:
f01d01
         pkg = mdraid
f01d01
@@ -116,6 +120,8 @@ def get_device_type(device):
f01d01
                     "lvmlv": DEVICE_TYPE_LVM,
f01d01
                     "lvmthinlv": DEVICE_TYPE_LVM_THINP,
f01d01
                     "lvmthinpool": DEVICE_TYPE_LVM,
f01d01
+                    "lvmvdolv": DEVICE_TYPE_LVM_VDO,
f01d01
+                    "lvmvdopool": DEVICE_TYPE_LVM,
f01d01
                     "btrfs subvolume": DEVICE_TYPE_BTRFS,
f01d01
                     "btrfs volume": DEVICE_TYPE_BTRFS,
f01d01
                     "mdarray": DEVICE_TYPE_MD}
f01d01
@@ -136,6 +142,7 @@ def get_device_factory(blivet, device_type=DEVICE_TYPE_LVM, **kwargs):
f01d01
                    DEVICE_TYPE_PARTITION: PartitionFactory,
f01d01
                    DEVICE_TYPE_MD: MDFactory,
f01d01
                    DEVICE_TYPE_LVM_THINP: LVMThinPFactory,
f01d01
+                   DEVICE_TYPE_LVM_VDO: LVMVDOFactory,
f01d01
                    DEVICE_TYPE_DISK: DeviceFactory}
f01d01
 
f01d01
     factory_class = class_table[device_type]
f01d01
@@ -1738,6 +1745,95 @@ def _get_new_device(self, *args, **kwargs):
f01d01
         return super(LVMThinPFactory, self)._get_new_device(*args, **kwargs)
f01d01
 
f01d01
 
f01d01
+class LVMVDOFactory(LVMFactory):
f01d01
+
f01d01
+    """ Factory for creating LVM VDO volumes.
f01d01
+
f01d01
+        :keyword pool_name: name for the VDO pool, if not specified unique name will be generated
f01d01
+        :type pool_name: str
f01d01
+        :keyword virtual_size: size for the VDO volume, usually bigger than pool size, if not
f01d01
+                               specified physical size (pool size) will be used
f01d01
+        :type size: :class:`~.size.Size`
f01d01
+        :keyword compression: whether to enable compression (defaults to True)
f01d01
+        :type compression: bool
f01d01
+        :keyword deduplication: whether to enable deduplication (defaults to True)
f01d01
+        :type deduplication: bool
f01d01
+    """
f01d01
+
f01d01
+    def __init__(self, storage, **kwargs):
f01d01
+        self.pool_name = kwargs.pop("pool_name", None)
f01d01
+        self.virtual_size = kwargs.pop("virtual_size", None)
f01d01
+        self.compression = kwargs.pop("compression", True)
f01d01
+        self.deduplication = kwargs.pop("deduplication", True)
f01d01
+        super(LVMVDOFactory, self).__init__(storage, **kwargs)
f01d01
+
f01d01
+    def _get_new_pool(self, *args, **kwargs):
f01d01
+        kwargs["vdo_pool"] = True
f01d01
+        return super(LVMVDOFactory, self)._get_new_device(*args, **kwargs)
f01d01
+
f01d01
+    def _set_device_size(self):
f01d01
+        """ Set the size of the factory device. """
f01d01
+        super(LVMVDOFactory, self)._set_device_size()
f01d01
+
f01d01
+        self.device.pool.size = self.size
f01d01
+        self._reconfigure_container()
f01d01
+
f01d01
+        if not self.virtual_size or self.virtual_size < self.size:
f01d01
+            # virtual_size is not set or smaller than current size --> it should be same as the pool size
f01d01
+            self.device.size = self.size
f01d01
+        else:
f01d01
+            self.device.size = self.virtual_size
f01d01
+
f01d01
+    def _set_pool_name(self):
f01d01
+        safe_new_name = self.storage.safe_device_name(self.pool_name)
f01d01
+        if self.device.pool.name != safe_new_name:
f01d01
+            if not safe_new_name:
f01d01
+                log.error("not renaming '%s' to invalid name '%s'",
f01d01
+                          self.device.pool.name, self.pool_name)
f01d01
+                return
f01d01
+            if safe_new_name in self.storage.names:
f01d01
+                log.error("not renaming '%s' to in-use name '%s'",
f01d01
+                          self.device.pool.name, safe_new_name)
f01d01
+                return
f01d01
+
f01d01
+            log.debug("renaming device '%s' to '%s'",
f01d01
+                      self.device.pool.name, safe_new_name)
f01d01
+            self.device.pool.raw_device.name = safe_new_name
f01d01
+
f01d01
+    def _set_name(self):
f01d01
+        super(LVMVDOFactory, self)._set_name()
f01d01
+        if self.pool_name:
f01d01
+            self._set_pool_name()
f01d01
+
f01d01
+    def _reconfigure_device(self):
f01d01
+        super(LVMVDOFactory, self)._reconfigure_device()
f01d01
+
f01d01
+        self.device.pool.compression = self.compression
f01d01
+        self.device.pool.deduplication = self.deduplication
f01d01
+
f01d01
+    #
f01d01
+    # methods to configure the factory's device
f01d01
+    #
f01d01
+    def _get_new_device(self, *args, **kwargs):
f01d01
+        """ Create and return the factory device as a StorageDevice. """
f01d01
+        pool = self._get_new_pool(name=self.pool_name,
f01d01
+                                  size=self.size,
f01d01
+                                  parents=[self.vg],
f01d01
+                                  compression=self.compression,
f01d01
+                                  deduplication=self.deduplication)
f01d01
+        self.storage.create_device(pool)
f01d01
+
f01d01
+        kwargs["parents"] = [pool]
f01d01
+        kwargs["vdo_lv"] = True
f01d01
+
f01d01
+        if self.virtual_size:
f01d01
+            vdolv_kwargs = kwargs.copy()
f01d01
+            vdolv_kwargs["size"] = self.virtual_size
f01d01
+        else:
f01d01
+            vdolv_kwargs = kwargs
f01d01
+        return super(LVMVDOFactory, self)._get_new_device(*args, **vdolv_kwargs)
f01d01
+
f01d01
+
f01d01
 class MDFactory(DeviceFactory):
f01d01
 
f01d01
     """ Factory for creating MD RAID devices. """
f01d01
diff --git a/tests/devicefactory_test.py b/tests/devicefactory_test.py
f01d01
index 08068779..7cdb51c5 100644
f01d01
--- a/tests/devicefactory_test.py
f01d01
+++ b/tests/devicefactory_test.py
f01d01
@@ -4,6 +4,9 @@
f01d01
 from decimal import Decimal
f01d01
 import os
f01d01
 
f01d01
+import test_compat  # pylint: disable=unused-import
f01d01
+from six.moves.mock import patch  # pylint: disable=no-name-in-module,import-error
f01d01
+
f01d01
 import blivet
f01d01
 
f01d01
 from blivet import devicefactory
f01d01
@@ -93,10 +96,12 @@ def _validate_factory_device(self, *args, **kwargs):
f01d01
             self.assertEqual(device.format.label,
f01d01
                              kwargs.get('label'))
f01d01
 
f01d01
-        self.assertLessEqual(device.size, kwargs.get("size"))
f01d01
-        self.assertGreaterEqual(device.size, device.format.min_size)
f01d01
-        if device.format.max_size:
f01d01
-            self.assertLessEqual(device.size, device.format.max_size)
f01d01
+        # sizes with VDO are special, we have a special check in LVMVDOFactoryTestCase._validate_factory_device
f01d01
+        if device_type != devicefactory.DEVICE_TYPE_LVM_VDO:
f01d01
+            self.assertLessEqual(device.size, kwargs.get("size"))
f01d01
+            self.assertGreaterEqual(device.size, device.format.min_size)
f01d01
+            if device.format.max_size:
f01d01
+                self.assertLessEqual(device.size, device.format.max_size)
f01d01
 
f01d01
         self.assertEqual(device.encrypted,
f01d01
                          kwargs.get("encrypted", False) or
f01d01
@@ -115,7 +120,11 @@ def test_device_factory(self):
f01d01
                   "mountpoint": '/factorytest'}
f01d01
         device = self._factory_device(device_type, **kwargs)
f01d01
         self._validate_factory_device(device, device_type, **kwargs)
f01d01
-        self.b.recursive_remove(device)
f01d01
+
f01d01
+        if device.type == "lvmvdolv":
f01d01
+            self.b.recursive_remove(device.pool)
f01d01
+        else:
f01d01
+            self.b.recursive_remove(device)
f01d01
 
f01d01
         if self.encryption_supported:
f01d01
             # Encrypt the leaf device
f01d01
@@ -157,6 +166,12 @@ def test_device_factory(self):
f01d01
         device = self._factory_device(device_type, **kwargs)
f01d01
         self._validate_factory_device(device, device_type, **kwargs)
f01d01
 
f01d01
+        # change size up
f01d01
+        kwargs["device"] = device
f01d01
+        kwargs["size"] = Size("900 MiB")
f01d01
+        device = self._factory_device(device_type, **kwargs)
f01d01
+        self._validate_factory_device(device, device_type, **kwargs)
f01d01
+
f01d01
         # Change LUKS version
f01d01
         kwargs["luks_version"] = "luks1"
f01d01
         device = self._factory_device(device_type, **kwargs)
f01d01
@@ -179,7 +194,7 @@ def _get_size_delta(self, devices=None):
f01d01
         """
f01d01
         return Size("1 MiB")
f01d01
 
f01d01
-    def test_get_free_disk_space(self):
f01d01
+    def test_get_free_disk_space(self, *args):  # pylint: disable=unused-argument
f01d01
         # get_free_disk_space should return the total free space on disks
f01d01
         kwargs = self._get_test_factory_args()
f01d01
         kwargs["size"] = Size("500 MiB")
f01d01
@@ -206,7 +221,7 @@ def test_get_free_disk_space(self):
f01d01
                                sum(d.size for d in self.b.disks) - device_space,
f01d01
                                delta=self._get_size_delta(devices=[device]))
f01d01
 
f01d01
-    def test_normalize_size(self):
f01d01
+    def test_normalize_size(self, *args):  # pylint: disable=unused-argument
f01d01
         # _normalize_size should adjust target size to within the format limits
f01d01
         fstype = "ext2"
f01d01
         ext2 = get_format(fstype)
f01d01
@@ -258,7 +273,7 @@ def test_default_factory_type(self):
f01d01
         factory = devicefactory.get_device_factory(self.b)
f01d01
         self.assertIsInstance(factory, devicefactory.LVMFactory)
f01d01
 
f01d01
-    def test_factory_defaults(self):
f01d01
+    def test_factory_defaults(self, *args):  # pylint: disable=unused-argument
f01d01
         ctor_kwargs = self._get_test_factory_args()
f01d01
         factory = devicefactory.get_device_factory(self.b, self.device_type, **ctor_kwargs)
f01d01
         for setting, value in factory._default_settings.items():
f01d01
@@ -522,6 +537,103 @@ def _get_size_delta(self, devices=None):
f01d01
         return delta
f01d01
 
f01d01
 
f01d01
+class LVMVDOFactoryTestCase(LVMFactoryTestCase):
f01d01
+    device_class = LVMLogicalVolumeDevice
f01d01
+    device_type = devicefactory.DEVICE_TYPE_LVM_VDO
f01d01
+    encryption_supported = False
f01d01
+
f01d01
+    def _validate_factory_device(self, *args, **kwargs):
f01d01
+        super(LVMVDOFactoryTestCase, self)._validate_factory_device(*args,
f01d01
+                                                                    **kwargs)
f01d01
+        device = args[0]
f01d01
+
f01d01
+        if kwargs.get("encrypted", False):
f01d01
+            vdolv = device.parents[0]
f01d01
+        else:
f01d01
+            vdolv = device
f01d01
+
f01d01
+        self.assertTrue(hasattr(vdolv, "pool"))
f01d01
+
f01d01
+        virtual_size = kwargs.get("virtual_size", 0)
f01d01
+        if virtual_size:
f01d01
+            self.assertEqual(vdolv.size, virtual_size)
f01d01
+        else:
f01d01
+            self.assertEqual(vdolv.size, vdolv.pool.size)
f01d01
+        self.assertGreaterEqual(vdolv.size, vdolv.pool.size)
f01d01
+
f01d01
+        compression = kwargs.get("compression", True)
f01d01
+        self.assertEqual(vdolv.pool.compression, compression)
f01d01
+
f01d01
+        deduplication = kwargs.get("deduplication", True)
f01d01
+        self.assertEqual(vdolv.pool.deduplication, deduplication)
f01d01
+
f01d01
+        pool_name = kwargs.get("pool_name", None)
f01d01
+        if pool_name:
f01d01
+            self.assertEqual(vdolv.pool.lvname, pool_name)
f01d01
+
f01d01
+        return device
f01d01
+
f01d01
+    @patch("blivet.formats.lvmpv.LVMPhysicalVolume.formattable", return_value=True)
f01d01
+    @patch("blivet.formats.lvmpv.LVMPhysicalVolume.destroyable", return_value=True)
f01d01
+    @patch("blivet.static_data.lvm_info.blockdev.lvm.lvs", return_value=[])
f01d01
+    @patch("blivet.devices.lvm.LVMVolumeGroupDevice.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMLogicalVolumeBase.type_external_dependencies", return_value=set())
f01d01
+    def test_device_factory(self, *args):  # pylint: disable=unused-argument,arguments-differ
f01d01
+        device_type = self.device_type
f01d01
+        kwargs = {"disks": self.b.disks,
f01d01
+                  "size": Size("400 MiB"),
f01d01
+                  "fstype": 'ext4',
f01d01
+                  "mountpoint": '/factorytest'}
f01d01
+        device = self._factory_device(device_type, **kwargs)
f01d01
+        self._validate_factory_device(device, device_type, **kwargs)
f01d01
+        self.b.recursive_remove(device.pool)
f01d01
+
f01d01
+        kwargs = {"disks": self.b.disks,
f01d01
+                  "size": Size("400 MiB"),
f01d01
+                  "fstype": 'ext4',
f01d01
+                  "mountpoint": '/factorytest',
f01d01
+                  "pool_name": "vdopool",
f01d01
+                  "deduplication": True,
f01d01
+                  "compression": True}
f01d01
+        device = self._factory_device(device_type, **kwargs)
f01d01
+        self._validate_factory_device(device, device_type, **kwargs)
f01d01
+
f01d01
+        # change size without specifying virtual_size: both sizes should grow
f01d01
+        kwargs["size"] = Size("600 MiB")
f01d01
+        kwargs["device"] = device
f01d01
+        device = self._factory_device(device_type, **kwargs)
f01d01
+        self._validate_factory_device(device, device_type, **kwargs)
f01d01
+
f01d01
+        # change virtual size
f01d01
+        kwargs["virtual_size"] = Size("6 GiB")
f01d01
+        kwargs["device"] = device
f01d01
+        device = self._factory_device(device_type, **kwargs)
f01d01
+        self._validate_factory_device(device, device_type, **kwargs)
f01d01
+
f01d01
+        # change virtual size to smaller than size
f01d01
+        kwargs["virtual_size"] = Size("500 GiB")
f01d01
+        kwargs["device"] = device
f01d01
+        device = self._factory_device(device_type, **kwargs)
f01d01
+        self._validate_factory_device(device, device_type, **kwargs)
f01d01
+
f01d01
+        # change deduplication and compression
f01d01
+        kwargs["deduplication"] = False
f01d01
+        kwargs["device"] = device
f01d01
+        device = self._factory_device(device_type, **kwargs)
f01d01
+        self._validate_factory_device(device, device_type, **kwargs)
f01d01
+
f01d01
+        kwargs["compression"] = False
f01d01
+        kwargs["device"] = device
f01d01
+        device = self._factory_device(device_type, **kwargs)
f01d01
+        self._validate_factory_device(device, device_type, **kwargs)
f01d01
+
f01d01
+        # rename the pool
f01d01
+        kwargs["pool_name"] = "vdopool2"
f01d01
+        kwargs["device"] = device
f01d01
+        device = self._factory_device(device_type, **kwargs)
f01d01
+        self._validate_factory_device(device, device_type, **kwargs)
f01d01
+
f01d01
+
f01d01
 class MDFactoryTestCase(DeviceFactoryTestCase):
f01d01
     device_type = devicefactory.DEVICE_TYPE_MD
f01d01
     device_class = MDRaidArrayDevice
f01d01
f01d01
From 22ba2b96111d5f153a3b55d3c56d84e597cf9a90 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Mon, 2 Nov 2020 14:33:06 +0100
f01d01
Subject: [PATCH 09/17] Add VM test for LVM VDO
f01d01
f01d01
---
f01d01
 tests/vmtests/blivet_reset_vmtest.py | 15 +++++++++++++++
f01d01
 tests/vmtests/runvmtests.py          |  3 ++-
f01d01
 2 files changed, 17 insertions(+), 1 deletion(-)
f01d01
f01d01
diff --git a/tests/vmtests/blivet_reset_vmtest.py b/tests/vmtests/blivet_reset_vmtest.py
f01d01
index 8743d51e..47fc84c4 100644
f01d01
--- a/tests/vmtests/blivet_reset_vmtest.py
f01d01
+++ b/tests/vmtests/blivet_reset_vmtest.py
f01d01
@@ -192,6 +192,21 @@ def setUp(self):
f01d01
         self.collect_expected_data()
f01d01
 
f01d01
 
f01d01
+class LVMVDOTestCase(BlivetResetTestCase):
f01d01
+
f01d01
+    def _set_up_storage(self):
f01d01
+        if not devicefactory.is_supported_device_type(devicefactory.DEVICE_TYPE_LVM_VDO):
f01d01
+            self.skipTest("VDO not supported, skipping")
f01d01
+
f01d01
+        self.blivet.factory_device(devicefactory.DEVICE_TYPE_LVM_VDO,
f01d01
+                                   size=Size("10 GiB"),
f01d01
+                                   fstype="ext4",
f01d01
+                                   disks=self.blivet.disks[:],
f01d01
+                                   name="vdolv",
f01d01
+                                   pool_name="vdopool",
f01d01
+                                   virtual_size=Size("40 GiB"))
f01d01
+
f01d01
+
f01d01
 @unittest.skip("temporarily disabled due to issues with raids with metadata version 0.90")
f01d01
 class MDRaid0TestCase(BlivetResetTestCase):
f01d01
 
f01d01
diff --git a/tests/vmtests/runvmtests.py b/tests/vmtests/runvmtests.py
f01d01
index 88143d3a..6f20484f 100644
f01d01
--- a/tests/vmtests/runvmtests.py
f01d01
+++ b/tests/vmtests/runvmtests.py
f01d01
@@ -12,7 +12,8 @@
f01d01
          "tests.vmtests.blivet_reset_vmtest.LVMThinSnapShotTestCase",
f01d01
          "tests.vmtests.blivet_reset_vmtest.LVMRaidTestCase",
f01d01
          "tests.vmtests.blivet_reset_vmtest.MDRaid0TestCase",
f01d01
-         "tests.vmtests.blivet_reset_vmtest.LVMOnMDTestCase"]
f01d01
+         "tests.vmtests.blivet_reset_vmtest.LVMOnMDTestCase",
f01d01
+         "tests.vmtests.blivet_reset_vmtest.LVMVDOTestCase"]
f01d01
 
f01d01
 SNAP_NAME = "snapshot"
f01d01
 
f01d01
f01d01
From 52b37bb86e856f1ede71f7cceb7284a639d741f4 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Thu, 19 Nov 2020 13:07:17 +0100
f01d01
Subject: [PATCH 10/17] Allow adding nodiscard option when running mkfs
f01d01
f01d01
For filesystems that support it we might want to add some nodiscard
f01d01
option to mkfs when creating format on devices like LVM VDO
f01d01
volumes where discard is very slow and doesn't really makes sense
f01d01
when running mkfs.
f01d01
---
f01d01
 blivet/formats/fs.py               | 12 +++++-
f01d01
 blivet/tasks/fsmkfs.py             | 59 +++++++++++++++++++++++++++---
f01d01
 tests/formats_test/methods_test.py |  3 +-
f01d01
 3 files changed, 66 insertions(+), 8 deletions(-)
f01d01
f01d01
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
f01d01
index 4ba83e6d..e61e5b86 100644
f01d01
--- a/blivet/formats/fs.py
f01d01
+++ b/blivet/formats/fs.py
f01d01
@@ -132,6 +132,7 @@ def __init__(self, **kwargs):
f01d01
         self.mountopts = kwargs.get("mountopts", "")
f01d01
         self.label = kwargs.get("label")
f01d01
         self.fsprofile = kwargs.get("fsprofile")
f01d01
+        self._mkfs_nodiscard = kwargs.get("nodiscard", False)
f01d01
 
f01d01
         self._user_mountopts = self.mountopts
f01d01
 
f01d01
@@ -263,6 +264,14 @@ def label_format_ok(self, label):
f01d01
     label = property(lambda s: s._get_label(), lambda s, l: s._set_label(l),
f01d01
                      doc="this filesystem's label")
f01d01
 
f01d01
+    def can_nodiscard(self):
f01d01
+        """Returns True if this filesystem supports nodiscard option during
f01d01
+           creation, otherwise False.
f01d01
+
f01d01
+           :rtype: bool
f01d01
+        """
f01d01
+        return self._mkfs.can_nodiscard and self._mkfs.available
f01d01
+
f01d01
     def can_set_uuid(self):
f01d01
         """Returns True if this filesystem supports setting an UUID during
f01d01
            creation, otherwise False.
f01d01
@@ -402,7 +411,8 @@ def _create(self, **kwargs):
f01d01
         try:
f01d01
             self._mkfs.do_task(options=kwargs.get("options"),
f01d01
                                label=not self.relabels(),
f01d01
-                               set_uuid=self.can_set_uuid())
f01d01
+                               set_uuid=self.can_set_uuid(),
f01d01
+                               nodiscard=self.can_nodiscard())
f01d01
         except FSWriteLabelError as e:
f01d01
             log.warning("Choosing not to apply label (%s) during creation of filesystem %s. Label format is unacceptable for this filesystem.", self.label, self.type)
f01d01
         except FSWriteUUIDError as e:
f01d01
diff --git a/blivet/tasks/fsmkfs.py b/blivet/tasks/fsmkfs.py
f01d01
index ad166aa0..c982f7e7 100644
f01d01
--- a/blivet/tasks/fsmkfs.py
f01d01
+++ b/blivet/tasks/fsmkfs.py
f01d01
@@ -37,6 +37,7 @@ class FSMkfsTask(fstask.FSTask):
f01d01
 
f01d01
     can_label = abc.abstractproperty(doc="whether this task labels")
f01d01
     can_set_uuid = abc.abstractproperty(doc="whether this task can set UUID")
f01d01
+    can_nodiscard = abc.abstractproperty(doc="whether this task can set nodiscard option")
f01d01
 
f01d01
 
f01d01
 @add_metaclass(abc.ABCMeta)
f01d01
@@ -48,6 +49,9 @@ class FSMkfs(task.BasicApplication, FSMkfsTask):
f01d01
     label_option = abc.abstractproperty(
f01d01
         doc="Option for setting a filesystem label.")
f01d01
 
f01d01
+    nodiscard_option = abc.abstractproperty(
f01d01
+        doc="Option for setting nodiscrad option for mkfs.")
f01d01
+
f01d01
     args = abc.abstractproperty(doc="options for creating filesystem")
f01d01
 
f01d01
     @abc.abstractmethod
f01d01
@@ -80,6 +84,15 @@ def can_set_uuid(self):
f01d01
         """
f01d01
         return self.get_uuid_args is not None
f01d01
 
f01d01
+    @property
f01d01
+    def can_nodiscard(self):
f01d01
+        """Whether this task can set nodiscard option for a filesystem.
f01d01
+
f01d01
+           :returns: True if nodiscard can be set
f01d01
+           :rtype: bool
f01d01
+        """
f01d01
+        return self.nodiscard_option is not None
f01d01
+
f01d01
     @property
f01d01
     def _label_options(self):
f01d01
         """ Any labeling options that a particular filesystem may use.
f01d01
@@ -100,6 +113,23 @@ def _label_options(self):
f01d01
         else:
f01d01
             raise FSWriteLabelError("Choosing not to apply label (%s) during creation of filesystem %s. Label format is unacceptable for this filesystem." % (self.fs.label, self.fs.type))
f01d01
 
f01d01
+    @property
f01d01
+    def _nodiscard_option(self):
f01d01
+        """ Any nodiscard options that a particular filesystem may use.
f01d01
+
f01d01
+            :returns: nodiscard options
f01d01
+            :rtype: list of str
f01d01
+        """
f01d01
+        # Do not know how to set nodiscard while formatting.
f01d01
+        if self.nodiscard_option is None:
f01d01
+            return []
f01d01
+
f01d01
+        # nodiscard option not requested
f01d01
+        if not self.fs._mkfs_nodiscard:
f01d01
+            return []
f01d01
+
f01d01
+        return self.nodiscard_option
f01d01
+
f01d01
     @property
f01d01
     def _uuid_options(self):
f01d01
         """Any UUID options that a particular filesystem may use.
f01d01
@@ -119,7 +149,7 @@ def _uuid_options(self):
f01d01
                                    " is unacceptable for this filesystem."
f01d01
                                    % (self.fs.uuid, self.fs.type))
f01d01
 
f01d01
-    def _format_options(self, options=None, label=False, set_uuid=False):
f01d01
+    def _format_options(self, options=None, label=False, set_uuid=False, nodiscard=False):
f01d01
         """Get a list of format options to be used when creating the
f01d01
            filesystem.
f01d01
 
f01d01
@@ -135,11 +165,12 @@ def _format_options(self, options=None, label=False, set_uuid=False):
f01d01
 
f01d01
         label_options = self._label_options if label else []
f01d01
         uuid_options = self._uuid_options if set_uuid else []
f01d01
+        nodiscard_option = self._nodiscard_option if nodiscard else []
f01d01
         create_options = shlex.split(self.fs.create_options or "")
f01d01
         return (options + self.args + label_options + uuid_options +
f01d01
-                create_options + [self.fs.device])
f01d01
+                nodiscard_option + create_options + [self.fs.device])
f01d01
 
f01d01
-    def _mkfs_command(self, options, label, set_uuid):
f01d01
+    def _mkfs_command(self, options, label, set_uuid, nodiscard):
f01d01
         """Return the command to make the filesystem.
f01d01
 
f01d01
            :param options: any special options
f01d01
@@ -148,12 +179,14 @@ def _mkfs_command(self, options, label, set_uuid):
f01d01
            :type label: bool
f01d01
            :param set_uuid: whether to set an UUID
f01d01
            :type set_uuid: bool
f01d01
+           :param nodiscard: whether to run mkfs with nodiscard option
f01d01
+           :type nodiscard: bool
f01d01
            :returns: the mkfs command
f01d01
            :rtype: list of str
f01d01
         """
f01d01
-        return [str(self.ext)] + self._format_options(options, label, set_uuid)
f01d01
+        return [str(self.ext)] + self._format_options(options, label, set_uuid, nodiscard)
f01d01
 
f01d01
-    def do_task(self, options=None, label=False, set_uuid=False):
f01d01
+    def do_task(self, options=None, label=False, set_uuid=False, nodiscard=False):
f01d01
         """Create the format on the device and label if possible and desired.
f01d01
 
f01d01
            :param options: any special options, may be None
f01d01
@@ -168,7 +201,7 @@ def do_task(self, options=None, label=False, set_uuid=False):
f01d01
             raise FSError("\n".join(error_msgs))
f01d01
 
f01d01
         options = options or []
f01d01
-        cmd = self._mkfs_command(options, label, set_uuid)
f01d01
+        cmd = self._mkfs_command(options, label, set_uuid, nodiscard)
f01d01
         try:
f01d01
             ret = util.run_program(cmd)
f01d01
         except OSError as e:
f01d01
@@ -181,6 +214,7 @@ def do_task(self, options=None, label=False, set_uuid=False):
f01d01
 class BTRFSMkfs(FSMkfs):
f01d01
     ext = availability.MKFS_BTRFS_APP
f01d01
     label_option = None
f01d01
+    nodiscard_option = ["--nodiscard"]
f01d01
 
f01d01
     def get_uuid_args(self, uuid):
f01d01
         return ["-U", uuid]
f01d01
@@ -193,6 +227,7 @@ def args(self):
f01d01
 class Ext2FSMkfs(FSMkfs):
f01d01
     ext = availability.MKE2FS_APP
f01d01
     label_option = "-L"
f01d01
+    nodiscard_option = ["-E", "nodiscard"]
f01d01
 
f01d01
     _opts = []
f01d01
 
f01d01
@@ -215,6 +250,7 @@ class Ext4FSMkfs(Ext3FSMkfs):
f01d01
 class FATFSMkfs(FSMkfs):
f01d01
     ext = availability.MKDOSFS_APP
f01d01
     label_option = "-n"
f01d01
+    nodiscard_option = None
f01d01
 
f01d01
     def get_uuid_args(self, uuid):
f01d01
         return ["-i", uuid.replace('-', '')]
f01d01
@@ -227,6 +263,7 @@ def args(self):
f01d01
 class GFS2Mkfs(FSMkfs):
f01d01
     ext = availability.MKFS_GFS2_APP
f01d01
     label_option = None
f01d01
+    nodiscard_option = None
f01d01
     get_uuid_args = None
f01d01
 
f01d01
     @property
f01d01
@@ -237,6 +274,7 @@ def args(self):
f01d01
 class HFSMkfs(FSMkfs):
f01d01
     ext = availability.HFORMAT_APP
f01d01
     label_option = "-l"
f01d01
+    nodiscard_option = None
f01d01
     get_uuid_args = None
f01d01
 
f01d01
     @property
f01d01
@@ -247,6 +285,7 @@ def args(self):
f01d01
 class HFSPlusMkfs(FSMkfs):
f01d01
     ext = availability.MKFS_HFSPLUS_APP
f01d01
     label_option = "-v"
f01d01
+    nodiscard_option = None
f01d01
     get_uuid_args = None
f01d01
 
f01d01
     @property
f01d01
@@ -257,6 +296,7 @@ def args(self):
f01d01
 class JFSMkfs(FSMkfs):
f01d01
     ext = availability.MKFS_JFS_APP
f01d01
     label_option = "-L"
f01d01
+    nodiscard_option = None
f01d01
     get_uuid_args = None
f01d01
 
f01d01
     @property
f01d01
@@ -267,6 +307,7 @@ def args(self):
f01d01
 class NTFSMkfs(FSMkfs):
f01d01
     ext = availability.MKNTFS_APP
f01d01
     label_option = "-L"
f01d01
+    nodiscard_option = None
f01d01
     get_uuid_args = None
f01d01
 
f01d01
     @property
f01d01
@@ -277,6 +318,7 @@ def args(self):
f01d01
 class ReiserFSMkfs(FSMkfs):
f01d01
     ext = availability.MKREISERFS_APP
f01d01
     label_option = "-l"
f01d01
+    nodiscard_option = None
f01d01
 
f01d01
     def get_uuid_args(self, uuid):
f01d01
         return ["-u", uuid]
f01d01
@@ -289,6 +331,7 @@ def args(self):
f01d01
 class XFSMkfs(FSMkfs):
f01d01
     ext = availability.MKFS_XFS_APP
f01d01
     label_option = "-L"
f01d01
+    nodiscard_option = ["-K"]
f01d01
 
f01d01
     def get_uuid_args(self, uuid):
f01d01
         return ["-m", "uuid=" + uuid]
f01d01
@@ -307,3 +350,7 @@ def can_label(self):
f01d01
     @property
f01d01
     def can_set_uuid(self):
f01d01
         return False
f01d01
+
f01d01
+    @property
f01d01
+    def can_nodiscard(self):
f01d01
+        return False
f01d01
diff --git a/tests/formats_test/methods_test.py b/tests/formats_test/methods_test.py
f01d01
index 710fa1c5..b2674ea7 100644
f01d01
--- a/tests/formats_test/methods_test.py
f01d01
+++ b/tests/formats_test/methods_test.py
f01d01
@@ -307,7 +307,8 @@ def _test_create_backend(self):
f01d01
             self.format._mkfs.do_task.assert_called_with(
f01d01
                 options=None,
f01d01
                 label=not self.format.relabels(),
f01d01
-                set_uuid=self.format.can_set_uuid()
f01d01
+                set_uuid=self.format.can_set_uuid(),
f01d01
+                nodiscard=self.format.can_nodiscard()
f01d01
             )
f01d01
 
f01d01
     def _test_setup_backend(self):
f01d01
f01d01
From ac04f74fa9bc8ded3facd302ca74ec033009a0bd Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Thu, 19 Nov 2020 13:19:21 +0100
f01d01
Subject: [PATCH 11/17] Add nodiscard option by default when creating VDO
f01d01
 logical volumes
f01d01
f01d01
User can override this by passing "nodiscard=False" to the LV
f01d01
constructor, but we want nodiscard by default.
f01d01
---
f01d01
 blivet/blivet.py            | 8 +++++++-
f01d01
 blivet/devicefactory.py     | 6 ++++++
f01d01
 tests/devicefactory_test.py | 7 +++++++
f01d01
 3 files changed, 20 insertions(+), 1 deletion(-)
f01d01
f01d01
diff --git a/blivet/blivet.py b/blivet/blivet.py
f01d01
index 754eb152..e4115691 100644
f01d01
--- a/blivet/blivet.py
f01d01
+++ b/blivet/blivet.py
f01d01
@@ -613,9 +613,15 @@ def new_lv(self, *args, **kwargs):
f01d01
 
f01d01
         mountpoint = kwargs.pop("mountpoint", None)
f01d01
         if 'fmt_type' in kwargs:
f01d01
+            fmt_args = kwargs.pop("fmt_args", {})
f01d01
+            if vdo_lv and "nodiscard" not in fmt_args.keys():
f01d01
+                # we don't want to run discard on VDO LV during mkfs so if user don't
f01d01
+                # tell us not to do it, we should add the nodiscard option to mkfs
f01d01
+                fmt_args["nodiscard"] = True
f01d01
+
f01d01
             kwargs["fmt"] = get_format(kwargs.pop("fmt_type"),
f01d01
                                        mountpoint=mountpoint,
f01d01
-                                       **kwargs.pop("fmt_args", {}))
f01d01
+                                       **fmt_args)
f01d01
 
f01d01
         name = kwargs.pop("name", None)
f01d01
         if name:
f01d01
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
f01d01
index c95037cc..085f2fd6 100644
f01d01
--- a/blivet/devicefactory.py
f01d01
+++ b/blivet/devicefactory.py
f01d01
@@ -1811,6 +1811,12 @@ def _reconfigure_device(self):
f01d01
         self.device.pool.compression = self.compression
f01d01
         self.device.pool.deduplication = self.deduplication
f01d01
 
f01d01
+    def _set_format(self):
f01d01
+        super(LVMVDOFactory, self)._set_format()
f01d01
+
f01d01
+        # preserve nodiscard mkfs option after changing filesystem
f01d01
+        self.device.format._mkfs_nodiscard = True
f01d01
+
f01d01
     #
f01d01
     # methods to configure the factory's device
f01d01
     #
f01d01
diff --git a/tests/devicefactory_test.py b/tests/devicefactory_test.py
f01d01
index 7cdb51c5..4de1e05b 100644
f01d01
--- a/tests/devicefactory_test.py
f01d01
+++ b/tests/devicefactory_test.py
f01d01
@@ -571,6 +571,10 @@ def _validate_factory_device(self, *args, **kwargs):
f01d01
         if pool_name:
f01d01
             self.assertEqual(vdolv.pool.lvname, pool_name)
f01d01
 
f01d01
+        # nodiscard should be always set for VDO LV format
f01d01
+        if vdolv.format.type:
f01d01
+            self.assertTrue(vdolv.format._mkfs_nodiscard)
f01d01
+
f01d01
         return device
f01d01
 
f01d01
     @patch("blivet.formats.lvmpv.LVMPhysicalVolume.formattable", return_value=True)
f01d01
@@ -633,6 +637,9 @@ def test_device_factory(self, *args):  # pylint: disable=unused-argument,argumen
f01d01
         device = self._factory_device(device_type, **kwargs)
f01d01
         self._validate_factory_device(device, device_type, **kwargs)
f01d01
 
f01d01
+        # change fstype
f01d01
+        kwargs["fstype"] = "xfs"
f01d01
+
f01d01
 
f01d01
 class MDFactoryTestCase(DeviceFactoryTestCase):
f01d01
     device_type = devicefactory.DEVICE_TYPE_MD
f01d01
f01d01
From 43f25ce84729c321d1ff2bbba2f50489f6d736b4 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Thu, 19 Nov 2020 13:31:40 +0100
f01d01
Subject: [PATCH 12/17] Add LVM VDO example
f01d01
f01d01
---
f01d01
 examples/lvm_vdo.py | 61 +++++++++++++++++++++++++++++++++++++++++++++
f01d01
 1 file changed, 61 insertions(+)
f01d01
 create mode 100644 examples/lvm_vdo.py
f01d01
f01d01
diff --git a/examples/lvm_vdo.py b/examples/lvm_vdo.py
f01d01
new file mode 100644
f01d01
index 00000000..ad081642
f01d01
--- /dev/null
f01d01
+++ b/examples/lvm_vdo.py
f01d01
@@ -0,0 +1,61 @@
f01d01
+import os
f01d01
+
f01d01
+import blivet
f01d01
+from blivet.size import Size
f01d01
+from blivet.util import set_up_logging, create_sparse_tempfile
f01d01
+
f01d01
+set_up_logging()
f01d01
+b = blivet.Blivet()   # create an instance of Blivet (don't add system devices)
f01d01
+
f01d01
+# create a disk image file on which to create new devices
f01d01
+disk1_file = create_sparse_tempfile("disk1", Size("100GiB"))
f01d01
+b.disk_images["disk1"] = disk1_file
f01d01
+disk2_file = create_sparse_tempfile("disk2", Size("100GiB"))
f01d01
+b.disk_images["disk2"] = disk2_file
f01d01
+
f01d01
+b.reset()
f01d01
+
f01d01
+try:
f01d01
+    disk1 = b.devicetree.get_device_by_name("disk1")
f01d01
+    disk2 = b.devicetree.get_device_by_name("disk2")
f01d01
+
f01d01
+    b.initialize_disk(disk1)
f01d01
+    b.initialize_disk(disk2)
f01d01
+
f01d01
+    pv = b.new_partition(size=Size("50GiB"), fmt_type="lvmpv", parents=[disk1])
f01d01
+    b.create_device(pv)
f01d01
+    pv2 = b.new_partition(size=Size("50GiB"), fmt_type="lvmpv", parents=[disk2])
f01d01
+    b.create_device(pv2)
f01d01
+
f01d01
+    # allocate the partitions (decide where and on which disks they'll reside)
f01d01
+    blivet.partitioning.do_partitioning(b)
f01d01
+
f01d01
+    vg = b.new_vg(parents=[pv, pv2])
f01d01
+    b.create_device(vg)
f01d01
+
f01d01
+    # create 80 GiB VDO pool
f01d01
+    # there can be only one VDO LV on the pool and these are created together
f01d01
+    # with one LVM call, we have 2 separate devices because there are two block
f01d01
+    # devices in the end and it allows to control the different "physical" size of
f01d01
+    # the pool and "logical" size of the VDO LV (which is usually bigger, accounting
f01d01
+    # for the saved space with deduplication and/or compression)
f01d01
+    pool = b.new_lv(size=Size("80GiB"), parents=[vg], name="vdopool", vdo_pool=True,
f01d01
+                    deduplication=True, compression=True)
f01d01
+    b.create_device(pool)
f01d01
+
f01d01
+    # create the VDO LV with 400 GiB "virtual size" and ext4 filesystem on the VDO
f01d01
+    # pool
f01d01
+    lv = b.new_lv(size=Size("400GiB"), parents=[pool], name="vdolv", vdo_lv=True,
f01d01
+                  fmt_type="ext4")
f01d01
+    b.create_device(lv)
f01d01
+
f01d01
+    print(b.devicetree)
f01d01
+
f01d01
+    # write the new partitions to disk and format them as specified
f01d01
+    b.do_it()
f01d01
+    print(b.devicetree)
f01d01
+    input("Check the state and hit ENTER to trigger cleanup")
f01d01
+finally:
f01d01
+    b.devicetree.teardown_disk_images()
f01d01
+    os.unlink(disk1_file)
f01d01
+    os.unlink(disk2_file)
f01d01
f01d01
From c487a1e6023b54f5beea8d99ba2f5da5d80590ee Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Wed, 25 Nov 2020 13:30:15 +0100
f01d01
Subject: [PATCH 13/17] Add LVM VDO documentation
f01d01
f01d01
---
f01d01
 doc/lvmvdo.rst | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++
f01d01
 1 file changed, 86 insertions(+)
f01d01
 create mode 100644 doc/lvmvdo.rst
f01d01
f01d01
diff --git a/doc/lvmvdo.rst b/doc/lvmvdo.rst
f01d01
new file mode 100644
f01d01
index 00000000..3965abd3
f01d01
--- /dev/null
f01d01
+++ b/doc/lvmvdo.rst
f01d01
@@ -0,0 +1,86 @@
f01d01
+LVM VDO support
f01d01
+===============
f01d01
+
f01d01
+Support for creating LVM VDO devices has been added in Blivet 3.4.
f01d01
+
f01d01
+These devices are similar to LVM thinly provisioned volumes, but there are some special steps
f01d01
+and limitations when creating these devices which this document describes.
f01d01
+
f01d01
+LVM VDO in Blivet
f01d01
+-----------------
f01d01
+
f01d01
+LVM VDO devices are represented by two ``LVMLogicalVolumeDevice`` devices:
f01d01
+
f01d01
+- VDO Pool logical volume with type 'lvmvdopool'
f01d01
+- VDO logical volume with type 'lvmvdolv' which is the child of the VDO Pool device
f01d01
+
f01d01
+Existing LVM VDO setup in Blivet:
f01d01
+
f01d01
+    existing 20 GiB disk vdb (265) with existing msdos disklabel
f01d01
+      existing 20 GiB partition vdb1 (275) with existing lvmpv
f01d01
+        existing 20 GiB lvmvg data (284)
f01d01
+          existing 10 GiB lvmvdopool data-vdopool (288)
f01d01
+            existing 50 GiB lvmvdolv data-vdolv (295)
f01d01
+
f01d01
+When creating LVM VDO setup using Blivet these two devices must be created together as these
f01d01
+are created by a single LVM command.
f01d01
+
f01d01
+It currently isn't possible to create additional VDO logical volumes in the pool. It is however
f01d01
+possible to create multiple VDO pools in a single volume group.
f01d01
+
f01d01
+Deduplication and compression are properties of the VDO pool. Size specified for the VDO pool
f01d01
+volume will be used as the "physical" size for the pool and size specified for the VDO logical volume
f01d01
+will be used as the "virtual" size for the VDO volume.
f01d01
+
f01d01
+When creating format, it must be created on the VDO logical volume. For filesystems with discard
f01d01
+support, no discard option will be automatically added when calling the ``mkfs`` command
f01d01
+(e.g. ``-K`` for ``mkfs.xfs``).
f01d01
+
f01d01
+Example for creating a *80 GiB* VDO pool with *400 GiB* VDO logical volume with an *ext4* format with
f01d01
+both deduplication and compression enabled:
f01d01
+
f01d01
+    pool = b.new_lv(size=Size("80GiB"), parents=[vg], name="vdopool", vdo_pool=True,
f01d01
+                    deduplication=True, compression=True)
f01d01
+    b.create_device(pool)
f01d01
+
f01d01
+    lv = b.new_lv(size=Size("400GiB"), parents=[pool], name="vdolv", vdo_lv=True,
f01d01
+                  fmt_type="ext4")
f01d01
+    b.create_device(lv)
f01d01
+
f01d01
+When removing existing LVM VDO devices, both devices must be removed from the devicetree and the VDO
f01d01
+logical volume must be removed first (``recursive_remove`` can be used to automate these two steps).
f01d01
+
f01d01
+Managing of existing LVM VDO devices is currently not supported.
f01d01
+
f01d01
+
f01d01
+LVM VDO in Devicefactory
f01d01
+------------------------
f01d01
+
f01d01
+For the top-down specified creation using device factories a new ``LVMVDOFactory`` factory has been
f01d01
+added. Factory device in this case is the VDO logical volume and is again automatically created
f01d01
+together with the VDO pool.
f01d01
+
f01d01
+Example of creating a new LVM VDO setup using the ``devicefactory`` module:
f01d01
+
f01d01
+    factory = blivet.devicefactory.LVMVDOFactory(b, size=Size("5 GiB"), virtual_size=Size("50 GiB"),
f01d01
+                                                 disks=disks, fstype="xfs",
f01d01
+                                                 container_name="data",
f01d01
+                                                 pool_name="myvdopool",
f01d01
+                                                 compression=True, deduplication=True)
f01d01
+    factory.configure()
f01d01
+    factory.device
f01d01
+
f01d01
+        LVMLogicalVolumeDevice instance (0x7f14d17422b0) --
f01d01
+            name = data-00  status = False  id = 528
f01d01
+            children = []
f01d01
+            parents = ['non-existent 5 GiB lvmvdopool data-myvdopool (519)']
f01d01
+            ...
f01d01
+
f01d01
+``size`` in this case sets the pool (physical) size, the VDO logical volume size can be specified
f01d01
+with ``virtual_size`` (if not specified it will be same as the pool size). Name for the VDO volume
f01d01
+can be specified using the ``name`` keyword argument. ``pool_name`` argument is optional and
f01d01
+a unique name will be generated if omitted. Both ``compression`` and ``deduplication`` default to
f01d01
+``True`` (enabled) if not specified.
f01d01
+
f01d01
+This factory can create only a single VDO logical volume in a single VDO pool but additional VDO pools
f01d01
+can be added by repeating the steps to create the first one.
f01d01
f01d01
From c6c776cf137b5c6ae454487df469e9a6dba8a5d1 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Wed, 9 Dec 2020 14:06:27 +0100
f01d01
Subject: [PATCH 14/17] Set minimum size for LVM VDO pool devices
f01d01
f01d01
---
f01d01
 blivet/devicefactory.py        |  3 +++
f01d01
 blivet/devices/lvm.py          | 26 ++++++++++++++++++++++++++
f01d01
 tests/devicefactory_test.py    | 29 ++++++++++++++++++++---------
f01d01
 tests/devices_test/lvm_test.py |  6 ++++++
f01d01
 4 files changed, 55 insertions(+), 9 deletions(-)
f01d01
f01d01
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
f01d01
index 085f2fd6..5e47eb9a 100644
f01d01
--- a/blivet/devicefactory.py
f01d01
+++ b/blivet/devicefactory.py
f01d01
@@ -277,6 +277,7 @@ class DeviceFactory(object):
f01d01
                          "container_size": SIZE_POLICY_AUTO,
f01d01
                          "container_raid_level": None,
f01d01
                          "container_encrypted": None}
f01d01
+    _device_min_size = Size(0)  # no limit by default, limited only by filesystem size
f01d01
 
f01d01
     def __init__(self, storage, **kwargs):
f01d01
         """
f01d01
@@ -1760,6 +1761,8 @@ class LVMVDOFactory(LVMFactory):
f01d01
         :type deduplication: bool
f01d01
     """
f01d01
 
f01d01
+    _device_min_size = LVMVDOPoolMixin._min_size
f01d01
+
f01d01
     def __init__(self, storage, **kwargs):
f01d01
         self.pool_name = kwargs.pop("pool_name", None)
f01d01
         self.virtual_size = kwargs.pop("virtual_size", None)
f01d01
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
f01d01
index 0802e2de..785fa2d2 100644
f01d01
--- a/blivet/devices/lvm.py
f01d01
+++ b/blivet/devices/lvm.py
f01d01
@@ -1792,6 +1792,7 @@ def populate_ksdata(self, data):
f01d01
 class LVMVDOPoolMixin(object):
f01d01
 
f01d01
     _external_dependencies = [availability.BLOCKDEV_LVM_PLUGIN, availability.BLOCKDEV_LVM_PLUGIN_VDO]
f01d01
+    _min_size = Size("5 GiB")  # 2.5 GiB for index and one 2 GiB slab rounded up to 5 GiB
f01d01
 
f01d01
     def __init__(self, compression=True, deduplication=True, index_memory=0, write_policy=None):
f01d01
         self.compression = compression
f01d01
@@ -1800,6 +1801,9 @@ def __init__(self, compression=True, deduplication=True, index_memory=0, write_p
f01d01
         self.write_policy = write_policy
f01d01
         self._lvs = []
f01d01
 
f01d01
+        if not self.exists and self.size < self.min_size:
f01d01
+            raise ValueError("Requested size %s is smaller than minimum %s" % (self.size, self.min_size))
f01d01
+
f01d01
     @property
f01d01
     def is_vdo_pool(self):
f01d01
         return self.seg_type == "vdo-pool"
f01d01
@@ -1856,6 +1860,23 @@ def direct(self):
f01d01
         """ Is this device directly accessible? """
f01d01
         return False
f01d01
 
f01d01
+    @property
f01d01
+    @util.requires_property("is_vdo_pool")
f01d01
+    def min_size(self):
f01d01
+        if self.exists:
f01d01
+            return self.current_size
f01d01
+
f01d01
+        return self._min_size
f01d01
+
f01d01
+    def _set_size(self, newsize):
f01d01
+        if not isinstance(newsize, Size):
f01d01
+            raise AttributeError("new size must of type Size")
f01d01
+
f01d01
+        if newsize < self.min_size:
f01d01
+            raise ValueError("Requested size %s is smaller than minimum %s" % (newsize, self.min_size))
f01d01
+
f01d01
+        DMDevice._set_size(self, newsize)
f01d01
+
f01d01
     def read_current_size(self):
f01d01
         log_method_call(self, exists=self.exists, path=self.path,
f01d01
                         sysfs_path=self.sysfs_path)
f01d01
@@ -2229,6 +2250,11 @@ def max_size(self):
f01d01
         max_format = self.format.max_size
f01d01
         return min(max_lv, max_format) if max_format else max_lv
f01d01
 
f01d01
+    @property
f01d01
+    @type_specific
f01d01
+    def min_size(self):
f01d01
+        return super(LVMLogicalVolumeDevice, self).min_size
f01d01
+
f01d01
     @property
f01d01
     @type_specific
f01d01
     def vg_space_used(self):
f01d01
diff --git a/tests/devicefactory_test.py b/tests/devicefactory_test.py
f01d01
index 4de1e05b..a1334cda 100644
f01d01
--- a/tests/devicefactory_test.py
f01d01
+++ b/tests/devicefactory_test.py
f01d01
@@ -49,13 +49,18 @@ class DeviceFactoryTestCase(unittest.TestCase):
f01d01
     encryption_supported = True
f01d01
     """ whether encryption of this device type is supported by blivet """
f01d01
 
f01d01
+    factory_class = None
f01d01
+    """ devicefactory class used in this test case """
f01d01
+
f01d01
+    _disk_size = Size("2 GiB")
f01d01
+
f01d01
     def setUp(self):
f01d01
         if self.device_type is None:
f01d01
             raise unittest.SkipTest("abstract base class")
f01d01
 
f01d01
         self.b = blivet.Blivet()  # don't populate it
f01d01
-        self.disk_files = [create_sparse_tempfile("factorytest", Size("2 GiB")),
f01d01
-                           create_sparse_tempfile("factorytest", Size("2 GiB"))]
f01d01
+        self.disk_files = [create_sparse_tempfile("factorytest", self._disk_size),
f01d01
+                           create_sparse_tempfile("factorytest", self._disk_size)]
f01d01
         for filename in self.disk_files:
f01d01
             disk = DiskFile(filename)
f01d01
             self.b.devicetree._add_device(disk)
f01d01
@@ -197,7 +202,7 @@ def _get_size_delta(self, devices=None):
f01d01
     def test_get_free_disk_space(self, *args):  # pylint: disable=unused-argument
f01d01
         # get_free_disk_space should return the total free space on disks
f01d01
         kwargs = self._get_test_factory_args()
f01d01
-        kwargs["size"] = Size("500 MiB")
f01d01
+        kwargs["size"] = max(Size("500 MiB"), self.factory_class._device_min_size)
f01d01
         factory = devicefactory.get_device_factory(self.b,
f01d01
                                                    self.device_type,
f01d01
                                                    disks=self.b.disks,
f01d01
@@ -285,7 +290,7 @@ def test_factory_defaults(self, *args):  # pylint: disable=unused-argument
f01d01
         kwargs = self._get_test_factory_args()
f01d01
         kwargs.update({"disks": self.b.disks[:],
f01d01
                        "fstype": "swap",
f01d01
-                       "size": Size("2GiB"),
f01d01
+                       "size": max(Size("2GiB"), self.factory_class._device_min_size),
f01d01
                        "label": "SWAP"})
f01d01
         device = self._factory_device(self.device_type, **kwargs)
f01d01
         factory = devicefactory.get_device_factory(self.b, self.device_type,
f01d01
@@ -302,6 +307,7 @@ def test_factory_defaults(self, *args):  # pylint: disable=unused-argument
f01d01
 class PartitionFactoryTestCase(DeviceFactoryTestCase):
f01d01
     device_class = PartitionDevice
f01d01
     device_type = devicefactory.DEVICE_TYPE_PARTITION
f01d01
+    factory_class = devicefactory.PartitionFactory
f01d01
 
f01d01
     def test_bug1178884(self):
f01d01
         # Test a change of format and size where old size is too large for the
f01d01
@@ -330,6 +336,7 @@ def _get_size_delta(self, devices=None):
f01d01
 class LVMFactoryTestCase(DeviceFactoryTestCase):
f01d01
     device_class = LVMLogicalVolumeDevice
f01d01
     device_type = devicefactory.DEVICE_TYPE_LVM
f01d01
+    factory_class = devicefactory.LVMFactory
f01d01
 
f01d01
     def _validate_factory_device(self, *args, **kwargs):
f01d01
         super(LVMFactoryTestCase, self)._validate_factory_device(*args, **kwargs)
f01d01
@@ -510,6 +517,7 @@ class LVMThinPFactoryTestCase(LVMFactoryTestCase):
f01d01
     device_class = LVMLogicalVolumeDevice
f01d01
     device_type = devicefactory.DEVICE_TYPE_LVM_THINP
f01d01
     encryption_supported = False
f01d01
+    factory_class = devicefactory.LVMThinPFactory
f01d01
 
f01d01
     def _validate_factory_device(self, *args, **kwargs):
f01d01
         super(LVMThinPFactoryTestCase, self)._validate_factory_device(*args,
f01d01
@@ -541,6 +549,8 @@ class LVMVDOFactoryTestCase(LVMFactoryTestCase):
f01d01
     device_class = LVMLogicalVolumeDevice
f01d01
     device_type = devicefactory.DEVICE_TYPE_LVM_VDO
f01d01
     encryption_supported = False
f01d01
+    _disk_size = Size("10 GiB")  # we need bigger disks for VDO
f01d01
+    factory_class = devicefactory.LVMVDOFactory
f01d01
 
f01d01
     def _validate_factory_device(self, *args, **kwargs):
f01d01
         super(LVMVDOFactoryTestCase, self)._validate_factory_device(*args,
f01d01
@@ -585,7 +595,7 @@ def _validate_factory_device(self, *args, **kwargs):
f01d01
     def test_device_factory(self, *args):  # pylint: disable=unused-argument,arguments-differ
f01d01
         device_type = self.device_type
f01d01
         kwargs = {"disks": self.b.disks,
f01d01
-                  "size": Size("400 MiB"),
f01d01
+                  "size": Size("6 GiB"),
f01d01
                   "fstype": 'ext4',
f01d01
                   "mountpoint": '/factorytest'}
f01d01
         device = self._factory_device(device_type, **kwargs)
f01d01
@@ -593,7 +603,7 @@ def test_device_factory(self, *args):  # pylint: disable=unused-argument,argumen
f01d01
         self.b.recursive_remove(device.pool)
f01d01
 
f01d01
         kwargs = {"disks": self.b.disks,
f01d01
-                  "size": Size("400 MiB"),
f01d01
+                  "size": Size("6 GiB"),
f01d01
                   "fstype": 'ext4',
f01d01
                   "mountpoint": '/factorytest',
f01d01
                   "pool_name": "vdopool",
f01d01
@@ -603,19 +613,19 @@ def test_device_factory(self, *args):  # pylint: disable=unused-argument,argumen
f01d01
         self._validate_factory_device(device, device_type, **kwargs)
f01d01
 
f01d01
         # change size without specifying virtual_size: both sizes should grow
f01d01
-        kwargs["size"] = Size("600 MiB")
f01d01
+        kwargs["size"] = Size("8 GiB")
f01d01
         kwargs["device"] = device
f01d01
         device = self._factory_device(device_type, **kwargs)
f01d01
         self._validate_factory_device(device, device_type, **kwargs)
f01d01
 
f01d01
         # change virtual size
f01d01
-        kwargs["virtual_size"] = Size("6 GiB")
f01d01
+        kwargs["virtual_size"] = Size("40 GiB")
f01d01
         kwargs["device"] = device
f01d01
         device = self._factory_device(device_type, **kwargs)
f01d01
         self._validate_factory_device(device, device_type, **kwargs)
f01d01
 
f01d01
         # change virtual size to smaller than size
f01d01
-        kwargs["virtual_size"] = Size("500 GiB")
f01d01
+        kwargs["virtual_size"] = Size("10 GiB")
f01d01
         kwargs["device"] = device
f01d01
         device = self._factory_device(device_type, **kwargs)
f01d01
         self._validate_factory_device(device, device_type, **kwargs)
f01d01
@@ -644,6 +654,7 @@ def test_device_factory(self, *args):  # pylint: disable=unused-argument,argumen
f01d01
 class MDFactoryTestCase(DeviceFactoryTestCase):
f01d01
     device_type = devicefactory.DEVICE_TYPE_MD
f01d01
     device_class = MDRaidArrayDevice
f01d01
+    factory_class = devicefactory.MDFactory
f01d01
 
f01d01
     def test_device_factory(self):
f01d01
         # RAID0 across two disks
f01d01
diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py
f01d01
index 493d3ba1..78b140ba 100644
f01d01
--- a/tests/devices_test/lvm_test.py
f01d01
+++ b/tests/devices_test/lvm_test.py
f01d01
@@ -705,6 +705,12 @@ def test_new_vdo_pool(self):
f01d01
 
f01d01
         self.assertEqual(vg.size, Size("10236 MiB"))
f01d01
 
f01d01
+        with self.assertRaises(ValueError):
f01d01
+            vdopool = b.new_lv(name="vdopool", vdo_pool=True,
f01d01
+                               parents=[vg], compression=True,
f01d01
+                               deduplication=True,
f01d01
+                               size=blivet.size.Size("1 GiB"))
f01d01
+
f01d01
         vdopool = b.new_lv(name="vdopool", vdo_pool=True,
f01d01
                            parents=[vg], compression=True,
f01d01
                            deduplication=True,
f01d01
f01d01
From 197f2877709e702c101ada6b9a055a88f09320c8 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Fri, 11 Dec 2020 14:20:48 +0100
f01d01
Subject: [PATCH 15/17] Use better description for libblockdev plugins in
f01d01
 tasks.availability
f01d01
f01d01
The old names were quite confusing when showing that "lvm" is
f01d01
missing when in fact libblockdev LVM plugin is missing. Also with
f01d01
LVM VDO we need to be able to tell the difference between missing
f01d01
LVM plugin and missing LVM VDO support.
f01d01
---
f01d01
 blivet/tasks/availability.py | 26 +++++++++++++-------------
f01d01
 1 file changed, 13 insertions(+), 13 deletions(-)
f01d01
f01d01
diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py
f01d01
index b107428e..52418685 100644
f01d01
--- a/blivet/tasks/availability.py
f01d01
+++ b/blivet/tasks/availability.py
f01d01
@@ -236,13 +236,13 @@ def availability_errors(self, resource):
f01d01
             :returns: [] if the name of the plugin is loaded
f01d01
             :rtype: list of str
f01d01
         """
f01d01
-        if resource.name not in blockdev.get_available_plugin_names():  # pylint: disable=no-value-for-parameter
f01d01
-            return ["libblockdev plugin %s not loaded" % resource.name]
f01d01
+        if self._tech_info.plugin_name not in blockdev.get_available_plugin_names():  # pylint: disable=no-value-for-parameter
f01d01
+            return ["libblockdev plugin %s not loaded" % self._tech_info.plugin_name]
f01d01
         else:
f01d01
             tech_missing = self._check_technologies()
f01d01
             if tech_missing:
f01d01
                 return ["libblockdev plugin %s is loaded but some required "
f01d01
-                        "technologies are not available:\n%s" % (resource.name, tech_missing)]
f01d01
+                        "technologies are not available:\n%s" % (self._tech_info.plugin_name, tech_missing)]
f01d01
             else:
f01d01
                 return []
f01d01
 
f01d01
@@ -411,16 +411,16 @@ def available_resource(name):
f01d01
 # we can't just check if the plugin is loaded, we also need to make sure
f01d01
 # that all technologies required by us our supported (some may be missing
f01d01
 # due to missing dependencies)
f01d01
-BLOCKDEV_BTRFS_PLUGIN = blockdev_plugin("btrfs", BLOCKDEV_BTRFS_TECH)
f01d01
-BLOCKDEV_CRYPTO_PLUGIN = blockdev_plugin("crypto", BLOCKDEV_CRYPTO_TECH)
f01d01
-BLOCKDEV_DM_PLUGIN = blockdev_plugin("dm", BLOCKDEV_DM_TECH)
f01d01
-BLOCKDEV_DM_PLUGIN_RAID = blockdev_plugin("dm", BLOCKDEV_DM_TECH_RAID)
f01d01
-BLOCKDEV_LOOP_PLUGIN = blockdev_plugin("loop", BLOCKDEV_LOOP_TECH)
f01d01
-BLOCKDEV_LVM_PLUGIN = blockdev_plugin("lvm", BLOCKDEV_LVM_TECH)
f01d01
-BLOCKDEV_LVM_PLUGIN_VDO = blockdev_plugin("lvm", BLOCKDEV_LVM_TECH_VDO)
f01d01
-BLOCKDEV_MDRAID_PLUGIN = blockdev_plugin("mdraid", BLOCKDEV_MD_TECH)
f01d01
-BLOCKDEV_MPATH_PLUGIN = blockdev_plugin("mpath", BLOCKDEV_MPATH_TECH)
f01d01
-BLOCKDEV_SWAP_PLUGIN = blockdev_plugin("swap", BLOCKDEV_SWAP_TECH)
f01d01
+BLOCKDEV_BTRFS_PLUGIN = blockdev_plugin("libblockdev btrfs plugin", BLOCKDEV_BTRFS_TECH)
f01d01
+BLOCKDEV_CRYPTO_PLUGIN = blockdev_plugin("libblockdev crypto plugin", BLOCKDEV_CRYPTO_TECH)
f01d01
+BLOCKDEV_DM_PLUGIN = blockdev_plugin("libblockdev dm plugin", BLOCKDEV_DM_TECH)
f01d01
+BLOCKDEV_DM_PLUGIN_RAID = blockdev_plugin("libblockdev dm plugin (raid technology)", BLOCKDEV_DM_TECH_RAID)
f01d01
+BLOCKDEV_LOOP_PLUGIN = blockdev_plugin("libblockdev loop plugin", BLOCKDEV_LOOP_TECH)
f01d01
+BLOCKDEV_LVM_PLUGIN = blockdev_plugin("libblockdev lvm plugin", BLOCKDEV_LVM_TECH)
f01d01
+BLOCKDEV_LVM_PLUGIN_VDO = blockdev_plugin("libblockdev lvm plugin (vdo technology)", BLOCKDEV_LVM_TECH_VDO)
f01d01
+BLOCKDEV_MDRAID_PLUGIN = blockdev_plugin("libblockdev mdraid plugin", BLOCKDEV_MD_TECH)
f01d01
+BLOCKDEV_MPATH_PLUGIN = blockdev_plugin("libblockdev mpath plugin", BLOCKDEV_MPATH_TECH)
f01d01
+BLOCKDEV_SWAP_PLUGIN = blockdev_plugin("libblockdev swap plugin", BLOCKDEV_SWAP_TECH)
f01d01
 
f01d01
 # applications with versions
f01d01
 # we need e2fsprogs newer than 1.41 and we are checking the version by running
f01d01
f01d01
From 5fc047b48b0de18fa249f102d2a7163ac2d6e6a6 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Fri, 11 Dec 2020 14:24:18 +0100
f01d01
Subject: [PATCH 16/17] Fix external dependencies for LVM VDO devices
f01d01
f01d01
The external and unavailable dependencies code is mostly supposed
f01d01
to work with just class objects and not instances, which is problem
f01d01
for LVM devices where the LVMLogicalVolumeDevice can't depend on
f01d01
LVM VDO and special LVM VDO device mixin classes don't inherit
f01d01
from the Device class so they are missing some availability
f01d01
functions.
f01d01
This fix adds the neccessary functions to LVM VDO mixin classes to
f01d01
make sure both "unavailable_type_dependencies" and
f01d01
"type_external_dependencies" work with LVMVDOLogicalVolumeMixin
f01d01
and LVMVDOPoolMixin. When working with an LVMLogicalVolumeDevice
f01d01
instance its dependencies are correctly set based on type of the
f01d01
logical volume.
f01d01
---
f01d01
 blivet/devicefactory.py        |   7 +--
f01d01
 blivet/devices/lvm.py          |  31 ++++++++++
f01d01
 tests/action_test.py           |   7 +++
f01d01
 tests/devicefactory_test.py    |  32 ++++++++++
f01d01
 tests/devices_test/lvm_test.py | 106 +++++++++++++++++++++++++++++++++
f01d01
 5 files changed, 179 insertions(+), 4 deletions(-)
f01d01
f01d01
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
f01d01
index 5e47eb9a..b29a107a 100644
f01d01
--- a/blivet/devicefactory.py
f01d01
+++ b/blivet/devicefactory.py
f01d01
@@ -27,7 +27,7 @@
f01d01
 from .devices import BTRFSDevice, DiskDevice
f01d01
 from .devices import LUKSDevice, LVMLogicalVolumeDevice
f01d01
 from .devices import PartitionDevice, MDRaidArrayDevice
f01d01
-from .devices.lvm import LVMVDOPoolMixin, DEFAULT_THPOOL_RESERVE
f01d01
+from .devices.lvm import LVMVDOPoolMixin, LVMVDOLogicalVolumeMixin, DEFAULT_THPOOL_RESERVE
f01d01
 from .formats import get_format
f01d01
 from .devicelibs import btrfs
f01d01
 from .devicelibs import mdraid
f01d01
@@ -70,9 +70,6 @@ def is_supported_device_type(device_type):
f01d01
         :returns: True if this device type is supported
f01d01
         :rtype: bool
f01d01
     """
f01d01
-    if device_type == DEVICE_TYPE_LVM_VDO:
f01d01
-        return not any(e for e in LVMVDOPoolMixin._external_dependencies if not e.available)
f01d01
-
f01d01
     devices = []
f01d01
     if device_type == DEVICE_TYPE_BTRFS:
f01d01
         devices = [BTRFSDevice]
f01d01
@@ -84,6 +81,8 @@ def is_supported_device_type(device_type):
f01d01
         devices = [PartitionDevice]
f01d01
     elif device_type == DEVICE_TYPE_MD:
f01d01
         devices = [MDRaidArrayDevice]
f01d01
+    elif device_type == DEVICE_TYPE_LVM_VDO:
f01d01
+        devices = [LVMLogicalVolumeDevice, LVMVDOPoolMixin, LVMVDOLogicalVolumeMixin]
f01d01
 
f01d01
     return not any(c.unavailable_type_dependencies() for c in devices)
f01d01
 
f01d01
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
f01d01
index 785fa2d2..ac900bf3 100644
f01d01
--- a/blivet/devices/lvm.py
f01d01
+++ b/blivet/devices/lvm.py
f01d01
@@ -1804,6 +1804,17 @@ def __init__(self, compression=True, deduplication=True, index_memory=0, write_p
f01d01
         if not self.exists and self.size < self.min_size:
f01d01
             raise ValueError("Requested size %s is smaller than minimum %s" % (self.size, self.min_size))
f01d01
 
f01d01
+    # these two methods are defined in Device but LVMVDOPoolMixin doesn't inherit from
f01d01
+    # it and we can't have this code in LVMLogicalVolumeDevice because we need to be able
f01d01
+    # to get dependencies without creating instance of the class
f01d01
+    @classmethod
f01d01
+    def type_external_dependencies(cls):
f01d01
+        return set(d for d in cls._external_dependencies) | LVMLogicalVolumeDevice.type_external_dependencies()
f01d01
+
f01d01
+    @classmethod
f01d01
+    def unavailable_type_dependencies(cls):
f01d01
+        return set(e for e in cls.type_external_dependencies() if not e.available)
f01d01
+
f01d01
     @property
f01d01
     def is_vdo_pool(self):
f01d01
         return self.seg_type == "vdo-pool"
f01d01
@@ -1926,6 +1937,17 @@ def _check_parents(self):
f01d01
         if not container or not isinstance(container, LVMLogicalVolumeDevice) or not container.is_vdo_pool:
f01d01
             raise ValueError("constructor requires a vdo-pool LV")
f01d01
 
f01d01
+    # these two methods are defined in Device but LVMVDOLogicalVolumeMixin doesn't inherit
f01d01
+    # from it and we can't have this code in LVMLogicalVolumeDevice because we need to be
f01d01
+    # able to get dependencies without creating instance of the class
f01d01
+    @classmethod
f01d01
+    def type_external_dependencies(cls):
f01d01
+        return set(d for d in cls._external_dependencies) | LVMLogicalVolumeDevice.type_external_dependencies()
f01d01
+
f01d01
+    @classmethod
f01d01
+    def unavailable_type_dependencies(cls):
f01d01
+        return set(e for e in cls.type_external_dependencies() if not e.available)
f01d01
+
f01d01
     @property
f01d01
     def vg_space_used(self):
f01d01
         return Size(0)    # the pool's size is already accounted for in the vg
f01d01
@@ -2217,6 +2239,15 @@ def _convert_from_lvs(self):
f01d01
         """Convert the LVs to create this LV from into its internal LVs"""
f01d01
         raise ValueError("Cannot create a new LV of type '%s' from other LVs" % self.seg_type)
f01d01
 
f01d01
+    @property
f01d01
+    def external_dependencies(self):
f01d01
+        deps = super(LVMLogicalVolumeBase, self).external_dependencies
f01d01
+        if self.is_vdo_pool:
f01d01
+            deps.update(LVMVDOPoolMixin.type_external_dependencies())
f01d01
+        if self.is_vdo_lv:
f01d01
+            deps.update(LVMVDOLogicalVolumeMixin.type_external_dependencies())
f01d01
+        return deps
f01d01
+
f01d01
     @property
f01d01
     @type_specific
f01d01
     def vg(self):
f01d01
diff --git a/tests/action_test.py b/tests/action_test.py
f01d01
index 77176f46..38a2e872 100644
f01d01
--- a/tests/action_test.py
f01d01
+++ b/tests/action_test.py
f01d01
@@ -18,6 +18,8 @@
f01d01
 from blivet.devices import MDRaidArrayDevice
f01d01
 from blivet.devices import LVMVolumeGroupDevice
f01d01
 from blivet.devices import LVMLogicalVolumeDevice
f01d01
+from blivet.devices.lvm import LVMVDOPoolMixin
f01d01
+from blivet.devices.lvm import LVMVDOLogicalVolumeMixin
f01d01
 
f01d01
 # format classes
f01d01
 from blivet.formats.fs import Ext2FS
f01d01
@@ -1252,6 +1254,11 @@ def test_lv_from_lvs_actions(self):
f01d01
         self.assertEqual(set(self.storage.lvs), {pool})
f01d01
         self.assertEqual(set(pool._internal_lvs), {lv1, lv2})
f01d01
 
f01d01
+
f01d01
+@unittest.skipUnless(not any(x.unavailable_type_dependencies() for x in DEVICE_CLASSES + [LVMVDOPoolMixin, LVMVDOLogicalVolumeMixin]), "some unsupported device classes required for this test")
f01d01
+@unittest.skipUnless(all(x().utils_available for x in FORMAT_CLASSES), "some unsupported format classes required for this test")
f01d01
+class DeviceActionLVMVDOTestCase(DeviceActionTestCase):
f01d01
+
f01d01
     def test_lvm_vdo_destroy(self):
f01d01
         self.destroy_all_devices()
f01d01
         sdc = self.storage.devicetree.get_device_by_name("sdc")
f01d01
diff --git a/tests/devicefactory_test.py b/tests/devicefactory_test.py
f01d01
index a1334cda..e4210ead 100644
f01d01
--- a/tests/devicefactory_test.py
f01d01
+++ b/tests/devicefactory_test.py
f01d01
@@ -592,6 +592,8 @@ def _validate_factory_device(self, *args, **kwargs):
f01d01
     @patch("blivet.static_data.lvm_info.blockdev.lvm.lvs", return_value=[])
f01d01
     @patch("blivet.devices.lvm.LVMVolumeGroupDevice.type_external_dependencies", return_value=set())
f01d01
     @patch("blivet.devices.lvm.LVMLogicalVolumeBase.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMVDOPoolMixin.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMVDOLogicalVolumeMixin.type_external_dependencies", return_value=set())
f01d01
     def test_device_factory(self, *args):  # pylint: disable=unused-argument,arguments-differ
f01d01
         device_type = self.device_type
f01d01
         kwargs = {"disks": self.b.disks,
f01d01
@@ -650,6 +652,36 @@ def test_device_factory(self, *args):  # pylint: disable=unused-argument,argumen
f01d01
         # change fstype
f01d01
         kwargs["fstype"] = "xfs"
f01d01
 
f01d01
+    @patch("blivet.formats.lvmpv.LVMPhysicalVolume.formattable", return_value=True)
f01d01
+    @patch("blivet.formats.lvmpv.LVMPhysicalVolume.destroyable", return_value=True)
f01d01
+    @patch("blivet.static_data.lvm_info.blockdev.lvm.lvs", return_value=[])
f01d01
+    @patch("blivet.devices.lvm.LVMVolumeGroupDevice.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMLogicalVolumeBase.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMVDOPoolMixin.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMVDOLogicalVolumeMixin.type_external_dependencies", return_value=set())
f01d01
+    def test_factory_defaults(self, *args):  # pylint: disable=unused-argument
f01d01
+        super(LVMVDOFactoryTestCase, self).test_factory_defaults()
f01d01
+
f01d01
+    @patch("blivet.formats.lvmpv.LVMPhysicalVolume.formattable", return_value=True)
f01d01
+    @patch("blivet.formats.lvmpv.LVMPhysicalVolume.destroyable", return_value=True)
f01d01
+    @patch("blivet.static_data.lvm_info.blockdev.lvm.lvs", return_value=[])
f01d01
+    @patch("blivet.devices.lvm.LVMVolumeGroupDevice.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMLogicalVolumeBase.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMVDOPoolMixin.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMVDOLogicalVolumeMixin.type_external_dependencies", return_value=set())
f01d01
+    def test_get_free_disk_space(self, *args):
f01d01
+        super(LVMVDOFactoryTestCase, self).test_get_free_disk_space()
f01d01
+
f01d01
+    @patch("blivet.formats.lvmpv.LVMPhysicalVolume.formattable", return_value=True)
f01d01
+    @patch("blivet.formats.lvmpv.LVMPhysicalVolume.destroyable", return_value=True)
f01d01
+    @patch("blivet.static_data.lvm_info.blockdev.lvm.lvs", return_value=[])
f01d01
+    @patch("blivet.devices.lvm.LVMVolumeGroupDevice.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMLogicalVolumeBase.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMVDOPoolMixin.type_external_dependencies", return_value=set())
f01d01
+    @patch("blivet.devices.lvm.LVMVDOLogicalVolumeMixin.type_external_dependencies", return_value=set())
f01d01
+    def test_normalize_size(self, *args):  # pylint: disable=unused-argument
f01d01
+        super(LVMVDOFactoryTestCase, self).test_normalize_size()
f01d01
+
f01d01
 
f01d01
 class MDFactoryTestCase(DeviceFactoryTestCase):
f01d01
     device_type = devicefactory.DEVICE_TYPE_MD
f01d01
diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py
f01d01
index 78b140ba..d938144d 100644
f01d01
--- a/tests/devices_test/lvm_test.py
f01d01
+++ b/tests/devices_test/lvm_test.py
f01d01
@@ -10,10 +10,13 @@
f01d01
 from blivet.devices import StorageDevice
f01d01
 from blivet.devices import LVMLogicalVolumeDevice
f01d01
 from blivet.devices import LVMVolumeGroupDevice
f01d01
+from blivet.devices.lvm import LVMVDOPoolMixin
f01d01
+from blivet.devices.lvm import LVMVDOLogicalVolumeMixin
f01d01
 from blivet.devices.lvm import LVMCacheRequest
f01d01
 from blivet.devices.lvm import LVPVSpec, LVMInternalLVtype
f01d01
 from blivet.size import Size
f01d01
 from blivet.devicelibs import raid
f01d01
+from blivet import devicefactory
f01d01
 from blivet import errors
f01d01
 
f01d01
 DEVICE_CLASSES = [
f01d01
@@ -690,6 +693,10 @@ def test_new_lv_from_non_existing_lvs(self):
f01d01
                 pool.create()
f01d01
                 self.assertTrue(lvm.thpool_convert.called)
f01d01
 
f01d01
+
f01d01
+@unittest.skipUnless(not any(x.unavailable_type_dependencies() for x in DEVICE_CLASSES + [LVMVDOPoolMixin, LVMVDOLogicalVolumeMixin]), "some unsupported device classes required for this test")
f01d01
+class BlivetNewLVMVDODeviceTest(unittest.TestCase):
f01d01
+
f01d01
     def test_new_vdo_pool(self):
f01d01
         b = blivet.Blivet()
f01d01
         pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
f01d01
@@ -726,3 +733,102 @@ def test_new_vdo_pool(self):
f01d01
         self.assertEqual(vdopool.children[0], vdolv)
f01d01
         self.assertEqual(vdolv.parents[0], vdopool)
f01d01
         self.assertListEqual(vg.lvs, [vdopool, vdolv])
f01d01
+
f01d01
+
f01d01
+@unittest.skipUnless(not any(x.unavailable_type_dependencies() for x in DEVICE_CLASSES), "some unsupported device classes required for this test")
f01d01
+class BlivetLVMVDODependenciesTest(unittest.TestCase):
f01d01
+    def test_vdo_dependencies(self):
f01d01
+        blivet.tasks.availability.CACHE_AVAILABILITY = False
f01d01
+
f01d01
+        b = blivet.Blivet()
f01d01
+        pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
f01d01
+                           size=Size("10 GiB"), exists=True)
f01d01
+        vg = LVMVolumeGroupDevice("testvg", parents=[pv], exists=True)
f01d01
+
f01d01
+        for dev in (pv, vg):
f01d01
+            b.devicetree._add_device(dev)
f01d01
+
f01d01
+        # check that all the above devices are in the expected places
f01d01
+        self.assertEqual(set(b.devices), {pv, vg})
f01d01
+        self.assertEqual(set(b.vgs), {vg})
f01d01
+
f01d01
+        self.assertEqual(vg.size, Size("10236 MiB"))
f01d01
+
f01d01
+        vdopool = b.new_lv(name="vdopool", vdo_pool=True,
f01d01
+                           parents=[vg], compression=True,
f01d01
+                           deduplication=True,
f01d01
+                           size=blivet.size.Size("8 GiB"))
f01d01
+
f01d01
+        vdolv = b.new_lv(name="vdolv", vdo_lv=True,
f01d01
+                         parents=[vdopool],
f01d01
+                         size=blivet.size.Size("40 GiB"))
f01d01
+
f01d01
+        # Dependencies check: for VDO types these should be combination of "normal"
f01d01
+        # LVM dependencies (LVM libblockdev plugin + kpartx and DM plugin from DMDevice)
f01d01
+        # and LVM VDO technology from the LVM plugin
f01d01
+        lvm_vdo_dependencies = ["kpartx",
f01d01
+                                "libblockdev dm plugin",
f01d01
+                                "libblockdev lvm plugin",
f01d01
+                                "libblockdev lvm plugin (vdo technology)"]
f01d01
+        pool_deps = [d.name for d in vdopool.external_dependencies]
f01d01
+        six.assertCountEqual(self, pool_deps, lvm_vdo_dependencies)
f01d01
+
f01d01
+        vdolv_deps = [d.name for d in vdolv.external_dependencies]
f01d01
+        six.assertCountEqual(self, vdolv_deps, lvm_vdo_dependencies)
f01d01
+
f01d01
+        # same dependencies should be returned when checking with class not instance
f01d01
+        pool_type_deps = [d.name for d in LVMVDOPoolMixin.type_external_dependencies()]
f01d01
+        six.assertCountEqual(self, pool_type_deps, lvm_vdo_dependencies)
f01d01
+
f01d01
+        vdolv_type_deps = [d.name for d in LVMVDOLogicalVolumeMixin.type_external_dependencies()]
f01d01
+        six.assertCountEqual(self, vdolv_type_deps, lvm_vdo_dependencies)
f01d01
+
f01d01
+        # just to be sure LVM VDO specific code didn't break "normal" LVs
f01d01
+        normallv = b.new_lv(name="lvol0",
f01d01
+                            parents=[vg],
f01d01
+                            size=blivet.size.Size("1 GiB"))
f01d01
+
f01d01
+        normalvl_deps = [d.name for d in normallv.external_dependencies]
f01d01
+        six.assertCountEqual(self, normalvl_deps, ["kpartx",
f01d01
+                                                   "libblockdev dm plugin",
f01d01
+                                                   "libblockdev lvm plugin"])
f01d01
+
f01d01
+        with patch("blivet.devices.lvm.LVMVDOPoolMixin._external_dependencies",
f01d01
+                   new=[blivet.tasks.availability.unavailable_resource("VDO unavailability test")]):
f01d01
+            with patch("blivet.devices.lvm.LVMVDOLogicalVolumeMixin._external_dependencies",
f01d01
+                       new=[blivet.tasks.availability.unavailable_resource("VDO unavailability test")]):
f01d01
+
f01d01
+                pool_deps = [d.name for d in vdopool.unavailable_dependencies]
f01d01
+                self.assertEqual(pool_deps, ["VDO unavailability test"])
f01d01
+
f01d01
+                vdolv_deps = [d.name for d in vdolv.unavailable_dependencies]
f01d01
+                self.assertEqual(vdolv_deps, ["VDO unavailability test"])
f01d01
+
f01d01
+                # same dependencies should be returned when checking with class not instance
f01d01
+                pool_type_deps = [d.name for d in LVMVDOPoolMixin.unavailable_type_dependencies()]
f01d01
+                six.assertCountEqual(self, pool_type_deps, ["VDO unavailability test"])
f01d01
+
f01d01
+                vdolv_type_deps = [d.name for d in LVMVDOLogicalVolumeMixin.unavailable_type_dependencies()]
f01d01
+                six.assertCountEqual(self, vdolv_type_deps, ["VDO unavailability test"])
f01d01
+
f01d01
+                normallv_deps = [d.name for d in normallv.unavailable_dependencies]
f01d01
+                self.assertEqual(normallv_deps, [])
f01d01
+
f01d01
+                with self.assertRaises(errors.DependencyError):
f01d01
+                    b.create_device(vdopool)
f01d01
+                    b.create_device(vdolv)
f01d01
+
f01d01
+                b.create_device(normallv)
f01d01
+
f01d01
+    def test_vdo_dependencies_devicefactory(self):
f01d01
+        with patch("blivet.devices.lvm.LVMVDOPoolMixin._external_dependencies",
f01d01
+                   new=[blivet.tasks.availability.unavailable_resource("VDO unavailability test")]):
f01d01
+            with patch("blivet.devices.lvm.LVMVDOLogicalVolumeMixin._external_dependencies",
f01d01
+                       new=[blivet.tasks.availability.unavailable_resource("VDO unavailability test")]):
f01d01
+
f01d01
+                # shouldn't affect "normal" LVM
f01d01
+                lvm_supported = devicefactory.is_supported_device_type(devicefactory.DEVICE_TYPE_LVM)
f01d01
+                self.assertTrue(lvm_supported)
f01d01
+
f01d01
+                vdo_supported = devicefactory.is_supported_device_type(devicefactory.DEVICE_TYPE_LVM_VDO)
f01d01
+                self.assertFalse(vdo_supported)
f01d01
f01d01
From c7fb125ec552ee5070f8180f92fe5545709192ff Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Fri, 11 Dec 2020 15:02:05 +0100
f01d01
Subject: [PATCH 17/17] Bump required libblockdev version to 2.24
f01d01
f01d01
LVM VDO support was added in 2.24.
f01d01
---
f01d01
 python-blivet.spec | 2 +-
f01d01
 1 file changed, 1 insertion(+), 1 deletion(-)
f01d01
f01d01
diff --git a/python-blivet.spec b/python-blivet.spec
f01d01
index ffd4210e..58cad0b2 100644
f01d01
--- a/python-blivet.spec
f01d01
+++ b/python-blivet.spec
f01d01
@@ -36,7 +36,7 @@ Source1: http://github.com/storaged-project/blivet/archive/%{realname}-%{realver
f01d01
 %global partedver 1.8.1
f01d01
 %global pypartedver 3.10.4
f01d01
 %global utillinuxver 2.15.1
f01d01
-%global libblockdevver 2.19
f01d01
+%global libblockdevver 2.24
f01d01
 %global libbytesizever 0.3
f01d01
 %global pyudevver 0.18
f01d01