From: mulhern amulhern@redhat.com
Related: #56
Use similar formula to setup, teardown, etc. methods.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/deviceaction.py | 2 +- blivet/formats/__init__.py | 88 ++++++++++++++++++++++++++++++++++++++++- blivet/formats/fs.py | 81 +++++++++---------------------------- blivet/tasks/fsresize.py | 2 +- tests/formats_test/fs_test.py | 2 +- tests/formats_test/fstesting.py | 20 +++++----- 6 files changed, 118 insertions(+), 77 deletions(-)
diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py index 4fd6df2..cbfbd1f 100644 --- a/blivet/deviceaction.py +++ b/blivet/deviceaction.py @@ -778,7 +778,7 @@ def execute(self, callbacks=None): callbacks.resize_format_pre(ResizeFormatPreData(msg))
self.device.setup(orig=True) - self.device.format.doResize() + self.device.format.resize()
if callbacks and callbacks.resize_format_post: msg = _("Resized filesystem on %(device)s") % {"device": self.device.path} diff --git a/blivet/formats/__init__.py b/blivet/formats/__init__.py index e57cf20..7fa25cb 100644 --- a/blivet/formats/__init__.py +++ b/blivet/formats/__init__.py @@ -32,7 +32,8 @@ from ..storage_log import log_method_call from ..errors import DeviceFormatError, FormatCreateError, FormatDestroyError, FormatSetupError from ..i18n import N_ -from ..size import Size +from ..size import Size, unitStr, ROUND_DOWN +from ..tasks import dfresize
import logging log = logging.getLogger("blivet") @@ -160,6 +161,7 @@ class DeviceFormat(ObjectID): _check = False _hidden = False # hide devices with this formatting? _ksMountpoint = None + _resizeClass = dfresize.UnimplementedDFResize
def __init__(self, **kwargs): """ @@ -179,6 +181,9 @@ def __init__(self, **kwargs): it via the 'device' kwarg to the :meth:`create` method. """ ObjectID.__init__(self) + + self._resizeTask = self._resizeClass(self) + self._label = None self._options = None self._device = None @@ -584,6 +589,85 @@ def _teardown(self, **kwargs): def _postTeardown(self, **kwargs): pass
+ def resize(self, **kwargs): + log_method_call(self, device=self.device, + type=self.type, status=self.status) + if not self._preResize(**kwargs): + return + + self._resize(**kwargs) + self._postResize(**kwargs) + + def _resize(self, **kwargs): + """ Do generic resizing actions. """ + # Bump target size to nearest whole number of the resize tool's units. + # We always round down because the fs has to fit on whatever device + # contains it. To round up would risk quietly setting a target size too + # large for the device to hold. + rounded = self.targetSize.roundToNearest(self._resizeTask.unit, + rounding=ROUND_DOWN) + + # 1. target size was between the min size and max size values prior to + # rounding (see _setTargetSize) + # 2. we've just rounded the target size down (or not at all) + # 3. the minimum size is already either rounded (see _getMinSize) or is + # equal to the current size (see updateSizeInfo) + # 5. the minimum size is less than or equal to the current size (see + # _getMinSize) + # + # This, I think, is sufficient to guarantee that the rounded target size + # is greater than or equal to the minimum size. + + # It is possible that rounding down a target size greater than the + # current size would move it below the current size, thus changing the + # direction of the resize. That means the target size was less than one + # unit larger than the current size, and we should do nothing and return + # early. + if self.targetSize > self.currentSize and rounded < self.currentSize: + log.info("rounding target size down to next %s obviated resize of " + "format on %s", unitStr(self._resizeTask.unit), self.device) + return + else: + self.targetSize = rounded + + self._resizeTask.doTask() + + def _deviceRequiredForResize(self): + """ Does this format need a device to be resized. + + :rtype: bool + :returns: True if this format needs a device, otherwise False + """ + return True + + def _preResize(self, **kwargs): + """ Do all preparatory checks for resizing a format. + + :rtype: bool + :returns: True if resize should proceed, otherwise False + """ + if not self.exists: + raise DeviceFormatError("format has not been created") + + if not self.resizable: + raise DeviceFormatError("format is not resizable") + + if self.targetSize == self.currentSize: + return False + + if not self._resizeTask.available: + return False + + if self._deviceRequiredForResize() and not os.path.exists(self.device): + raise DeviceFormatError("device %s does not exist" % self.device) + + return True + + def _postResize(self, **kwargs): + """ Do what must be done after resizing a format. """ + self._size = self.targetSize + self.notifyKernel() + @property def status(self): return (self.exists and @@ -615,7 +699,7 @@ def packages(self): @property def resizable(self): """ Can formats of this type be resized? """ - return self._resizable and self.exists + return self._resizable and self.exists and self._resizeTask.available
@property def linuxNative(self): diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py index c63f1f4..f4547ca 100644 --- a/blivet/formats/fs.py +++ b/blivet/formats/fs.py @@ -46,7 +46,7 @@ from parted import fileSystemType from ..storage_log import log_exception_info, log_method_call from .. import arch -from ..size import Size, ROUND_UP, ROUND_DOWN, unitStr +from ..size import Size, ROUND_UP from ..i18n import N_ from .. import udev from ..mounts import mountsCache @@ -106,7 +106,6 @@ def __init__(self, **kwargs): self._mkfs = self._mkfsClass(self) self._mount = self._mountClass(self) self._readlabel = self._readlabelClass(self) - self._resizeTask = self._resizeClass(self) self._sync = self._syncClass(self) self._writelabel = self._writelabelClass(self)
@@ -330,23 +329,19 @@ def _postCreate(self, **kwargs): def doResize(self): """ Resize this filesystem based on this instance's targetSize attr.
- :raises: FSResizeError, FSError - """ - if not self.exists: - raise FSResizeError("filesystem does not exist", self.device) - - if not self.resizable: - raise FSResizeError("filesystem not resizable", self.device) + :raises: FSResizeError, FSError, DeviceFormatError
- if self.targetSize == self.currentSize: - return + .. versionchanged:: 1.5 + Raises :class:`~.errors.DeviceFormatError`
- if not self._resizeTask.available: - return + .. deprecated:: 1.5 + Use :func:`resize` instead. + """ + self.resize()
- # tmpfs mounts don't need an existing device node - if not self.device == "tmpfs" and not os.path.exists(self.device): - raise FSResizeError("device does not exist", self.device) + def _preResize(self, **kwargs): + if not super(FS, self)._preResize(**kwargs): + return False
# The first minimum size can be incorrect if the fs was not # properly unmounted. After doCheck the minimum size will be correct @@ -362,47 +357,11 @@ def doResize(self): self.targetSize = self.minSize log.info("Minimum size changed, setting targetSize on %s to %s", self.device, self.targetSize) + return True
- # Bump target size to nearest whole number of the resize tool's units. - # We always round down because the fs has to fit on whatever device - # contains it. To round up would risk quietly setting a target size too - # large for the device to hold. - rounded = self.targetSize.roundToNearest(self._resizeTask.unit, - rounding=ROUND_DOWN) - - # 1. target size was between the min size and max size values prior to - # rounding (see _setTargetSize) - # 2. we've just rounded the target size down (or not at all) - # 3. the minimum size is already either rounded (see _getMinSize) or is - # equal to the current size (see updateSizeInfo) - # 5. the minimum size is less than or equal to the current size (see - # _getMinSize) - # - # This, I think, is sufficient to guarantee that the rounded target size - # is greater than or equal to the minimum size. - - # It is possible that rounding down a target size greater than the - # current size would move it below the current size, thus changing the - # direction of the resize. That means the target size was less than one - # unit larger than the current size, and we should do nothing and return - # early. - if self.targetSize > self.currentSize and rounded < self.currentSize: - log.info("rounding target size down to next %s obviated resize of " - "filesystem on %s", unitStr(self._resizeTask.unit), self.device) - return - else: - self.targetSize = rounded - - try: - self._resizeTask.doTask() - except FSError as e: - raise FSResizeError(e, self.device) - + def _postResize(self, **kwargs): self.doCheck() - - # XXX must be a smarter way to do this - self._size = self.targetSize - self.notifyKernel() + super(FS, self)._postResize(**kwargs)
def doCheck(self): """ Run a filesystem check. @@ -659,11 +618,6 @@ def mountable(self): def formattable(self): return super(FS, self).formattable and self._mkfs.available
- @property - def resizable(self): - """ Can formats of this filesystem type be resized? """ - return super(FS, self).resizable and self._resizeTask.available - def _getOptions(self): return self.mountopts or ",".join(self._mount.options)
@@ -1224,13 +1178,16 @@ def _setDevice(self, value): # same, nothing actually needs to be set pass
- def doResize(self): + def _resize(self, **kwargs): # Override superclass method to record whether mount options # should include an explicit size specification. original_size = self._size - FS.doResize(self) + super(TmpFS, self)._resize(**kwargs) self._accept_default_size = self._accept_default_size and original_size == self._size
+ def _deviceRequiredForResize(self): + return False + register_device_format(TmpFS)
diff --git a/blivet/tasks/fsresize.py b/blivet/tasks/fsresize.py index c6db378..ee3238c 100644 --- a/blivet/tasks/fsresize.py +++ b/blivet/tasks/fsresize.py @@ -80,7 +80,7 @@ def doTask(self): raise FSError(e)
if ret: - raise FSError("resize failed: %s" % ret) + raise FSError("resize of format on device %s failed: %s" % (self.fs.device, ret))
class Ext2FSResize(FSResize): ext = availability.RESIZE2FS_APP diff --git a/tests/formats_test/fs_test.py b/tests/formats_test/fs_test.py index e981408..8a4c074 100644 --- a/tests/formats_test/fs_test.py +++ b/tests/formats_test/fs_test.py @@ -121,7 +121,7 @@ def testResize(self): self.an_fs.updateSizeInfo() newsize = self.an_fs.currentSize * 2 self.an_fs.targetSize = newsize - self.assertIsNone(self.an_fs.doResize()) + self.assertIsNone(self.an_fs.resize()) self.assertEqual(self.an_fs.size, newsize.roundToNearest(self.an_fs._resizeTask.unit, rounding=ROUND_DOWN))
def testShrink(self): diff --git a/tests/formats_test/fstesting.py b/tests/formats_test/fstesting.py index bf654f8..03eb156 100644 --- a/tests/formats_test/fstesting.py +++ b/tests/formats_test/fstesting.py @@ -6,7 +6,7 @@ import tempfile
from tests import loopbackedtestcase -from blivet.errors import DeviceFormatError, FSError, FSResizeError +from blivet.errors import DeviceFormatError, FSError from blivet.size import Size, ROUND_DOWN from blivet.formats import fs
@@ -200,7 +200,7 @@ def testResize(self): with self.assertRaises(DeviceFormatError): an_fs.targetSize = Size("64 MiB") with self.assertRaises(DeviceFormatError): - an_fs.doResize() + an_fs.resize() else: self.assertTrue(an_fs.resizable) # Try a reasonable target size @@ -208,7 +208,7 @@ def testResize(self): an_fs.targetSize = TARGET_SIZE self.assertEqual(an_fs.targetSize, TARGET_SIZE) self.assertNotEqual(an_fs._size, TARGET_SIZE) - self.assertIsNone(an_fs.doResize()) + self.assertIsNone(an_fs.resize()) ACTUAL_SIZE = TARGET_SIZE.roundToNearest(an_fs._resizeTask.unit, rounding=ROUND_DOWN) self.assertEqual(an_fs.size, ACTUAL_SIZE) self.assertEqual(an_fs._size, ACTUAL_SIZE) @@ -237,7 +237,7 @@ def testNoExplicitTargetSize(self): self.assertNotEqual(an_fs.currentSize, an_fs.targetSize) self.assertEqual(an_fs.currentSize, an_fs._size) self.assertEqual(an_fs.targetSize, Size(0)) - self.assertIsNone(an_fs.doResize()) + self.assertIsNone(an_fs.resize()) self.assertEqual(an_fs.currentSize, an_fs.minSize) self.assertEqual(an_fs.targetSize, an_fs.minSize) self._test_sizes(an_fs) @@ -261,7 +261,7 @@ def testNoExplicitTargetSize2(self): # set in the constructor. self.assertNotEqual(an_fs.currentSize, an_fs.targetSize) self.assertEqual(an_fs.targetSize, SIZE) - self.assertIsNone(an_fs.doResize()) + self.assertIsNone(an_fs.resize()) self.assertEqual(an_fs.currentSize, SIZE) self.assertEqual(an_fs.targetSize, SIZE) self._test_sizes(an_fs) @@ -279,7 +279,7 @@ def testShrink(self):
TARGET_SIZE = Size("64 MiB") an_fs.targetSize = TARGET_SIZE - self.assertIsNone(an_fs.doResize()) + self.assertIsNone(an_fs.resize())
TARGET_SIZE = TARGET_SIZE / 2 self.assertTrue(TARGET_SIZE > an_fs.minSize) @@ -287,13 +287,13 @@ def testShrink(self): self.assertEqual(an_fs.targetSize, TARGET_SIZE) self.assertNotEqual(an_fs._size, TARGET_SIZE) # FIXME: - # doCheck() in updateSizeInfo() in doResize() does not complete tidily + # doCheck() in updateSizeInfo() in resize() does not complete tidily # here, so resizable becomes False and self.targetSize can not be # assigned to. This alerts us to the fact that now min size # and size are both incorrect values. if isinstance(an_fs, fs.NTFS): return - self.assertIsNone(an_fs.doResize()) + self.assertIsNone(an_fs.resize()) ACTUAL_SIZE = TARGET_SIZE.roundToNearest(an_fs._resizeTask.unit, rounding=ROUND_DOWN) self.assertEqual(an_fs._size, ACTUAL_SIZE) self._test_sizes(an_fs) @@ -352,8 +352,8 @@ def testTooBig2(self): BIG_SIZE = an_fs.maxSize - Size(1) an_fs.targetSize = BIG_SIZE self.assertEqual(an_fs.targetSize, BIG_SIZE) - with self.assertRaises(FSResizeError): - an_fs.doResize() + with self.assertRaises(FSError): + an_fs.resize()
# CHECKME: size and target size will be adjusted attempted values # while currentSize will be actual value