diff --git a/SOURCES/0012-xfs-grow-support.patch b/SOURCES/0012-xfs-grow-support.patch
new file mode 100644
index 0000000..1607c51
--- /dev/null
+++ b/SOURCES/0012-xfs-grow-support.patch
@@ -0,0 +1,459 @@
+From 433d863cd8a57e5fc30948ff905e6a477ed5f17c Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 14 Jul 2020 11:27:08 +0200
+Subject: [PATCH 1/4] Add support for XFS format grow
+
+---
+ blivet/formats/fs.py         |  2 ++
+ blivet/tasks/availability.py |  1 +
+ blivet/tasks/fsresize.py     | 54 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 57 insertions(+)
+
+diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
+index eee15aaa..12cb9885 100644
+--- a/blivet/formats/fs.py
++++ b/blivet/formats/fs.py
+@@ -1089,11 +1089,13 @@ class XFS(FS):
+     _formattable = True
+     _linux_native = True
+     _supported = True
++    _resizable = True
+     _packages = ["xfsprogs"]
+     _info_class = fsinfo.XFSInfo
+     _mkfs_class = fsmkfs.XFSMkfs
+     _readlabel_class = fsreadlabel.XFSReadLabel
+     _size_info_class = fssize.XFSSize
++    _resize_class = fsresize.XFSResize
+     _sync_class = fssync.XFSSync
+     _writelabel_class = fswritelabel.XFSWriteLabel
+     _writeuuid_class = fswriteuuid.XFSWriteUUID
+diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py
+index b6b5955a..df62780c 100644
+--- a/blivet/tasks/availability.py
++++ b/blivet/tasks/availability.py
+@@ -455,5 +455,6 @@ TUNE2FS_APP = application_by_version("tune2fs", E2FSPROGS_VERSION)
+ XFSADMIN_APP = application("xfs_admin")
+ XFSDB_APP = application("xfs_db")
+ XFSFREEZE_APP = application("xfs_freeze")
++XFSRESIZE_APP = application("xfs_growfs")
+ 
+ MOUNT_APP = application("mount")
+diff --git a/blivet/tasks/fsresize.py b/blivet/tasks/fsresize.py
+index e7e26984..12c0367f 100644
+--- a/blivet/tasks/fsresize.py
++++ b/blivet/tasks/fsresize.py
+@@ -20,7 +20,10 @@
+ # Red Hat Author(s): Anne Mulhern <amulhern@redhat.com>
+ 
+ import abc
++import os
++import tempfile
+ 
++from contextlib import contextmanager
+ from six import add_metaclass
+ 
+ from ..errors import FSError
+@@ -32,6 +35,9 @@ from . import task
+ from . import fstask
+ from . import dfresize
+ 
++import logging
++log = logging.getLogger("blivet")
++
+ 
+ @add_metaclass(abc.ABCMeta)
+ class FSResizeTask(fstask.FSTask):
+@@ -115,6 +121,54 @@ class NTFSResize(FSResize):
+         ]
+ 
+ 
++class XFSResize(FSResize):
++    ext = availability.XFSRESIZE_APP
++    unit = B
++    size_fmt = None
++
++    @contextmanager
++    def _do_temp_mount(self):
++        if self.fs.status:
++            yield
++        else:
++            dev_name = os.path.basename(self.fs.device)
++            tmpdir = tempfile.mkdtemp(prefix="xfs-tempmount-%s" % dev_name)
++            log.debug("mounting XFS on '%s' to '%s' for resize", self.fs.device, tmpdir)
++            try:
++                self.fs.mount(mountpoint=tmpdir)
++            except FSError as e:
++                raise FSError("Failed to mount XFS filesystem for resize: %s" % str(e))
++
++            try:
++                yield
++            finally:
++                util.umount(mountpoint=tmpdir)
++                os.rmdir(tmpdir)
++
++    def _get_block_size(self):
++        if self.fs._current_info:
++            # this should be set by update_size_info()
++            for line in self.fs._current_info.split("\n"):
++                if line.startswith("blocksize ="):
++                    return int(line.split("=")[-1])
++
++        raise FSError("Failed to get XFS filesystem block size for resize")
++
++    def size_spec(self):
++        # size for xfs_growfs is in blocks
++        return str(self.fs.target_size.convert_to(self.unit) / self._get_block_size())
++
++    @property
++    def args(self):
++        return [self.fs.system_mountpoint, "-D", self.size_spec()]
++
++    def do_task(self):
++        """ Resizes the XFS format. """
++
++        with self._do_temp_mount():
++            super(XFSResize, self).do_task()
++
++
+ class TmpFSResize(FSResize):
+ 
+     ext = availability.MOUNT_APP
+-- 
+2.26.2
+
+
+From 56d05334231c30699a9c77dedbc23fdb021b9dee Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 14 Jul 2020 11:27:51 +0200
+Subject: [PATCH 2/4] Add tests for XFS resize
+
+XFS supports only grow so we can't reuse most of the fstesting
+code and we also need to test the resize on partition because
+XFS won't allow grow to size bigger than the underlying block
+device.
+---
+ tests/formats_test/fs_test.py   | 91 +++++++++++++++++++++++++++++++++
+ tests/formats_test/fstesting.py | 33 ++++++------
+ 2 files changed, 107 insertions(+), 17 deletions(-)
+
+diff --git a/tests/formats_test/fs_test.py b/tests/formats_test/fs_test.py
+index 15fc0c35..9bc5d20d 100644
+--- a/tests/formats_test/fs_test.py
++++ b/tests/formats_test/fs_test.py
+@@ -2,8 +2,13 @@ import os
+ import tempfile
+ import unittest
+ 
++import parted
++
+ import blivet.formats.fs as fs
+ from blivet.size import Size, ROUND_DOWN
++from blivet.errors import DeviceFormatError
++from blivet.formats import get_format
++from blivet.devices import PartitionDevice, DiskDevice
+ 
+ from tests import loopbackedtestcase
+ 
+@@ -50,6 +55,92 @@ class ReiserFSTestCase(fstesting.FSAsRoot):
+ class XFSTestCase(fstesting.FSAsRoot):
+     _fs_class = fs.XFS
+ 
++    def can_resize(self, an_fs):
++        resize_tasks = (an_fs._resize, an_fs._size_info)
++        return not any(t.availability_errors for t in resize_tasks)
++
++    def _create_partition(self, disk, size):
++        disk.format = get_format("disklabel", device=disk.path, label_type="msdos")
++        disk.format.create()
++        pstart = disk.format.alignment.grainSize
++        pend = pstart + int(Size(size) / disk.format.parted_device.sectorSize)
++        disk.format.add_partition(pstart, pend, parted.PARTITION_NORMAL)
++        disk.format.parted_disk.commit()
++        part = disk.format.parted_disk.getPartitionBySector(pstart)
++
++        device = PartitionDevice(os.path.basename(part.path))
++        device.disk = disk
++        device.exists = True
++        device.parted_partition = part
++
++        return device
++
++    def _remove_partition(self, partition, disk):
++        disk.format.remove_partition(partition.parted_partition)
++        disk.format.parted_disk.commit()
++
++    def test_resize(self):
++        an_fs = self._fs_class()
++        if not an_fs.formattable:
++            self.skipTest("can not create filesystem %s" % an_fs.name)
++        an_fs.device = self.loop_devices[0]
++        self.assertIsNone(an_fs.create())
++        an_fs.update_size_info()
++
++        self._test_sizes(an_fs)
++        # CHECKME: target size is still 0 after updated_size_info is called.
++        self.assertEqual(an_fs.size, Size(0) if an_fs.resizable else an_fs._size)
++
++        if not self.can_resize(an_fs):
++            self.assertFalse(an_fs.resizable)
++            # Not resizable, so can not do resizing actions.
++            with self.assertRaises(DeviceFormatError):
++                an_fs.target_size = Size("64 MiB")
++            with self.assertRaises(DeviceFormatError):
++                an_fs.do_resize()
++        else:
++            disk = DiskDevice(os.path.basename(self.loop_devices[0]))
++            part = self._create_partition(disk, Size("50 MiB"))
++            an_fs = self._fs_class()
++            an_fs.device = part.path
++            self.assertIsNone(an_fs.create())
++            an_fs.update_size_info()
++
++            self.assertTrue(an_fs.resizable)
++
++            # grow the partition so we can grow the filesystem
++            self._remove_partition(part, disk)
++            part = self._create_partition(disk, size=part.size + Size("40 MiB"))
++
++            # Try a reasonable target size
++            TARGET_SIZE = Size("64 MiB")
++            an_fs.target_size = TARGET_SIZE
++            self.assertEqual(an_fs.target_size, TARGET_SIZE)
++            self.assertNotEqual(an_fs._size, TARGET_SIZE)
++            self.assertIsNone(an_fs.do_resize())
++            ACTUAL_SIZE = TARGET_SIZE.round_to_nearest(an_fs._resize.unit, rounding=ROUND_DOWN)
++            self.assertEqual(an_fs.size, ACTUAL_SIZE)
++            self.assertEqual(an_fs._size, ACTUAL_SIZE)
++            self._test_sizes(an_fs)
++
++            self._remove_partition(part, disk)
++
++        # and no errors should occur when checking
++        self.assertIsNone(an_fs.do_check())
++
++    def test_shrink(self):
++        self.skipTest("Not checking resize for this test category.")
++
++    def test_too_small(self):
++        self.skipTest("Not checking resize for this test category.")
++
++    def test_no_explicit_target_size2(self):
++        self.skipTest("Not checking resize for this test category.")
++
++    def test_too_big2(self):
++        # XXX this tests assumes that resizing to max size - 1 B will fail, but xfs_grow won't
++        self.skipTest("Not checking resize for this test category.")
++
+ 
+ class HFSTestCase(fstesting.FSAsRoot):
+     _fs_class = fs.HFS
+diff --git a/tests/formats_test/fstesting.py b/tests/formats_test/fstesting.py
+index 62f806f9..86b2a116 100644
+--- a/tests/formats_test/fstesting.py
++++ b/tests/formats_test/fstesting.py
+@@ -11,16 +11,6 @@ from blivet.size import Size, ROUND_DOWN
+ from blivet.formats import fs
+ 
+ 
+-def can_resize(an_fs):
+-    """ Returns True if this filesystem has all necessary resizing tools
+-        available.
+-
+-        :param an_fs: a filesystem object
+-    """
+-    resize_tasks = (an_fs._resize, an_fs._size_info, an_fs._minsize)
+-    return not any(t.availability_errors for t in resize_tasks)
+-
+-
+ @add_metaclass(abc.ABCMeta)
+ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
+ 
+@@ -32,6 +22,15 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
+     def __init__(self, methodName='run_test'):
+         super(FSAsRoot, self).__init__(methodName=methodName, device_spec=[self._DEVICE_SIZE])
+ 
++    def can_resize(self, an_fs):
++        """ Returns True if this filesystem has all necessary resizing tools
++            available.
++
++            :param an_fs: a filesystem object
++        """
++        resize_tasks = (an_fs._resize, an_fs._size_info, an_fs._minsize)
++        return not any(t.availability_errors for t in resize_tasks)
++
+     def _test_sizes(self, an_fs):
+         """ Test relationships between different size values.
+ 
+@@ -190,7 +189,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
+         # CHECKME: target size is still 0 after updated_size_info is called.
+         self.assertEqual(an_fs.size, Size(0) if an_fs.resizable else an_fs._size)
+ 
+-        if not can_resize(an_fs):
++        if not self.can_resize(an_fs):
+             self.assertFalse(an_fs.resizable)
+             # Not resizable, so can not do resizing actions.
+             with self.assertRaises(DeviceFormatError):
+@@ -221,7 +220,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
+         # in constructor call behavior would be different.
+ 
+         an_fs = self._fs_class()
+-        if not can_resize(an_fs):
++        if not self.can_resize(an_fs):
+             self.skipTest("Not checking resize for this test category.")
+         if not an_fs.formattable:
+             self.skipTest("can not create filesystem %s" % an_fs.name)
+@@ -244,7 +243,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
+         """
+         SIZE = Size("64 MiB")
+         an_fs = self._fs_class(size=SIZE)
+-        if not can_resize(an_fs):
++        if not self.can_resize(an_fs):
+             self.skipTest("Not checking resize for this test category.")
+         if not an_fs.formattable:
+             self.skipTest("can not create filesystem %s" % an_fs.name)
+@@ -264,7 +263,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
+ 
+     def test_shrink(self):
+         an_fs = self._fs_class()
+-        if not can_resize(an_fs):
++        if not self.can_resize(an_fs):
+             self.skipTest("Not checking resize for this test category.")
+         if not an_fs.formattable:
+             self.skipTest("can not create filesystem %s" % an_fs.name)
+@@ -296,7 +295,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
+ 
+     def test_too_small(self):
+         an_fs = self._fs_class()
+-        if not can_resize(an_fs):
++        if not self.can_resize(an_fs):
+             self.skipTest("Not checking resize for this test category.")
+         if not an_fs.formattable:
+             self.skipTest("can not create or resize filesystem %s" % an_fs.name)
+@@ -315,7 +314,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
+ 
+     def test_too_big(self):
+         an_fs = self._fs_class()
+-        if not can_resize(an_fs):
++        if not self.can_resize(an_fs):
+             self.skipTest("Not checking resize for this test category.")
+         if not an_fs.formattable:
+             self.skipTest("can not create filesystem %s" % an_fs.name)
+@@ -334,7 +333,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
+ 
+     def test_too_big2(self):
+         an_fs = self._fs_class()
+-        if not can_resize(an_fs):
++        if not self.can_resize(an_fs):
+             self.skipTest("Not checking resize for this test category.")
+         if not an_fs.formattable:
+             self.skipTest("can not create filesystem %s" % an_fs.name)
+-- 
+2.26.2
+
+
+From 51acc04f4639f143b55789a06a68aae988a91296 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Wed, 15 Jul 2020 12:59:04 +0200
+Subject: [PATCH 3/4] Add support for checking and fixing XFS using xfs_repair
+
+---
+ blivet/formats/fs.py          |  1 +
+ blivet/tasks/availability.py  |  1 +
+ blivet/tasks/fsck.py          | 12 ++++++++++++
+ tests/formats_test/fs_test.py |  6 +++---
+ 4 files changed, 17 insertions(+), 3 deletions(-)
+
+diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
+index 12cb9885..06fbdf10 100644
+--- a/blivet/formats/fs.py
++++ b/blivet/formats/fs.py
+@@ -1091,6 +1091,7 @@ class XFS(FS):
+     _supported = True
+     _resizable = True
+     _packages = ["xfsprogs"]
++    _fsck_class = fsck.XFSCK
+     _info_class = fsinfo.XFSInfo
+     _mkfs_class = fsmkfs.XFSMkfs
+     _readlabel_class = fsreadlabel.XFSReadLabel
+diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py
+index df62780c..f3b76650 100644
+--- a/blivet/tasks/availability.py
++++ b/blivet/tasks/availability.py
+@@ -456,5 +456,6 @@ XFSADMIN_APP = application("xfs_admin")
+ XFSDB_APP = application("xfs_db")
+ XFSFREEZE_APP = application("xfs_freeze")
+ XFSRESIZE_APP = application("xfs_growfs")
++XFSREPAIR_APP = application("xfs_repair")
+ 
+ MOUNT_APP = application("mount")
+diff --git a/blivet/tasks/fsck.py b/blivet/tasks/fsck.py
+index 5274f13a..8477f5f8 100644
+--- a/blivet/tasks/fsck.py
++++ b/blivet/tasks/fsck.py
+@@ -123,6 +123,18 @@ class Ext2FSCK(FSCK):
+         return "\n".join(msgs) or None
+ 
+ 
++class XFSCK(FSCK):
++    _fsck_errors = {1: "Runtime error encountered during repair operation.",
++                    2: "XFS repair was unable to proceed due to a dirty log."}
++
++    ext = availability.XFSREPAIR_APP
++    options = []
++
++    def _error_message(self, rc):
++        msgs = (self._fsck_errors[c] for c in self._fsck_errors.keys() if rc & c)
++        return "\n".join(msgs) or None
++
++
+ class HFSPlusFSCK(FSCK):
+     _fsck_errors = {3: "Quick check found a dirty filesystem; no repairs done.",
+                     4: "Root filesystem was dirty. System should be rebooted.",
+diff --git a/tests/formats_test/fs_test.py b/tests/formats_test/fs_test.py
+index 9bc5d20d..8fb099fd 100644
+--- a/tests/formats_test/fs_test.py
++++ b/tests/formats_test/fs_test.py
+@@ -123,10 +123,10 @@ class XFSTestCase(fstesting.FSAsRoot):
+             self.assertEqual(an_fs._size, ACTUAL_SIZE)
+             self._test_sizes(an_fs)
+ 
+-            self._remove_partition(part, disk)
++            # and no errors should occur when checking
++            self.assertIsNone(an_fs.do_check())
+ 
+-        # and no errors should occur when checking
+-        self.assertIsNone(an_fs.do_check())
++            self._remove_partition(part, disk)
+ 
+     def test_shrink(self):
+         self.skipTest("Not checking resize for this test category.")
+-- 
+2.26.2
+
+
+From 2a6947098e66f880193f3bac2282a6c7857ca5f7 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Thu, 16 Jul 2020 09:05:35 +0200
+Subject: [PATCH 4/4] Use xfs_db in read-only mode when getting XFS information
+
+This way it will also work on mounted filesystems.
+---
+ blivet/tasks/fsinfo.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/blivet/tasks/fsinfo.py b/blivet/tasks/fsinfo.py
+index af208f5d..41ff700f 100644
+--- a/blivet/tasks/fsinfo.py
++++ b/blivet/tasks/fsinfo.py
+@@ -95,7 +95,7 @@ class ReiserFSInfo(FSInfo):
+ 
+ class XFSInfo(FSInfo):
+     ext = availability.XFSDB_APP
+-    options = ["-c", "sb 0", "-c", "p dblocks", "-c", "p blocksize"]
++    options = ["-c", "sb 0", "-c", "p dblocks", "-c", "p blocksize", "-r"]
+ 
+ 
+ class UnimplementedFSInfo(fstask.UnimplementedFSTask):
+-- 
+2.26.2
+
diff --git a/SOURCES/0013-Do-not-limit-swap-to-128-GiB.patch b/SOURCES/0013-Do-not-limit-swap-to-128-GiB.patch
new file mode 100644
index 0000000..5b9f0ed
--- /dev/null
+++ b/SOURCES/0013-Do-not-limit-swap-to-128-GiB.patch
@@ -0,0 +1,76 @@
+From aa4ce218fe9b4ee3571d872ff1575a499596181c Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Fri, 29 May 2020 12:14:30 +0200
+Subject: [PATCH 1/2] Do not limit swap to 128 GiB
+
+The limit was part of change to limit suggested swap size in
+kickstart which doesn't use the SwapSpace._max_size so there is no
+reason to limit this for manual installations.
+16 TiB seems to be max usable swap size based on mkswap code.
+
+Resolves: rhbz#1656485
+---
+ blivet/formats/swap.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/blivet/formats/swap.py b/blivet/formats/swap.py
+index 4b8a7edf..3cc59138 100644
+--- a/blivet/formats/swap.py
++++ b/blivet/formats/swap.py
+@@ -52,8 +52,7 @@ class SwapSpace(DeviceFormat):
+     _linux_native = True                # for clearpart
+     _plugin = availability.BLOCKDEV_SWAP_PLUGIN
+ 
+-    # see rhbz#744129 for details
+-    _max_size = Size("128 GiB")
++    _max_size = Size("16 TiB")
+ 
+     config_actions_map = {"label": "write_label"}
+ 
+-- 
+2.26.2
+
+
+From 93aa6ad87116f1c86616d73dbe561251c4a0c286 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Thu, 11 Jun 2020 14:27:44 +0200
+Subject: [PATCH 2/2] Add test for SwapSpace max size
+
+---
+ tests/formats_test/swap_test.py | 24 ++++++++++++++++++++++++
+ 1 file changed, 24 insertions(+)
+ create mode 100644 tests/formats_test/swap_test.py
+
+diff --git a/tests/formats_test/swap_test.py b/tests/formats_test/swap_test.py
+new file mode 100644
+index 00000000..56356144
+--- /dev/null
++++ b/tests/formats_test/swap_test.py
+@@ -0,0 +1,24 @@
++import test_compat  # pylint: disable=unused-import
++
++import six
++import unittest
++
++from blivet.devices.storage import StorageDevice
++from blivet.errors import DeviceError
++from blivet.formats import get_format
++
++from blivet.size import Size
++
++
++class SwapNodevTestCase(unittest.TestCase):
++
++    def test_swap_max_size(self):
++        StorageDevice("dev", size=Size("129 GiB"),
++                      fmt=get_format("swap"))
++
++        StorageDevice("dev", size=Size("15 TiB"),
++                      fmt=get_format("swap"))
++
++        with six.assertRaisesRegex(self, DeviceError, "device is too large for new format"):
++            StorageDevice("dev", size=Size("17 TiB"),
++                          fmt=get_format("swap"))
+-- 
+2.26.2
+
diff --git a/SOURCES/0014-Use-UnusableConfigurationError-for-patially-hidden-multipath-devices.patch b/SOURCES/0014-Use-UnusableConfigurationError-for-patially-hidden-multipath-devices.patch
new file mode 100644
index 0000000..1e14de6
--- /dev/null
+++ b/SOURCES/0014-Use-UnusableConfigurationError-for-patially-hidden-multipath-devices.patch
@@ -0,0 +1,78 @@
+From 4e6a322d32d2a12f8a87ab763a6286cf3d7b5c27 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 8 Sep 2020 13:57:40 +0200
+Subject: [PATCH] Use UnusableConfigurationError for partially hidden multipath
+ devices
+
+Follow-up for https://github.com/storaged-project/blivet/pull/883
+to make Anaconda show an error message instead of crashing.
+
+Resolves: rhbz#1877052
+---
+ blivet/devicetree.py     | 4 ++--
+ blivet/errors.py         | 6 ++++++
+ tests/devicetree_test.py | 4 ++--
+ 3 files changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/blivet/devicetree.py b/blivet/devicetree.py
+index 2afb0d0e..57a9bbd7 100644
+--- a/blivet/devicetree.py
++++ b/blivet/devicetree.py
+@@ -32,7 +32,7 @@ from gi.repository import BlockDev as blockdev
+ 
+ from .actionlist import ActionList
+ from .callbacks import callbacks
+-from .errors import DeviceError, DeviceTreeError, StorageError, DuplicateUUIDError
++from .errors import DeviceError, DeviceTreeError, StorageError, DuplicateUUIDError, InvalidMultideviceSelection
+ from .deviceaction import ActionDestroyDevice, ActionDestroyFormat
+ from .devices import BTRFSDevice, NoDevice, PartitionDevice
+ from .devices import LVMLogicalVolumeDevice, LVMVolumeGroupDevice
+@@ -936,7 +936,7 @@ class DeviceTreeBase(object):
+             if is_ignored:
+                 if len(disk.children) == 1:
+                     if not all(self._is_ignored_disk(d) for d in disk.children[0].parents):
+-                        raise DeviceTreeError("Including only a subset of raid/multipath member disks is not allowed.")
++                        raise InvalidMultideviceSelection("Including only a subset of raid/multipath member disks is not allowed.")
+ 
+                     # and also children like fwraid or mpath
+                     self.hide(disk.children[0])
+diff --git a/blivet/errors.py b/blivet/errors.py
+index 811abf81..7a93f1ce 100644
+--- a/blivet/errors.py
++++ b/blivet/errors.py
+@@ -233,6 +233,12 @@ class DuplicateVGError(UnusableConfigurationError):
+                     "Hint 2: You can get the VG UUIDs by running "
+                     "'pvs -o +vg_uuid'.")
+ 
++
++class InvalidMultideviceSelection(UnusableConfigurationError):
++    suggestion = N_("All parent devices must be selected when choosing exclusive "
++                    "or ignored disks for a multipath or firmware RAID device.")
++
++
+ # DeviceAction
+ 
+ 
+diff --git a/tests/devicetree_test.py b/tests/devicetree_test.py
+index 6032e7f6..4e47ffc3 100644
+--- a/tests/devicetree_test.py
++++ b/tests/devicetree_test.py
+@@ -5,7 +5,7 @@ import six
+ import unittest
+ 
+ from blivet.actionlist import ActionList
+-from blivet.errors import DeviceTreeError, DuplicateUUIDError
++from blivet.errors import DeviceTreeError, DuplicateUUIDError, InvalidMultideviceSelection
+ from blivet.deviceaction import ACTION_TYPE_DESTROY, ACTION_OBJECT_DEVICE
+ from blivet.devicelibs import lvm
+ from blivet.devices import DiskDevice
+@@ -512,5 +512,5 @@ class DeviceTreeIgnoredExclusiveMultipathTestCase(unittest.TestCase):
+         self.tree.ignored_disks = ["sda", "sdb"]
+         self.tree.exclusive_disks = []
+ 
+-        with self.assertRaises(DeviceTreeError):
++        with self.assertRaises(InvalidMultideviceSelection):
+             self.tree._hide_ignored_disks()
+-- 
+2.26.2
+
diff --git a/SOURCES/0015-Fix-possible-UnicodeDecodeError-when-reading-model-f.patch b/SOURCES/0015-Fix-possible-UnicodeDecodeError-when-reading-model-f.patch
new file mode 100644
index 0000000..24e408e
--- /dev/null
+++ b/SOURCES/0015-Fix-possible-UnicodeDecodeError-when-reading-model-f.patch
@@ -0,0 +1,32 @@
+From 866a48e6c3d8246d2897bb402a191df5f2848aa4 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 23 Jun 2020 10:33:33 +0200
+Subject: [PATCH] Fix possible UnicodeDecodeError when reading model from sysfs
+
+Some Innovation IT NVMe devices have an (invalid) unicode in their
+model name.
+
+Resolves: rhbz#1849326
+---
+ blivet/udev.py | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/blivet/udev.py b/blivet/udev.py
+index 41c99496..2c795225 100644
+--- a/blivet/udev.py
++++ b/blivet/udev.py
+@@ -185,8 +185,9 @@ def __is_blacklisted_blockdev(dev_name):
+         if any(re.search(expr, dev_name) for expr in device_name_blacklist):
+             return True
+ 
+-    if os.path.exists("/sys/class/block/%s/device/model" % (dev_name,)):
+-        model = open("/sys/class/block/%s/device/model" % (dev_name,)).read()
++    model_path = "/sys/class/block/%s/device/model" % dev_name
++    if os.path.exists(model_path):
++        model = open(model_path, encoding="utf-8", errors="replace").read()
+         for bad in ("IBM *STMF KERNEL", "SCEI Flash-5", "DGC LUNZ"):
+             if model.find(bad) != -1:
+                 log.info("ignoring %s with model %s", dev_name, model)
+-- 
+2.26.2
+
diff --git a/SOURCES/0016-Basic-LVM-VDO-support.patch b/SOURCES/0016-Basic-LVM-VDO-support.patch
new file mode 100644
index 0000000..b52342b
--- /dev/null
+++ b/SOURCES/0016-Basic-LVM-VDO-support.patch
@@ -0,0 +1,415 @@
+From 3f6bbf52442609b8e6e3919a3fdd8c5af64923e6 Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 12 May 2020 12:48:41 +0200
+Subject: [PATCH 1/3] Add basic support for LVM VDO devices
+
+This adds support for LVM VDO devices detection during populate
+and allows removing both VDO LVs and VDO pools using actions.
+---
+ blivet/devices/lvm.py           | 150 +++++++++++++++++++++++++++++++-
+ blivet/populator/helpers/lvm.py |  16 +++-
+ tests/action_test.py            |  39 +++++++++
+ tests/devices_test/lvm_test.py  |  34 ++++++++
+ tests/storagetestcase.py        |  11 ++-
+ 5 files changed, 245 insertions(+), 5 deletions(-)
+
+diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
+index 97de6acd..d9e24a33 100644
+--- a/blivet/devices/lvm.py
++++ b/blivet/devices/lvm.py
+@@ -1789,8 +1789,132 @@ class LVMThinLogicalVolumeMixin(object):
+         data.pool_name = self.pool.lvname
+ 
+ 
++class LVMVDOPoolMixin(object):
++    def __init__(self):
++        self._lvs = []
++
++    @property
++    def is_vdo_pool(self):
++        return self.seg_type == "vdo-pool"
++
++    @property
++    def type(self):
++        return "lvmvdopool"
++
++    @property
++    def resizable(self):
++        return False
++
++    @util.requires_property("is_vdo_pool")
++    def _add_log_vol(self, lv):
++        """ Add an LV to this VDO pool. """
++        if lv in self._lvs:
++            raise ValueError("lv is already part of this VDO pool")
++
++        self.vg._add_log_vol(lv)
++        log.debug("Adding %s/%s to %s", lv.name, lv.size, self.name)
++        self._lvs.append(lv)
++
++    @util.requires_property("is_vdo_pool")
++    def _remove_log_vol(self, lv):
++        """ Remove an LV from this VDO pool. """
++        if lv not in self._lvs:
++            raise ValueError("specified lv is not part of this VDO pool")
++
++        self._lvs.remove(lv)
++        self.vg._remove_log_vol(lv)
++
++    @property
++    @util.requires_property("is_vdo_pool")
++    def lvs(self):
++        """ A list of this VDO pool's LVs """
++        return self._lvs[:]     # we don't want folks changing our list
++
++    @property
++    def direct(self):
++        """ Is this device directly accessible? """
++        return False
++
++    def _create(self):
++        """ Create the device. """
++        raise NotImplementedError
++
++
++class LVMVDOLogicalVolumeMixin(object):
++    def __init__(self):
++        pass
++
++    def _init_check(self):
++        pass
++
++    def _check_parents(self):
++        """Check that this device has parents as expected"""
++        if isinstance(self.parents, (list, ParentList)):
++            if len(self.parents) != 1:
++                raise ValueError("constructor requires a single vdo-pool LV")
++
++            container = self.parents[0]
++        else:
++            container = self.parents
++
++        if not container or not isinstance(container, LVMLogicalVolumeDevice) or not container.is_vdo_pool:
++            raise ValueError("constructor requires a vdo-pool LV")
++
++    @property
++    def vg_space_used(self):
++        return Size(0)    # the pool's size is already accounted for in the vg
++
++    @property
++    def is_vdo_lv(self):
++        return self.seg_type == "vdo"
++
++    @property
++    def vg(self):
++        # parents[0] is the pool, not the VG so set the VG here
++        return self.pool.vg
++
++    @property
++    def type(self):
++        return "vdolv"
++
++    @property
++    def resizable(self):
++        return False
++
++    @property
++    @util.requires_property("is_vdo_lv")
++    def pool(self):
++        return self.parents[0]
++
++    def _create(self):
++        """ Create the device. """
++        raise NotImplementedError
++
++    def _destroy(self):
++        # nothing to do here, VDO LV is destroyed automatically together with
++        # the VDO pool
++        pass
++
++    def remove_hook(self, modparent=True):
++        if modparent:
++            self.pool._remove_log_vol(self)
++
++        # pylint: disable=bad-super-call
++        super(LVMLogicalVolumeBase, self).remove_hook(modparent=modparent)
++
++    def add_hook(self, new=True):
++        # pylint: disable=bad-super-call
++        super(LVMLogicalVolumeBase, self).add_hook(new=new)
++        if new:
++            return
++
++        if self not in self.pool.lvs:
++            self.pool._add_log_vol(self)
++
++
+ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin, LVMSnapshotMixin,
+-                             LVMThinPoolMixin, LVMThinLogicalVolumeMixin):
++                             LVMThinPoolMixin, LVMThinLogicalVolumeMixin, LVMVDOPoolMixin,
++                             LVMVDOLogicalVolumeMixin):
+     """ An LVM Logical Volume """
+ 
+     # generally resizable, see :property:`resizable` for details
+@@ -1879,6 +2003,8 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
+         LVMLogicalVolumeBase.__init__(self, name, parents, size, uuid, seg_type,
+                                       fmt, exists, sysfs_path, grow, maxsize,
+                                       percent, cache_request, pvs, from_lvs)
++        LVMVDOPoolMixin.__init__(self)
++        LVMVDOLogicalVolumeMixin.__init__(self)
+ 
+         LVMInternalLogicalVolumeMixin._init_check(self)
+         LVMSnapshotMixin._init_check(self)
+@@ -1905,6 +2031,10 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
+             ret.append(LVMThinPoolMixin)
+         if self.is_thin_lv:
+             ret.append(LVMThinLogicalVolumeMixin)
++        if self.is_vdo_pool:
++            ret.append(LVMVDOPoolMixin)
++        if self.is_vdo_lv:
++            ret.append(LVMVDOLogicalVolumeMixin)
+         return ret
+ 
+     def _try_specific_call(self, name, *args, **kwargs):
+@@ -2066,6 +2196,11 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
+     def display_lv_name(self):
+         return self.lvname
+ 
++    @property
++    @type_specific
++    def pool(self):
++        return super(LVMLogicalVolumeDevice, self).pool
++
+     def _setup(self, orig=False):
+         """ Open, or set up, a device. """
+         log_method_call(self, self.name, orig=orig, status=self.status,
+@@ -2167,6 +2302,19 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin
+         udev.settle()
+         blockdev.lvm.lvresize(self.vg.name, self._name, self.size)
+ 
++    @type_specific
++    def _add_log_vol(self, lv):
++        pass
++
++    @type_specific
++    def _remove_log_vol(self, lv):
++        pass
++
++    @property
++    @type_specific
++    def lvs(self):
++        return []
++
+     @property
+     @type_specific
+     def direct(self):
+diff --git a/blivet/populator/helpers/lvm.py b/blivet/populator/helpers/lvm.py
+index 4b674fac..ff8bf59f 100644
+--- a/blivet/populator/helpers/lvm.py
++++ b/blivet/populator/helpers/lvm.py
+@@ -211,9 +211,6 @@ class LVMFormatPopulator(FormatPopulator):
+                     origin = self._devicetree.get_device_by_name(origin_device_name)
+ 
+                 lv_kwargs["origin"] = origin
+-            elif lv_attr[0] == 'v':
+-                # skip vorigins
+-                return
+             elif lv_attr[0] in 'IrielTCo' and lv_name.endswith(']'):
+                 # an internal LV, add the an instance of the appropriate class
+                 # to internal_lvs for later processing when non-internal LVs are
+@@ -237,6 +234,19 @@ class LVMFormatPopulator(FormatPopulator):
+                     origin = self._devicetree.get_device_by_name(origin_device_name)
+                     lv_kwargs["origin"] = origin
+ 
++                lv_parents = [self._devicetree.get_device_by_name(pool_device_name)]
++            elif lv_attr[0] == 'd':
++                # vdo pool
++                # nothing to do here
++                pass
++            elif lv_attr[0] == 'v':
++                if lv_type != "vdo":
++                    # skip vorigins
++                    return
++                pool_name = blockdev.lvm.vdolvpoolname(vg_name, lv_name)
++                pool_device_name = "%s-%s" % (vg_name, pool_name)
++                add_required_lv(pool_device_name, "failed to look up VDO pool")
++
+                 lv_parents = [self._devicetree.get_device_by_name(pool_device_name)]
+             elif lv_name.endswith(']'):
+                 # unrecognized Internal LVM2 device
+diff --git a/tests/action_test.py b/tests/action_test.py
+index 90c1b312..8f9a7424 100644
+--- a/tests/action_test.py
++++ b/tests/action_test.py
+@@ -1252,6 +1252,45 @@ class DeviceActionTestCase(StorageTestCase):
+         self.assertEqual(set(self.storage.lvs), {pool})
+         self.assertEqual(set(pool._internal_lvs), {lv1, lv2})
+ 
++    def test_lvm_vdo_destroy(self):
++        self.destroy_all_devices()
++        sdc = self.storage.devicetree.get_device_by_name("sdc")
++        sdc1 = self.new_device(device_class=PartitionDevice, name="sdc1",
++                               size=Size("50 GiB"), parents=[sdc],
++                               fmt=blivet.formats.get_format("lvmpv"))
++        self.schedule_create_device(sdc1)
++
++        vg = self.new_device(device_class=LVMVolumeGroupDevice,
++                             name="vg", parents=[sdc1])
++        self.schedule_create_device(vg)
++
++        pool = self.new_device(device_class=LVMLogicalVolumeDevice,
++                               name="data", parents=[vg],
++                               size=Size("10 GiB"),
++                               seg_type="vdo-pool", exists=True)
++        self.storage.devicetree._add_device(pool)
++        lv = self.new_device(device_class=LVMLogicalVolumeDevice,
++                             name="meta", parents=[pool],
++                             size=Size("50 GiB"),
++                             seg_type="vdo", exists=True)
++        self.storage.devicetree._add_device(lv)
++
++        remove_lv = self.schedule_destroy_device(lv)
++        self.assertListEqual(pool.lvs, [])
++        self.assertNotIn(lv, vg.lvs)
++
++        # cancelling the action should put lv back to both vg and pool lvs
++        self.storage.devicetree.actions.remove(remove_lv)
++        self.assertListEqual(pool.lvs, [lv])
++        self.assertIn(lv, vg.lvs)
++
++        # can't remove non-leaf pool
++        with self.assertRaises(ValueError):
++            self.schedule_destroy_device(pool)
++
++        self.schedule_destroy_device(lv)
++        self.schedule_destroy_device(pool)
++
+ 
+ class ConfigurationActionsTest(unittest.TestCase):
+ 
+diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py
+index 9e701d18..204cb99a 100644
+--- a/tests/devices_test/lvm_test.py
++++ b/tests/devices_test/lvm_test.py
+@@ -405,6 +405,40 @@ class LVMDeviceTest(unittest.TestCase):
+                                exists=False)
+         self.assertFalse(vg.is_empty)
+ 
++    def test_lvm_vdo_pool(self):
++        pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"),
++                           size=Size("1 GiB"), exists=True)
++        vg = LVMVolumeGroupDevice("testvg", parents=[pv])
++        pool = LVMLogicalVolumeDevice("testpool", parents=[vg], size=Size("512 MiB"),
++                                      seg_type="vdo-pool", exists=True)
++        self.assertTrue(pool.is_vdo_pool)
++
++        free = vg.free_space
++        lv = LVMLogicalVolumeDevice("testlv", parents=[pool], size=Size("2 GiB"),
++                                    seg_type="vdo", exists=True)
++        self.assertTrue(lv.is_vdo_lv)
++        self.assertEqual(lv.vg, vg)
++        self.assertEqual(lv.pool, pool)
++
++        # free space in the vg shouldn't be affected by the vdo lv
++        self.assertEqual(lv.vg_space_used, 0)
++        self.assertEqual(free, vg.free_space)
++
++        self.assertListEqual(pool.lvs, [lv])
++
++        # now try to destroy both the pool and the vdo lv
++        # for the lv this should be a no-op, destroying the pool should destroy both
++        with patch("blivet.devices.lvm.blockdev.lvm") as lvm:
++            lv.destroy()
++            lv.remove_hook()
++            self.assertFalse(lv.exists)
++            self.assertFalse(lvm.lvremove.called)
++            self.assertListEqual(pool.lvs, [])
++
++            pool.destroy()
++            self.assertFalse(pool.exists)
++            self.assertTrue(lvm.lvremove.called)
++
+ 
+ class TypeSpecificCallsTest(unittest.TestCase):
+     def test_type_specific_calls(self):
+diff --git a/tests/storagetestcase.py b/tests/storagetestcase.py
+index e581bca6..1844dec5 100644
+--- a/tests/storagetestcase.py
++++ b/tests/storagetestcase.py
+@@ -96,7 +96,16 @@ class StorageTestCase(unittest.TestCase):
+     def new_device(self, *args, **kwargs):
+         """ Return a new Device instance suitable for testing. """
+         device_class = kwargs.pop("device_class")
+-        exists = kwargs.pop("exists", False)
++
++        # we intentionally don't pass the "exists" kwarg to the constructor
++        # becauses this causes issues with some devices (especially partitions)
++        # but we still need it for some LVs like VDO because we can't create
++        # those so we need to fake their existence even for the constructor
++        if device_class is blivet.devices.LVMLogicalVolumeDevice:
++            exists = kwargs.get("exists", False)
++        else:
++            exists = kwargs.pop("exists", False)
++
+         part_type = kwargs.pop("part_type", parted.PARTITION_NORMAL)
+         device = device_class(*args, **kwargs)
+ 
+-- 
+2.26.2
+
+
+From f05a66e1bed1ca1f3cd7d7ffecd6693ab4d7f32a Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 12 May 2020 12:52:47 +0200
+Subject: [PATCH 2/3] Fix checking for filesystem support in action_test
+
+---
+ tests/action_test.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tests/action_test.py b/tests/action_test.py
+index 8f9a7424..228eb97a 100644
+--- a/tests/action_test.py
++++ b/tests/action_test.py
+@@ -56,7 +56,7 @@ FORMAT_CLASSES = [
+ 
+ 
+ @unittest.skipUnless(not any(x.unavailable_type_dependencies() for x in DEVICE_CLASSES), "some unsupported device classes required for this test")
+-@unittest.skipUnless(not any(x().utils_available for x in FORMAT_CLASSES), "some unsupported format classes required for this test")
++@unittest.skipUnless(all(x().utils_available for x in FORMAT_CLASSES), "some unsupported format classes required for this test")
+ class DeviceActionTestCase(StorageTestCase):
+ 
+     """ DeviceActionTestSuite """
+-- 
+2.26.2
+
+
+From 69bd2e69e21c8779377a6f54b3d83cb35138867a Mon Sep 17 00:00:00 2001
+From: Vojtech Trefny <vtrefny@redhat.com>
+Date: Tue, 12 May 2020 12:54:03 +0200
+Subject: [PATCH 3/3] Fix LV min size for resize in test_action_dependencies
+
+We've recently changed min size for all filesystems so we can't
+resize the LV to the device minimal size.
+This was overlooked in the original change because these tests
+were skipped.
+---
+ tests/action_test.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tests/action_test.py b/tests/action_test.py
+index 228eb97a..77176f46 100644
+--- a/tests/action_test.py
++++ b/tests/action_test.py
+@@ -870,7 +870,7 @@ class DeviceActionTestCase(StorageTestCase):
+                                   name="testlv2", parents=[testvg])
+         testlv2.format = self.new_format("ext4", device=testlv2.path,
+                                          exists=True, device_instance=testlv2)
+-        shrink_lv2 = ActionResizeDevice(testlv2, testlv2.size - Size("10 GiB"))
++        shrink_lv2 = ActionResizeDevice(testlv2, testlv2.size - Size("10 GiB") + Ext4FS._min_size)
+         shrink_lv2.apply()
+ 
+         self.assertTrue(grow_lv.requires(shrink_lv2))
+-- 
+2.26.2
+
diff --git a/SPECS/python-blivet.spec b/SPECS/python-blivet.spec
index da3276d..44bca8a 100644
--- a/SPECS/python-blivet.spec
+++ b/SPECS/python-blivet.spec
@@ -23,7 +23,7 @@ Version: 3.2.2
 
 #%%global prerelease .b2
 # prerelease, if defined, should be something like .a1, .b1, .b2.dev1, or .c2
-Release: 6%{?prerelease}%{?dist}
+Release: 7%{?prerelease}%{?dist}
 Epoch: 1
 License: LGPLv2+
 Group: System Environment/Libraries
@@ -42,6 +42,11 @@ Patch7: 0008-set-allowed-disk-labels-for-s390x-as-standard-ones-plus-dasd.patch
 Patch8: 0009-Do-not-use-BlockDev-utils_have_kernel_module-to-check-for-modules.patch
 Patch9: 0010-Fix-name-resolution-for-MD-devices-and-partitions-on.patch
 Patch10: 0011-Fix-ignoring-disk-devices-with-parents-or-children.patch
+Patch11: 0012-xfs-grow-support.patch
+Patch12: 0013-Do-not-limit-swap-to-128-GiB.patch
+Patch13: 0014-Use-UnusableConfigurationError-for-patially-hidden-multipath-devices.patch
+Patch14: 0015-Fix-possible-UnicodeDecodeError-when-reading-model-f.patch
+Patch15: 0016-Basic-LVM-VDO-support.patch
 
 # Versions of required components (done so we make sure the buildrequires
 # match the requires versions of things).
@@ -203,6 +208,18 @@ configuration.
 %endif
 
 %changelog
+* Wed Nov 18 2020 Vojtech Trefny <vtrefny@redhat.com> - 3.2.2-7
+- Add support for XFS format grow
+  Resolves: rhbz#1862349
+- Do not limit swap to 128 GiB
+  Resolves: rhbz#1656485
+- Use UnusableConfigurationError for partially hidden multipath devices
+  Resolves: rhbz#1877052
+- Fix possible UnicodeDecodeError when reading model from sysfs
+  Resolves: rhbz#1849326
+- Add basic support for LVM VDO devices
+  Resolves: rhbz#1828745
+
 * Thu Aug 20 2020 Vojtech Trefny <vtrefny@redhat.com> - 3.2.2-6
 - Fix name resolution for MD devices and partitions on them
   Resolves: rhbz#1862904