neil / rpms / python-blivet

Forked from rpms/python-blivet a year ago
Clone
f01d01
From 433d863cd8a57e5fc30948ff905e6a477ed5f17c Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Tue, 14 Jul 2020 11:27:08 +0200
f01d01
Subject: [PATCH 1/4] Add support for XFS format grow
f01d01
f01d01
---
f01d01
 blivet/formats/fs.py         |  2 ++
f01d01
 blivet/tasks/availability.py |  1 +
f01d01
 blivet/tasks/fsresize.py     | 54 ++++++++++++++++++++++++++++++++++++
f01d01
 3 files changed, 57 insertions(+)
f01d01
f01d01
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
f01d01
index eee15aaa..12cb9885 100644
f01d01
--- a/blivet/formats/fs.py
f01d01
+++ b/blivet/formats/fs.py
f01d01
@@ -1089,11 +1089,13 @@ class XFS(FS):
f01d01
     _formattable = True
f01d01
     _linux_native = True
f01d01
     _supported = True
f01d01
+    _resizable = True
f01d01
     _packages = ["xfsprogs"]
f01d01
     _info_class = fsinfo.XFSInfo
f01d01
     _mkfs_class = fsmkfs.XFSMkfs
f01d01
     _readlabel_class = fsreadlabel.XFSReadLabel
f01d01
     _size_info_class = fssize.XFSSize
f01d01
+    _resize_class = fsresize.XFSResize
f01d01
     _sync_class = fssync.XFSSync
f01d01
     _writelabel_class = fswritelabel.XFSWriteLabel
f01d01
     _writeuuid_class = fswriteuuid.XFSWriteUUID
f01d01
diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py
f01d01
index b6b5955a..df62780c 100644
f01d01
--- a/blivet/tasks/availability.py
f01d01
+++ b/blivet/tasks/availability.py
f01d01
@@ -455,5 +455,6 @@ TUNE2FS_APP = application_by_version("tune2fs", E2FSPROGS_VERSION)
f01d01
 XFSADMIN_APP = application("xfs_admin")
f01d01
 XFSDB_APP = application("xfs_db")
f01d01
 XFSFREEZE_APP = application("xfs_freeze")
f01d01
+XFSRESIZE_APP = application("xfs_growfs")
f01d01
 
f01d01
 MOUNT_APP = application("mount")
f01d01
diff --git a/blivet/tasks/fsresize.py b/blivet/tasks/fsresize.py
f01d01
index e7e26984..12c0367f 100644
f01d01
--- a/blivet/tasks/fsresize.py
f01d01
+++ b/blivet/tasks/fsresize.py
f01d01
@@ -20,7 +20,10 @@
f01d01
 # Red Hat Author(s): Anne Mulhern <amulhern@redhat.com>
f01d01
 
f01d01
 import abc
f01d01
+import os
f01d01
+import tempfile
f01d01
 
f01d01
+from contextlib import contextmanager
f01d01
 from six import add_metaclass
f01d01
 
f01d01
 from ..errors import FSError
f01d01
@@ -32,6 +35,9 @@ from . import task
f01d01
 from . import fstask
f01d01
 from . import dfresize
f01d01
 
f01d01
+import logging
f01d01
+log = logging.getLogger("blivet")
f01d01
+
f01d01
 
f01d01
 @add_metaclass(abc.ABCMeta)
f01d01
 class FSResizeTask(fstask.FSTask):
f01d01
@@ -115,6 +121,54 @@ class NTFSResize(FSResize):
f01d01
         ]
f01d01
 
f01d01
 
f01d01
+class XFSResize(FSResize):
f01d01
+    ext = availability.XFSRESIZE_APP
f01d01
+    unit = B
f01d01
+    size_fmt = None
f01d01
+
f01d01
+    @contextmanager
f01d01
+    def _do_temp_mount(self):
f01d01
+        if self.fs.status:
f01d01
+            yield
f01d01
+        else:
f01d01
+            dev_name = os.path.basename(self.fs.device)
f01d01
+            tmpdir = tempfile.mkdtemp(prefix="xfs-tempmount-%s" % dev_name)
f01d01
+            log.debug("mounting XFS on '%s' to '%s' for resize", self.fs.device, tmpdir)
f01d01
+            try:
f01d01
+                self.fs.mount(mountpoint=tmpdir)
f01d01
+            except FSError as e:
f01d01
+                raise FSError("Failed to mount XFS filesystem for resize: %s" % str(e))
f01d01
+
f01d01
+            try:
f01d01
+                yield
f01d01
+            finally:
f01d01
+                util.umount(mountpoint=tmpdir)
f01d01
+                os.rmdir(tmpdir)
f01d01
+
f01d01
+    def _get_block_size(self):
f01d01
+        if self.fs._current_info:
f01d01
+            # this should be set by update_size_info()
f01d01
+            for line in self.fs._current_info.split("\n"):
f01d01
+                if line.startswith("blocksize ="):
f01d01
+                    return int(line.split("=")[-1])
f01d01
+
f01d01
+        raise FSError("Failed to get XFS filesystem block size for resize")
f01d01
+
f01d01
+    def size_spec(self):
f01d01
+        # size for xfs_growfs is in blocks
f01d01
+        return str(self.fs.target_size.convert_to(self.unit) / self._get_block_size())
f01d01
+
f01d01
+    @property
f01d01
+    def args(self):
f01d01
+        return [self.fs.system_mountpoint, "-D", self.size_spec()]
f01d01
+
f01d01
+    def do_task(self):
f01d01
+        """ Resizes the XFS format. """
f01d01
+
f01d01
+        with self._do_temp_mount():
f01d01
+            super(XFSResize, self).do_task()
f01d01
+
f01d01
+
f01d01
 class TmpFSResize(FSResize):
f01d01
 
f01d01
     ext = availability.MOUNT_APP
f01d01
-- 
f01d01
2.26.2
f01d01
f01d01
f01d01
From 56d05334231c30699a9c77dedbc23fdb021b9dee Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Tue, 14 Jul 2020 11:27:51 +0200
f01d01
Subject: [PATCH 2/4] Add tests for XFS resize
f01d01
f01d01
XFS supports only grow so we can't reuse most of the fstesting
f01d01
code and we also need to test the resize on partition because
f01d01
XFS won't allow grow to size bigger than the underlying block
f01d01
device.
f01d01
---
f01d01
 tests/formats_test/fs_test.py   | 91 +++++++++++++++++++++++++++++++++
f01d01
 tests/formats_test/fstesting.py | 33 ++++++------
f01d01
 2 files changed, 107 insertions(+), 17 deletions(-)
f01d01
f01d01
diff --git a/tests/formats_test/fs_test.py b/tests/formats_test/fs_test.py
f01d01
index 15fc0c35..9bc5d20d 100644
f01d01
--- a/tests/formats_test/fs_test.py
f01d01
+++ b/tests/formats_test/fs_test.py
f01d01
@@ -2,8 +2,13 @@ import os
f01d01
 import tempfile
f01d01
 import unittest
f01d01
 
f01d01
+import parted
f01d01
+
f01d01
 import blivet.formats.fs as fs
f01d01
 from blivet.size import Size, ROUND_DOWN
f01d01
+from blivet.errors import DeviceFormatError
f01d01
+from blivet.formats import get_format
f01d01
+from blivet.devices import PartitionDevice, DiskDevice
f01d01
 
f01d01
 from tests import loopbackedtestcase
f01d01
 
f01d01
@@ -50,6 +55,92 @@ class ReiserFSTestCase(fstesting.FSAsRoot):
f01d01
 class XFSTestCase(fstesting.FSAsRoot):
f01d01
     _fs_class = fs.XFS
f01d01
 
f01d01
+    def can_resize(self, an_fs):
f01d01
+        resize_tasks = (an_fs._resize, an_fs._size_info)
f01d01
+        return not any(t.availability_errors for t in resize_tasks)
f01d01
+
f01d01
+    def _create_partition(self, disk, size):
f01d01
+        disk.format = get_format("disklabel", device=disk.path, label_type="msdos")
f01d01
+        disk.format.create()
f01d01
+        pstart = disk.format.alignment.grainSize
f01d01
+        pend = pstart + int(Size(size) / disk.format.parted_device.sectorSize)
f01d01
+        disk.format.add_partition(pstart, pend, parted.PARTITION_NORMAL)
f01d01
+        disk.format.parted_disk.commit()
f01d01
+        part = disk.format.parted_disk.getPartitionBySector(pstart)
f01d01
+
f01d01
+        device = PartitionDevice(os.path.basename(part.path))
f01d01
+        device.disk = disk
f01d01
+        device.exists = True
f01d01
+        device.parted_partition = part
f01d01
+
f01d01
+        return device
f01d01
+
f01d01
+    def _remove_partition(self, partition, disk):
f01d01
+        disk.format.remove_partition(partition.parted_partition)
f01d01
+        disk.format.parted_disk.commit()
f01d01
+
f01d01
+    def test_resize(self):
f01d01
+        an_fs = self._fs_class()
f01d01
+        if not an_fs.formattable:
f01d01
+            self.skipTest("can not create filesystem %s" % an_fs.name)
f01d01
+        an_fs.device = self.loop_devices[0]
f01d01
+        self.assertIsNone(an_fs.create())
f01d01
+        an_fs.update_size_info()
f01d01
+
f01d01
+        self._test_sizes(an_fs)
f01d01
+        # CHECKME: target size is still 0 after updated_size_info is called.
f01d01
+        self.assertEqual(an_fs.size, Size(0) if an_fs.resizable else an_fs._size)
f01d01
+
f01d01
+        if not self.can_resize(an_fs):
f01d01
+            self.assertFalse(an_fs.resizable)
f01d01
+            # Not resizable, so can not do resizing actions.
f01d01
+            with self.assertRaises(DeviceFormatError):
f01d01
+                an_fs.target_size = Size("64 MiB")
f01d01
+            with self.assertRaises(DeviceFormatError):
f01d01
+                an_fs.do_resize()
f01d01
+        else:
f01d01
+            disk = DiskDevice(os.path.basename(self.loop_devices[0]))
f01d01
+            part = self._create_partition(disk, Size("50 MiB"))
f01d01
+            an_fs = self._fs_class()
f01d01
+            an_fs.device = part.path
f01d01
+            self.assertIsNone(an_fs.create())
f01d01
+            an_fs.update_size_info()
f01d01
+
f01d01
+            self.assertTrue(an_fs.resizable)
f01d01
+
f01d01
+            # grow the partition so we can grow the filesystem
f01d01
+            self._remove_partition(part, disk)
f01d01
+            part = self._create_partition(disk, size=part.size + Size("40 MiB"))
f01d01
+
f01d01
+            # Try a reasonable target size
f01d01
+            TARGET_SIZE = Size("64 MiB")
f01d01
+            an_fs.target_size = TARGET_SIZE
f01d01
+            self.assertEqual(an_fs.target_size, TARGET_SIZE)
f01d01
+            self.assertNotEqual(an_fs._size, TARGET_SIZE)
f01d01
+            self.assertIsNone(an_fs.do_resize())
f01d01
+            ACTUAL_SIZE = TARGET_SIZE.round_to_nearest(an_fs._resize.unit, rounding=ROUND_DOWN)
f01d01
+            self.assertEqual(an_fs.size, ACTUAL_SIZE)
f01d01
+            self.assertEqual(an_fs._size, ACTUAL_SIZE)
f01d01
+            self._test_sizes(an_fs)
f01d01
+
f01d01
+            self._remove_partition(part, disk)
f01d01
+
f01d01
+        # and no errors should occur when checking
f01d01
+        self.assertIsNone(an_fs.do_check())
f01d01
+
f01d01
+    def test_shrink(self):
f01d01
+        self.skipTest("Not checking resize for this test category.")
f01d01
+
f01d01
+    def test_too_small(self):
f01d01
+        self.skipTest("Not checking resize for this test category.")
f01d01
+
f01d01
+    def test_no_explicit_target_size2(self):
f01d01
+        self.skipTest("Not checking resize for this test category.")
f01d01
+
f01d01
+    def test_too_big2(self):
f01d01
+        # XXX this tests assumes that resizing to max size - 1 B will fail, but xfs_grow won't
f01d01
+        self.skipTest("Not checking resize for this test category.")
f01d01
+
f01d01
 
f01d01
 class HFSTestCase(fstesting.FSAsRoot):
f01d01
     _fs_class = fs.HFS
f01d01
diff --git a/tests/formats_test/fstesting.py b/tests/formats_test/fstesting.py
f01d01
index 62f806f9..86b2a116 100644
f01d01
--- a/tests/formats_test/fstesting.py
f01d01
+++ b/tests/formats_test/fstesting.py
f01d01
@@ -11,16 +11,6 @@ from blivet.size import Size, ROUND_DOWN
f01d01
 from blivet.formats import fs
f01d01
 
f01d01
 
f01d01
-def can_resize(an_fs):
f01d01
-    """ Returns True if this filesystem has all necessary resizing tools
f01d01
-        available.
f01d01
-
f01d01
-        :param an_fs: a filesystem object
f01d01
-    """
f01d01
-    resize_tasks = (an_fs._resize, an_fs._size_info, an_fs._minsize)
f01d01
-    return not any(t.availability_errors for t in resize_tasks)
f01d01
-
f01d01
-
f01d01
 @add_metaclass(abc.ABCMeta)
f01d01
 class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
f01d01
 
f01d01
@@ -32,6 +22,15 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
f01d01
     def __init__(self, methodName='run_test'):
f01d01
         super(FSAsRoot, self).__init__(methodName=methodName, device_spec=[self._DEVICE_SIZE])
f01d01
 
f01d01
+    def can_resize(self, an_fs):
f01d01
+        """ Returns True if this filesystem has all necessary resizing tools
f01d01
+            available.
f01d01
+
f01d01
+            :param an_fs: a filesystem object
f01d01
+        """
f01d01
+        resize_tasks = (an_fs._resize, an_fs._size_info, an_fs._minsize)
f01d01
+        return not any(t.availability_errors for t in resize_tasks)
f01d01
+
f01d01
     def _test_sizes(self, an_fs):
f01d01
         """ Test relationships between different size values.
f01d01
 
f01d01
@@ -190,7 +189,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
f01d01
         # CHECKME: target size is still 0 after updated_size_info is called.
f01d01
         self.assertEqual(an_fs.size, Size(0) if an_fs.resizable else an_fs._size)
f01d01
 
f01d01
-        if not can_resize(an_fs):
f01d01
+        if not self.can_resize(an_fs):
f01d01
             self.assertFalse(an_fs.resizable)
f01d01
             # Not resizable, so can not do resizing actions.
f01d01
             with self.assertRaises(DeviceFormatError):
f01d01
@@ -221,7 +220,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
f01d01
         # in constructor call behavior would be different.
f01d01
 
f01d01
         an_fs = self._fs_class()
f01d01
-        if not can_resize(an_fs):
f01d01
+        if not self.can_resize(an_fs):
f01d01
             self.skipTest("Not checking resize for this test category.")
f01d01
         if not an_fs.formattable:
f01d01
             self.skipTest("can not create filesystem %s" % an_fs.name)
f01d01
@@ -244,7 +243,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
f01d01
         """
f01d01
         SIZE = Size("64 MiB")
f01d01
         an_fs = self._fs_class(size=SIZE)
f01d01
-        if not can_resize(an_fs):
f01d01
+        if not self.can_resize(an_fs):
f01d01
             self.skipTest("Not checking resize for this test category.")
f01d01
         if not an_fs.formattable:
f01d01
             self.skipTest("can not create filesystem %s" % an_fs.name)
f01d01
@@ -264,7 +263,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
f01d01
 
f01d01
     def test_shrink(self):
f01d01
         an_fs = self._fs_class()
f01d01
-        if not can_resize(an_fs):
f01d01
+        if not self.can_resize(an_fs):
f01d01
             self.skipTest("Not checking resize for this test category.")
f01d01
         if not an_fs.formattable:
f01d01
             self.skipTest("can not create filesystem %s" % an_fs.name)
f01d01
@@ -296,7 +295,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
f01d01
 
f01d01
     def test_too_small(self):
f01d01
         an_fs = self._fs_class()
f01d01
-        if not can_resize(an_fs):
f01d01
+        if not self.can_resize(an_fs):
f01d01
             self.skipTest("Not checking resize for this test category.")
f01d01
         if not an_fs.formattable:
f01d01
             self.skipTest("can not create or resize filesystem %s" % an_fs.name)
f01d01
@@ -315,7 +314,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
f01d01
 
f01d01
     def test_too_big(self):
f01d01
         an_fs = self._fs_class()
f01d01
-        if not can_resize(an_fs):
f01d01
+        if not self.can_resize(an_fs):
f01d01
             self.skipTest("Not checking resize for this test category.")
f01d01
         if not an_fs.formattable:
f01d01
             self.skipTest("can not create filesystem %s" % an_fs.name)
f01d01
@@ -334,7 +333,7 @@ class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
f01d01
 
f01d01
     def test_too_big2(self):
f01d01
         an_fs = self._fs_class()
f01d01
-        if not can_resize(an_fs):
f01d01
+        if not self.can_resize(an_fs):
f01d01
             self.skipTest("Not checking resize for this test category.")
f01d01
         if not an_fs.formattable:
f01d01
             self.skipTest("can not create filesystem %s" % an_fs.name)
f01d01
-- 
f01d01
2.26.2
f01d01
f01d01
f01d01
From 51acc04f4639f143b55789a06a68aae988a91296 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Wed, 15 Jul 2020 12:59:04 +0200
f01d01
Subject: [PATCH 3/4] Add support for checking and fixing XFS using xfs_repair
f01d01
f01d01
---
f01d01
 blivet/formats/fs.py          |  1 +
f01d01
 blivet/tasks/availability.py  |  1 +
f01d01
 blivet/tasks/fsck.py          | 12 ++++++++++++
f01d01
 tests/formats_test/fs_test.py |  6 +++---
f01d01
 4 files changed, 17 insertions(+), 3 deletions(-)
f01d01
f01d01
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
f01d01
index 12cb9885..06fbdf10 100644
f01d01
--- a/blivet/formats/fs.py
f01d01
+++ b/blivet/formats/fs.py
f01d01
@@ -1091,6 +1091,7 @@ class XFS(FS):
f01d01
     _supported = True
f01d01
     _resizable = True
f01d01
     _packages = ["xfsprogs"]
f01d01
+    _fsck_class = fsck.XFSCK
f01d01
     _info_class = fsinfo.XFSInfo
f01d01
     _mkfs_class = fsmkfs.XFSMkfs
f01d01
     _readlabel_class = fsreadlabel.XFSReadLabel
f01d01
diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py
f01d01
index df62780c..f3b76650 100644
f01d01
--- a/blivet/tasks/availability.py
f01d01
+++ b/blivet/tasks/availability.py
f01d01
@@ -456,5 +456,6 @@ XFSADMIN_APP = application("xfs_admin")
f01d01
 XFSDB_APP = application("xfs_db")
f01d01
 XFSFREEZE_APP = application("xfs_freeze")
f01d01
 XFSRESIZE_APP = application("xfs_growfs")
f01d01
+XFSREPAIR_APP = application("xfs_repair")
f01d01
 
f01d01
 MOUNT_APP = application("mount")
f01d01
diff --git a/blivet/tasks/fsck.py b/blivet/tasks/fsck.py
f01d01
index 5274f13a..8477f5f8 100644
f01d01
--- a/blivet/tasks/fsck.py
f01d01
+++ b/blivet/tasks/fsck.py
f01d01
@@ -123,6 +123,18 @@ class Ext2FSCK(FSCK):
f01d01
         return "\n".join(msgs) or None
f01d01
 
f01d01
 
f01d01
+class XFSCK(FSCK):
f01d01
+    _fsck_errors = {1: "Runtime error encountered during repair operation.",
f01d01
+                    2: "XFS repair was unable to proceed due to a dirty log."}
f01d01
+
f01d01
+    ext = availability.XFSREPAIR_APP
f01d01
+    options = []
f01d01
+
f01d01
+    def _error_message(self, rc):
f01d01
+        msgs = (self._fsck_errors[c] for c in self._fsck_errors.keys() if rc & c)
f01d01
+        return "\n".join(msgs) or None
f01d01
+
f01d01
+
f01d01
 class HFSPlusFSCK(FSCK):
f01d01
     _fsck_errors = {3: "Quick check found a dirty filesystem; no repairs done.",
f01d01
                     4: "Root filesystem was dirty. System should be rebooted.",
f01d01
diff --git a/tests/formats_test/fs_test.py b/tests/formats_test/fs_test.py
f01d01
index 9bc5d20d..8fb099fd 100644
f01d01
--- a/tests/formats_test/fs_test.py
f01d01
+++ b/tests/formats_test/fs_test.py
f01d01
@@ -123,10 +123,10 @@ class XFSTestCase(fstesting.FSAsRoot):
f01d01
             self.assertEqual(an_fs._size, ACTUAL_SIZE)
f01d01
             self._test_sizes(an_fs)
f01d01
 
f01d01
-            self._remove_partition(part, disk)
f01d01
+            # and no errors should occur when checking
f01d01
+            self.assertIsNone(an_fs.do_check())
f01d01
 
f01d01
-        # and no errors should occur when checking
f01d01
-        self.assertIsNone(an_fs.do_check())
f01d01
+            self._remove_partition(part, disk)
f01d01
 
f01d01
     def test_shrink(self):
f01d01
         self.skipTest("Not checking resize for this test category.")
f01d01
-- 
f01d01
2.26.2
f01d01
f01d01
f01d01
From 2a6947098e66f880193f3bac2282a6c7857ca5f7 Mon Sep 17 00:00:00 2001
f01d01
From: Vojtech Trefny <vtrefny@redhat.com>
f01d01
Date: Thu, 16 Jul 2020 09:05:35 +0200
f01d01
Subject: [PATCH 4/4] Use xfs_db in read-only mode when getting XFS information
f01d01
f01d01
This way it will also work on mounted filesystems.
f01d01
---
f01d01
 blivet/tasks/fsinfo.py | 2 +-
f01d01
 1 file changed, 1 insertion(+), 1 deletion(-)
f01d01
f01d01
diff --git a/blivet/tasks/fsinfo.py b/blivet/tasks/fsinfo.py
f01d01
index af208f5d..41ff700f 100644
f01d01
--- a/blivet/tasks/fsinfo.py
f01d01
+++ b/blivet/tasks/fsinfo.py
f01d01
@@ -95,7 +95,7 @@ class ReiserFSInfo(FSInfo):
f01d01
 
f01d01
 class XFSInfo(FSInfo):
f01d01
     ext = availability.XFSDB_APP
f01d01
-    options = ["-c", "sb 0", "-c", "p dblocks", "-c", "p blocksize"]
f01d01
+    options = ["-c", "sb 0", "-c", "p dblocks", "-c", "p blocksize", "-r"]
f01d01
 
f01d01
 
f01d01
 class UnimplementedFSInfo(fstask.UnimplementedFSTask):
f01d01
-- 
f01d01
2.26.2
f01d01