From 0777b9d519421f3c46f6dcd51e39ecdc2956e2e0 Mon Sep 17 00:00:00 2001
From: Jan Pokorny <japokorn@redhat.com>
Date: Thu, 25 Apr 2024 14:06:13 +0200
Subject: [PATCH] Added support for PV grow

Storage role requires support for a case when PV has to be resized to
fill all available space when its device's size changes (usually on VM).

A new flag 'grow_to_fill' was added, which marks the device for size
expansion (all available space it taken).
Proper size is determined by LVM, avoiding inaccurate size
calculations in blivet.
---
 blivet/formats/__init__.py                    |  4 +++-
 blivet/formats/lvmpv.py                       | 23 ++++++++++++++++++-
 blivet/tasks/pvtask.py                        |  7 +++++-
 .../storage_tests/formats_test/lvmpv_test.py  | 10 ++++++++
 4 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/blivet/formats/__init__.py b/blivet/formats/__init__.py
index b1ad740e..eb8b6ab3 100644
--- a/blivet/formats/__init__.py
+++ b/blivet/formats/__init__.py
@@ -424,7 +424,9 @@ class DeviceFormat(ObjectID):
         if not self.resizable:
             raise FormatResizeError("format not resizable", self.device)
 
-        if self.target_size == self.current_size:
+        # skip if sizes are equal unless grow to fill on lvmpv is requested
+        if (self.target_size == self.current_size and
+                (self.type != "lvmpv" or not self.grow_to_fill)):  # pylint: disable=no-member
             return
 
         if not self._resize.available:
diff --git a/blivet/formats/lvmpv.py b/blivet/formats/lvmpv.py
index 65acedbe..51fa4a3c 100644
--- a/blivet/formats/lvmpv.py
+++ b/blivet/formats/lvmpv.py
@@ -33,7 +33,7 @@ from ..devicelibs import lvm
 from ..tasks import availability, pvtask
 from ..i18n import N_
 from ..size import Size
-from ..errors import PhysicalVolumeError
+from ..errors import DeviceFormatError, PhysicalVolumeError
 from . import DeviceFormat, register_device_format
 from .. import udev
 from ..static_data.lvm_info import pvs_info, vgs_info
@@ -98,6 +98,9 @@ class LVMPhysicalVolume(DeviceFormat):
 
         self.inconsistent_vg = False
 
+        # when set to True, blivet will try to resize the PV to fill all available space
+        self._grow_to_fill = False
+
     def __repr__(self):
         s = DeviceFormat.__repr__(self)
         s += ("  vg_name = %(vg_name)s  vg_uuid = %(vg_uuid)s"
@@ -106,6 +109,24 @@ class LVMPhysicalVolume(DeviceFormat):
                "pe_start": self.pe_start, "data_alignment": self.data_alignment})
         return s
 
+    @property
+    def grow_to_fill(self):
+        """
+            Can be set to True to mark format for resize so it matches size of its device.
+            (Main usecase is disk size increase on VM)
+            Uses blockdev/lvm for exact new size calculation.
+            ActionResizeFormat has to be executed to apply the change.
+            Format has to be resizable (i.e. run format.update_size_info() first) to allow this.
+        """
+        return self._grow_to_fill
+
+    @grow_to_fill.setter
+    def grow_to_fill(self, fill: bool):
+        if fill is True:
+            if not self.resizable:
+                raise DeviceFormatError("format is not resizable")
+        self._grow_to_fill = fill
+
     @property
     def dict(self):
         d = super(LVMPhysicalVolume, self).dict
diff --git a/blivet/tasks/pvtask.py b/blivet/tasks/pvtask.py
index 04c8a4d1..b5bd72e0 100644
--- a/blivet/tasks/pvtask.py
+++ b/blivet/tasks/pvtask.py
@@ -82,6 +82,11 @@ class PVResize(task.BasicApplication, dfresize.DFResizeTask):
     def do_task(self):  # pylint: disable=arguments-differ
         """ Resizes the LVMPV format. """
         try:
-            blockdev.lvm.pvresize(self.pv.device, self.pv.target_size.convert_to(self.unit))
+            if self.pv.grow_to_fill:
+                # resize PV to fill all available space on device by omitting
+                # the size parameter
+                blockdev.lvm.pvresize(self.pv.device, 0)
+            else:
+                blockdev.lvm.pvresize(self.pv.device, self.pv.target_size.convert_to(self.unit))
         except blockdev.LVMError as e:
             raise PhysicalVolumeError(e)
diff --git a/tests/storage_tests/formats_test/lvmpv_test.py b/tests/storage_tests/formats_test/lvmpv_test.py
index cdc33ec4..d2811f3e 100644
--- a/tests/storage_tests/formats_test/lvmpv_test.py
+++ b/tests/storage_tests/formats_test/lvmpv_test.py
@@ -37,6 +37,9 @@ class LVMPVTestCase(loopbackedtestcase.LoopBackedTestCase):
         self.fmt.update_size_info()
         self.assertTrue(self.fmt.resizable)
 
+        # save the pv maximum size
+        maxpvsize = self.fmt.current_size
+
         # resize the format
         new_size = Size("50 MiB")
         self.fmt.target_size = new_size
@@ -46,5 +49,12 @@ class LVMPVTestCase(loopbackedtestcase.LoopBackedTestCase):
         self.fmt.update_size_info()
         self.assertEqual(self.fmt.current_size, new_size)
 
+        # Test growing PV to fill all available space on the device
+        self.fmt.grow_to_fill = True
+        self.fmt.do_resize()
+
+        self.fmt.update_size_info()
+        self.assertEqual(self.fmt.current_size, maxpvsize)
+
     def _pvremove(self):
         self.fmt._destroy()
-- 
2.45.0