This is a request for comment, because it is incomplete. I am trying to figure out better ways to test the higher-level functionality but am having problems obtaining a cooperating virtual machine.
There are a few points: * What makes this a bit tricky is that LUKS is handled as both a device and a format. There is a sense in which it is both. * I decided to start from the bottom up this time, working on resizing the format first, rather than worrying about the Actions. * If the resize of a LUKSDevice is handled as just a resize of its slave's LUKS format then blivet.resizeDevice() does not need to change at all to handle LUKS devices AFAICT. * However, it does not make much sense to resize a LUKS format outside the LUKS device...since the format knows nothing about the size of the, e.g., filesystem that it encrypts. And yet, it is still possible. Not sure whether it is necessary or desirable to do anything about that. * I have not handled the dependencies between the actions.
From: mulhern amulhern@redhat.com
Related: #56
Signed-off-by: mulhern amulhern@redhat.com --- blivet/devicelibs/crypto.py | 1 + 1 file changed, 1 insertion(+)
diff --git a/blivet/devicelibs/crypto.py b/blivet/devicelibs/crypto.py index 474c905..a94cb60 100644 --- a/blivet/devicelibs/crypto.py +++ b/blivet/devicelibs/crypto.py @@ -25,5 +25,6 @@
LUKS_METADATA_SIZE = Size("2 MiB") MIN_CREATE_ENTROPY = 256 # bits +SECTOR_SIZE = Size("512 B")
EXTERNAL_DEPENDENCIES = [availability.BLOCKDEV_CRYPTO_PLUGIN]
From: mulhern amulhern@redhat.com
Related: #56
Signed-off-by: mulhern amulhern@redhat.com --- tests/formats_test/luks_test.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 tests/formats_test/luks_test.py
diff --git a/tests/formats_test/luks_test.py b/tests/formats_test/luks_test.py new file mode 100755 index 0000000..c4389f0 --- /dev/null +++ b/tests/formats_test/luks_test.py @@ -0,0 +1,29 @@ +import blivet.formats.luks as luks + +from tests import loopbackedtestcase + +class LUKSTestCase(loopbackedtestcase.LoopBackedTestCase): + + def __init__(self, methodName='runTest'): + super(LUKSTestCase, self).__init__(methodName=methodName, deviceSpec=[self.DEFAULT_STORE_SIZE]) + self.fmt = luks.LUKS(passphrase="passphrase", name="super-luks") + + def testSimple(self): + """ Simple test of creation, setup, and teardown. """ + # test that creation of format on device occurs w/out error + device = self.loopDevices[0] + + self.assertFalse(self.fmt.exists) + self.fmt.device = device + self.assertIsNone(self.fmt.create()) + self.assertIsNotNone(self.fmt.mapName) + self.assertTrue(self.fmt.exists) + self.assertTrue("LUKS" in self.fmt.name) + + # test that the device can be opened once created + self.assertIsNone(self.fmt.setup()) + self.assertTrue(self.fmt.status) + + # test that the device can be closed again + self.assertIsNone(self.fmt.teardown()) + self.assertFalse(self.fmt.status)
From: mulhern amulhern@redhat.com
Related: #56
Signed-off-by: mulhern amulhern@redhat.com --- blivet/tasks/availability.py | 1 + 1 file changed, 1 insertion(+)
diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py index efb8b75..12dc349 100644 --- a/blivet/tasks/availability.py +++ b/blivet/tasks/availability.py @@ -285,4 +285,5 @@ def available_resource(name): XFSDB_APP = application("xfs_db") XFSFREEZE_APP = application("xfs_freeze")
+LSBLK_APP = application("lsblk") MOUNT_APP = application("mount")
From: mulhern amulhern@redhat.com
Related: #56
Signed-off-by: mulhern amulhern@redhat.com --- blivet/tasks/lukstasks.py | 102 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 blivet/tasks/lukstasks.py
diff --git a/blivet/tasks/lukstasks.py b/blivet/tasks/lukstasks.py new file mode 100644 index 0000000..931ad7a --- /dev/null +++ b/blivet/tasks/lukstasks.py @@ -0,0 +1,102 @@ +# lukstasks.py +# Tasks for a LUKS format. +# +# Copyright (C) 2015 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Anne Mulhern amulhern@redhat.com + +from .. import util + +from ..errors import LUKSError +from ..size import Size + +from . import availability +from . import task + +class LUKSSize(task.BasicApplication): + """ Obtain information about the size of a LUKS format. """ + + # Note that this task currently makes use of lsblk. It must + # parse the output of lsblk --list and use the mapName of the device + # to identify the correct entry. Ideally, it would be possible + # to get the information by specifying the device using the mapName, + # but lsblk won't cooperate, reporting that the luks devices is not + # a block device instead. + + ext = availability.LSBLK_APP + + description = "size of a luks device" + + def __init__(self, a_luks): + """ Initializer. + + :param :class:`~.formats.luks.LUKS` a_luks: a LUKS format object + """ + self.luks = a_luks + + @property + def _sizeCommand(self): + """ Returns the command for reading luks format size. + + :returns: a list consisting of the appropriate command + :rtype: list of str + """ + return [ + str(self.ext), + '--list', + '--noheadings', + '--bytes', + '--output=NAME,SIZE' + ] + + def _extractSize(self, tab): + """ Extract size information from blkid output. + + :param str tab: tabular information for blkid + :rtype: :class:`~.size.Size` + :raises :class:`~.errors.LUKSError`: if size cannot be obtained + + Expects tab to be in name, value lines, where the value is + the size in bytes. + """ + for line in tab.strip().split('\n'): + name, size = line.split() + if name == self.luks.mapName: + return Size(int(size)) + raise LUKSError("Could not extract size from blkid output for %s" % self.luks.mapName) + + def doTask(self): + """ Returns the size of the luks format. + + :returns: the size of the luks format + :rtype: :class:`~.size.Size` + :raises :class:`~.errors.LUKSError`: if size cannot be obtained + """ + error_msgs = self.availabilityErrors + if error_msgs: + raise LUKSError("\n".join(error_msgs)) + + error_msg = None + try: + (rc, out) = util.run_program_and_capture_output(self._sizeCommand) + if rc: + error_msg = "failed to gather luks size info: %s" % rc + except OSError as e: + error_msg = "failed to gather luks size info: %s" % e + if error_msg: + raise LUKSError(error_msg) + return self._extractSize(out)
From: mulhern amulhern@redhat.com
Related: #56
Use LUKSSize task to obtain the info.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/luks.py | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/blivet/formats/luks.py b/blivet/formats/luks.py index f729e1e..df24ee1 100644 --- a/blivet/formats/luks.py +++ b/blivet/formats/luks.py @@ -30,6 +30,7 @@ from ..flags import flags from ..i18n import _, N_ from ..tasks import availability +from ..tasks import lukstasks
import logging log = logging.getLogger("blivet") @@ -46,6 +47,7 @@ class LUKS(DeviceFormat): _packages = ["cryptsetup"] # required packages _minSize = crypto.LUKS_METADATA_SIZE _plugin = availability.BLOCKDEV_CRYPTO_PLUGIN + _sizeinfoClass = lukstasks.LUKSSize
def __init__(self, **kwargs): """ @@ -80,6 +82,9 @@ def __init__(self, **kwargs): """ log_method_call(self, **kwargs) DeviceFormat.__init__(self, **kwargs) + + self._sizeinfo = self._sizeinfoClass(self) + self.cipher = kwargs.get("cipher") self.key_size = kwargs.get("key_size") self.mapName = kwargs.get("name") @@ -263,6 +268,12 @@ def escrow(self, directory, backupPassphrase): directory, backupPassphrase) log.debug("escrow: escrowVolume done for %s", repr(self.device))
+ def updateSizeInfo(self): + """ Update this format's current size. """ + try: + self._size = self._sizeinfo.doTask() + except LUKSError as e: + log.warning("Failed to obtain current size for device %s: %s", self.device, e)
register_device_format(LUKS)
From: mulhern amulhern@redhat.com
Related: #56
Could be creating any kind of format at this point, so use the corresponding error.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/populator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/blivet/populator.py b/blivet/populator.py index d86fa39..7cb26ea 100644 --- a/blivet/populator.py +++ b/blivet/populator.py @@ -29,7 +29,7 @@
from gi.repository import BlockDev as blockdev
-from .errors import CorruptGPTError, DeviceError, DeviceTreeError, DiskLabelScanError, DuplicateVGError, FSError, InvalidDiskLabelError, LUKSError +from .errors import CorruptGPTError, DeviceError, DeviceFormatError, DeviceTreeError, DiskLabelScanError, DuplicateVGError, InvalidDiskLabelError, LUKSError from .devices import BTRFSSubVolumeDevice, BTRFSVolumeDevice, BTRFSSnapShotDevice from .devices import DASDDevice, DMDevice, DMLinearDevice, DMRaidArrayDevice, DiskDevice from .devices import FcoeDiskDevice, FileDevice, LoopDevice, LUKSDevice @@ -1404,7 +1404,7 @@ def handleUdevDeviceFormat(self, info, device): device.format = formats.getFormat(format_designator, **kwargs) if device.format.type: log.info("got format: %s", device.format) - except FSError: + except DeviceFormatError: log.warning("type '%s' on '%s' invalid, assuming no format", format_designator, name) device.format = formats.DeviceFormat()
From: mulhern amulhern@redhat.com
Related: #56
Attributes are: * _size * _targetSize
Method are: * size * currentSize * targetSize * updateSizeInfo
_setTargetSize() now raises DeviceFormatError where previously it raised FSError.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/__init__.py | 75 +++++++++++++++++++++++++++++++++++++++-- blivet/formats/fs.py | 52 +++------------------------- tests/formats_test/fstesting.py | 6 ++-- 3 files changed, 79 insertions(+), 54 deletions(-)
diff --git a/blivet/formats/__init__.py b/blivet/formats/__init__.py index b3e58fa..e57cf20 100644 --- a/blivet/formats/__init__.py +++ b/blivet/formats/__init__.py @@ -183,6 +183,9 @@ def __init__(self, **kwargs): self._options = None self._device = None
+ self._size = kwargs.get("size", Size(0)) + self._targetSize = self._size + self.device = kwargs.get("device") self.uuid = kwargs.get("uuid") self.exists = kwargs.get("exists", False) @@ -193,13 +196,15 @@ def __repr__(self): " type = %(type)s name = %(name)s status = %(status)s\n" " device = %(device)s uuid = %(uuid)s exists = %(exists)s\n" " options = %(options)s supported = %(supported)s" - " formattable = %(format)s resizable = %(resize)s\n" % + " formattable = %(format)s resizable = %(resize)s" + " size = %(size)s targetSize = %(targetSize)s\n" % {"classname": self.__class__.__name__, "id": "%#x" % id(self), "object_id": self.id, "type": self.type, "name": self.name, "status": self.status, "device": self.device, "uuid": self.uuid, "exists": self.exists, "options": self.options, "supported": self.supported, - "format": self.formattable, "resize": self.resizable}) + "format": self.formattable, "resize": self.resizable, + "size": self._size, "targetSize": self.targetSize}) return s
@property @@ -218,7 +223,8 @@ def dict(self): d = {"type": self.type, "name": self.name, "device": self.device, "uuid": self.uuid, "exists": self.exists, "options": self.options, "supported": self.supported, - "resizable": self.resizable} + "resizable": self.resizable, "size": self._size, + "targetSize": self.targetSize} return d
def labeling(self): @@ -278,6 +284,15 @@ def _getLabel(self): """ return self._label
+ def _getSize(self): + """ Get this format's size. """ + return self.targetSize if self.resizable else self._size + + size = property( + lambda s: s._getSize(), + doc="This format's size, accounting for pending changes" + ) + def _setOptions(self, options): self._options = options
@@ -290,6 +305,55 @@ def _getOptions(self): doc="fstab entry option string" )
+ def _getTargetSize(self): + """ Get the target size. + + :returns: the target size + :rtype: :class:`~.size.Size` + """ + return self._targetSize + + def _setTargetSize(self, newsize): + """ Set the target size. + + :param :class:`~.size.Size` newsize: a size value + + This is the size which the format is set to when the + format is resized. + """ + if not isinstance(newsize, Size): + raise ValueError("new size must be of type Size") + + if not self.exists: + raise DeviceFormatError("format has not been created") + + if not self.resizable: + raise DeviceFormatError("format is not resizable") + + if newsize < self.minSize: + raise ValueError("requested size %s must be at least minimum size %s" % (newsize, self.minSize)) + + if self.maxSize and newsize >= self.maxSize: + raise ValueError("requested size %s must be less than maximum size %s" % (newsize, self.maxSize)) + + self._targetSize = newsize + + targetSize = property( + lambda s: s._getTargetSize(), + lambda s, v: s._setTargetSize(v), + doc="target size for a resize operation" + ) + + def updateSizeInfo(self): + """ Update this format's current size. + + May also update this format's minimum size. + + May also affect whether or not this filesystem is considered + resizable by setting self._resizable or other attributes. + """ + pass + def _deviceCheck(self, devspec): """ Verifies that device spec has a proper format.
@@ -590,6 +654,11 @@ def minSize(self): return self._minSize
@property + def currentSize(self): + """ The format's current actual size. """ + return self._size if self.exists else Size(0) + + @property def hidden(self): """ Whether devices with this formatting should be hidden in UIs. """ return self._hidden diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py index e2c5ef3..fb24243 100644 --- a/blivet/formats/fs.py +++ b/blivet/formats/fs.py @@ -122,7 +122,6 @@ def __init__(self, **kwargs): self.fsprofile = kwargs.get("fsprofile")
# filesystem size does not necessarily equal device size - self._size = kwargs.get("size", Size(0)) self._minInstanceSize = Size(0) # min size of this FS instance
# Resize operations are limited to error-free filesystems whose current @@ -146,11 +145,9 @@ def __init__(self, **kwargs): def __repr__(self): s = DeviceFormat.__repr__(self) s += (" mountpoint = %(mountpoint)s mountopts = %(mountopts)s\n" - " label = %(label)s size = %(size)s" - " targetSize = %(targetSize)s\n" % + " label = %(label)s\n" % {"mountpoint": self.mountpoint, "mountopts": self.mountopts, - "label": self.label, "size": self._size, - "targetSize": self.targetSize}) + "label": self.label}) return s
@property @@ -163,8 +160,8 @@ def desc(self): @property def dict(self): d = super(FS, self).dict - d.update({"mountpoint": self.mountpoint, "size": self._size, - "label": self.label, "targetSize": self.targetSize, + d.update({"mountpoint": self.mountpoint, + "label": self.label, "mountable": self.mountable}) return d
@@ -196,42 +193,6 @@ def labelFormatOK(self, label): label = property(lambda s: s._getLabel(), lambda s,l: s._setLabel(l), doc="this filesystem's label")
- def _setTargetSize(self, newsize): - """ Set the target size for this filesystem. - - :param :class:`~.size.Size` newsize: the newsize - """ - if not isinstance(newsize, Size): - raise ValueError("new size must be of type Size") - - if not self.exists: - raise FSError("filesystem has not been created") - - if not self.resizable: - raise FSError("filesystem is not resizable") - - if newsize < self.minSize: - raise ValueError("requested size %s must be at least minimum size %s" % (newsize, self.minSize)) - - if self.maxSize and newsize >= self.maxSize: - raise ValueError("requested size %s must be less than maximum size %s" % (newsize, self.maxSize)) - - self._targetSize = newsize - - def _getTargetSize(self): - """ Get this filesystem's target size. """ - return self._targetSize - - targetSize = property(_getTargetSize, _setTargetSize, - doc="Target size for this filesystem") - - def _getSize(self): - """ Get this filesystem's size. """ - return self.targetSize if self.resizable else self._size - - size = property(_getSize, doc="This filesystem's size, accounting " - "for pending changes") - def updateSizeInfo(self): """ Update this filesystem's current and minimum size (for resize). """
@@ -326,11 +287,6 @@ def _padSize(self, size): return padded
@property - def currentSize(self): - """ The filesystem's current actual size. """ - return self._size if self.exists else Size(0) - - @property def free(self): """ The amount of space that can be gained by resizing this filesystem to its minimum size. diff --git a/tests/formats_test/fstesting.py b/tests/formats_test/fstesting.py index 32c85af..fd205f2 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 FSError, FSResizeError +from blivet.errors import DeviceFormatError, FSError, FSResizeError from blivet.size import Size, ROUND_DOWN from blivet.formats import fs
@@ -197,9 +197,9 @@ def testResize(self): if not can_resize(an_fs): self.assertFalse(an_fs.resizable) # Not resizable, so can not do resizing actions. - with self.assertRaises(FSError): + with self.assertRaises(DeviceFormatError): an_fs.targetSize = Size("64 MiB") - with self.assertRaises(FSError): + with self.assertRaises(DeviceFormatError): an_fs.doResize() else: self.assertTrue(an_fs.resizable)
From: mulhern amulhern@redhat.com
Related: #56
Signed-off-by: mulhern amulhern@redhat.com --- tests/formats_test/luks_test.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/tests/formats_test/luks_test.py b/tests/formats_test/luks_test.py index c4389f0..76bb862 100755 --- a/tests/formats_test/luks_test.py +++ b/tests/formats_test/luks_test.py @@ -1,4 +1,5 @@ import blivet.formats.luks as luks +from blivet.size import Size
from tests import loopbackedtestcase
@@ -27,3 +28,36 @@ def testSimple(self): # test that the device can be closed again self.assertIsNone(self.fmt.teardown()) self.assertFalse(self.fmt.status) + + def testSize(self): + """ Test that sizes are calculated correctly. """ + device = self.loopDevices[0] + + # create the device + self.fmt.device = device + self.assertIsNone(self.fmt.create()) + + # the size is 0 + self.assertEqual(self.fmt.size, Size(0)) + self.assertEqual(self.fmt.currentSize, Size(0)) + self.assertEqual(self.fmt.targetSize, Size(0)) + + # open the luks device + self.assertIsNone(self.fmt.setup()) + + # size is unchanged + self.assertEqual(self.fmt.size, Size(0)) + self.assertEqual(self.fmt.currentSize, Size(0)) + self.assertEqual(self.fmt.targetSize, Size(0)) + + # update the size info + self.fmt.updateSizeInfo() + + # the size is greater than zero and less than the size of the device + self.assertLess(self.fmt.size, self.DEFAULT_STORE_SIZE) + self.assertGreater(self.fmt.size, Size(0)) + + self.assertEqual(self.fmt.currentSize, self.fmt.size) + self.assertEqual(self.fmt.targetSize, Size(0)) + + self.fmt.teardown()
From: mulhern amulhern@redhat.com
Related: #56
* Set _resizable True. * Set up resize class. * Fix up size tests that break when LUKS format becomes resizable.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/luks.py | 2 ++ tests/formats_test/luks_test.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/blivet/formats/luks.py b/blivet/formats/luks.py index df24ee1..dc17f02 100644 --- a/blivet/formats/luks.py +++ b/blivet/formats/luks.py @@ -48,6 +48,8 @@ class LUKS(DeviceFormat): _minSize = crypto.LUKS_METADATA_SIZE _plugin = availability.BLOCKDEV_CRYPTO_PLUGIN _sizeinfoClass = lukstasks.LUKSSize + _resizeClass = lukstasks.LUKSResize + _resizable = True
def __init__(self, **kwargs): """ diff --git a/tests/formats_test/luks_test.py b/tests/formats_test/luks_test.py index 76bb862..d6127f1 100755 --- a/tests/formats_test/luks_test.py +++ b/tests/formats_test/luks_test.py @@ -53,11 +53,14 @@ def testSize(self): # update the size info self.fmt.updateSizeInfo()
+ # set target size to imitate FS constructor + self.fmt.targetSize = self.fmt._size + # the size is greater than zero and less than the size of the device self.assertLess(self.fmt.size, self.DEFAULT_STORE_SIZE) self.assertGreater(self.fmt.size, Size(0))
self.assertEqual(self.fmt.currentSize, self.fmt.size) - self.assertEqual(self.fmt.targetSize, Size(0)) + self.assertEqual(self.fmt.targetSize, self.fmt.size)
self.fmt.teardown()
From: mulhern amulhern@redhat.com
Related: #56
Signed-off-by: mulhern amulhern@redhat.com --- tests/formats_test/luks_test.py | 75 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+)
diff --git a/tests/formats_test/luks_test.py b/tests/formats_test/luks_test.py index d6127f1..a7c7d3a 100755 --- a/tests/formats_test/luks_test.py +++ b/tests/formats_test/luks_test.py @@ -1,5 +1,6 @@ import blivet.formats.luks as luks from blivet.size import Size +from blivet.errors import DeviceFormatError, LUKSError
from tests import loopbackedtestcase
@@ -64,3 +65,77 @@ def testSize(self): self.assertEqual(self.fmt.targetSize, self.fmt.size)
self.fmt.teardown() + + def testResize(self): + """ Test that resizing does the correct thing. """ + device = self.loopDevices[0] + + # create the device + self.fmt.device = device + self.assertIsNone(self.fmt.create()) + + self.fmt.updateSizeInfo() + + newsize = Size("64 MiB") + self.fmt.targetSize = newsize + + # can not resize an unopened device + with self.assertRaises(DeviceFormatError): + self.fmt.resize() + + # open the luks device + self.assertIsNone(self.fmt.setup()) + + # can resize an opened device + self.fmt.resize() + + # actually looking up the actual size, should yield same + # as target size. + self.assertEqual(self.fmt._sizeinfo.doTask(), newsize) + + # device format computed size + self.assertEqual(self.fmt.size, newsize) + self.assertEqual(self.fmt.currentSize, newsize) + self.assertEqual(self.fmt.targetSize, newsize) + + self.fmt.teardown() + + def testResizeBounds(self): + """ Test resizing outside the bounds of the device. + Note that the bounds of the device are not the same as the + bounds of the format. + """ + device = self.loopDevices[0] + + # create the device + self.fmt.device = device + self.assertIsNone(self.fmt.create()) + + self.fmt.updateSizeInfo() + + newsize = Size("128 MiB") + self.fmt.targetSize = newsize + + # open the luks device + self.assertIsNone(self.fmt.setup()) + + # can not resize outside the bounds of the device + with self.assertRaises(LUKSError): + self.fmt.resize() + + newsize = Size("96 MiB") + self.fmt.targetSize = newsize + + # can resize within the bounds of the device + self.assertIsNone(self.fmt.resize()) + + # actually looking up the actual size, should yield same + # as target size. + self.assertEqual(self.fmt._sizeinfo.doTask(), newsize) + + # device format computed size + self.assertEqual(self.fmt.size, newsize) + self.assertEqual(self.fmt.currentSize, newsize) + self.assertEqual(self.fmt.targetSize, newsize) + + self.fmt.teardown()
From: mulhern amulhern@redhat.com
Related: #56
Signed-off-by: mulhern amulhern@redhat.com --- blivet/tasks/lukstasks.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+)
diff --git a/blivet/tasks/lukstasks.py b/blivet/tasks/lukstasks.py index 176aed8..e5bafe6 100644 --- a/blivet/tasks/lukstasks.py +++ b/blivet/tasks/lukstasks.py @@ -19,8 +19,11 @@ # # Red Hat Author(s): Anne Mulhern amulhern@redhat.com
+from gi.repository import BlockDev as blockdev + from .. import util
+from ..devicelibs import crypto from ..errors import LUKSError from ..size import Size
@@ -101,3 +104,27 @@ def doTask(self): if error_msg: raise LUKSError(error_msg) return self._extractSize(out) + +class LUKSResize(task.BasicApplication, dfresize.DFResizeTask): + """ Handle resize of LUKS device. """ + + description = "resize luks device" + + ext = availability.BLOCKDEV_CRYPTO_PLUGIN + + # units for specifying new size + unit = crypto.SECTOR_SIZE + + def __init__(self, a_luks): + """ Initializer. + + :param :class:`~.formats.luks.LUKS` a_luks: a LUKS format object + """ + self.luks = a_luks + + def doTask(self): + """ Resizes the LUKS format. """ + try: + blockdev.crypto.luks_resize(self.luks.mapName, self.luks.targetSize.convertTo(self.unit)) + except blockdev.CryptoError as e: + raise LUKSError(e)
From: mulhern amulhern@redhat.com
Related: #56
Adjust the task class heirarchy accordingly.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/tasks/dfresize.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ blivet/tasks/fsresize.py | 17 +++-------------- blivet/tasks/lukstasks.py | 1 + tests/pylint/runpylint.py | 1 + 4 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 blivet/tasks/dfresize.py
diff --git a/blivet/tasks/dfresize.py b/blivet/tasks/dfresize.py new file mode 100644 index 0000000..856707b --- /dev/null +++ b/blivet/tasks/dfresize.py @@ -0,0 +1,45 @@ +# dfresize.py +# DeviceFormat resizing classes. +# +# Copyright (C) 2015 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Anne Mulhern amulhern@redhat.com + +import abc + +from six import add_metaclass + +from . import task + +@add_metaclass(abc.ABCMeta) +class DFResizeTask(task.Task): + """ The abstract properties that any resize task must have. """ + + unit = abc.abstractproperty(doc="Resize unit.") + +class UnimplementedDFResize(task.UnimplementedTask, DFResizeTask): + + def __init__(self, a_df): + """ Initializer. + + :param DeviceFormat a_df: a device format object + """ + self.df = a_df + + @property + def unit(self): + raise NotImplementedError() diff --git a/blivet/tasks/fsresize.py b/blivet/tasks/fsresize.py index ef12686..c6db378 100644 --- a/blivet/tasks/fsresize.py +++ b/blivet/tasks/fsresize.py @@ -29,12 +29,12 @@
from . import availability from . import task +from . import dfresize
@add_metaclass(abc.ABCMeta) -class FSResizeTask(task.Task): +class FSResizeTask(dfresize.DFResizeTask): """ The abstract properties that any resize task must have. """
- unit = abc.abstractproperty(doc="Resize unit.") size_fmt = abc.abstractproperty(doc="Size format string.")
@add_metaclass(abc.ABCMeta) @@ -133,18 +133,7 @@ def args(self): options = ("remount", opts, self.sizeSpec()) return ['-o', ",".join(options), self.fs._type, self.fs.systemMountpoint]
-class UnimplementedFSResize(task.UnimplementedTask, FSResizeTask): - - def __init__(self, an_fs): - """ Initializer. - - :param FS an_fs: a filesystem object - """ - self.fs = an_fs - - @property - def unit(self): - raise NotImplementedError() +class UnimplementedFSResize(dfresize.UnimplementedDFResize, FSResizeTask):
@property def size_fmt(self): diff --git a/blivet/tasks/lukstasks.py b/blivet/tasks/lukstasks.py index 931ad7a..176aed8 100644 --- a/blivet/tasks/lukstasks.py +++ b/blivet/tasks/lukstasks.py @@ -26,6 +26,7 @@
from . import availability from . import task +from . import dfresize
class LUKSSize(task.BasicApplication): """ Obtain information about the size of a LUKS format. """ diff --git a/tests/pylint/runpylint.py b/tests/pylint/runpylint.py index 64badd4..7203658 100755 --- a/tests/pylint/runpylint.py +++ b/tests/pylint/runpylint.py @@ -18,6 +18,7 @@ def __init__(self): FalsePositive(r"Method 'doTask' is abstract in class 'UnimplementedTask' but is not overridden"), FalsePositive(r"No value for argument 'member_count' in unbound method call$"), FalsePositive(r"No value for argument 'smallest_member_size' in unbound method call$"), + FalsePositive(r"TmpFS._sizeOption: Instance of 'UnimplementedDFResize' has no 'size_fmt' member$"),
# FIXME: These are temporary, until there's a python3 anaconda. FalsePositive(r"Unable to import 'pyanaconda'$"),
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
From: mulhern amulhern@redhat.com
Related: #56
Preparatory to using _resize name for an internal resizing method.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/fs.py | 20 ++++++++++---------- tests/formats_test/fs_test.py | 2 +- tests/formats_test/fstesting.py | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py index fb24243..c63f1f4 100644 --- a/blivet/formats/fs.py +++ b/blivet/formats/fs.py @@ -106,7 +106,7 @@ def __init__(self, **kwargs): self._mkfs = self._mkfsClass(self) self._mount = self._mountClass(self) self._readlabel = self._readlabelClass(self) - self._resize = self._resizeClass(self) + self._resizeTask = self._resizeClass(self) self._sync = self._syncClass(self) self._writelabel = self._writelabelClass(self)
@@ -127,7 +127,7 @@ def __init__(self, **kwargs): # Resize operations are limited to error-free filesystems whose current # size is known. self._resizable = False - if flags.installer_mode and self._resize.available: + if flags.installer_mode and self._resizeTask.available: # if you want current/min size you have to call updateSizeInfo try: self.updateSizeInfo() @@ -282,7 +282,7 @@ def _padSize(self, size):
# make sure the padded and rounded min size is not larger than # the current size - padded = min(padded.roundToNearest(self._resize.unit, rounding=ROUND_UP), self.currentSize) + padded = min(padded.roundToNearest(self._resizeTask.unit, rounding=ROUND_UP), self.currentSize)
return padded
@@ -341,7 +341,7 @@ def doResize(self): if self.targetSize == self.currentSize: return
- if not self._resize.available: + if not self._resizeTask.available: return
# tmpfs mounts don't need an existing device node @@ -367,7 +367,7 @@ def doResize(self): # 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._resize.unit, + rounded = self.targetSize.roundToNearest(self._resizeTask.unit, rounding=ROUND_DOWN)
# 1. target size was between the min size and max size values prior to @@ -388,13 +388,13 @@ def doResize(self): # 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._resize.unit), self.device) + "filesystem on %s", unitStr(self._resizeTask.unit), self.device) return else: self.targetSize = rounded
try: - self._resize.doTask() + self._resizeTask.doTask() except FSError as e: raise FSResizeError(e, self.device)
@@ -639,7 +639,7 @@ def writeLabel(self): @property def utilsAvailable(self): # we aren't checking for fsck because we shouldn't need it - tasks = (self._mkfs, self._resize, self._writelabel, self._info) + tasks = (self._mkfs, self._resizeTask, self._writelabel, self._info) return all(not t.implemented or t.available for t in tasks)
@property @@ -662,7 +662,7 @@ def formattable(self): @property def resizable(self): """ Can formats of this filesystem type be resized? """ - return super(FS, self).resizable and self._resize.available + return super(FS, self).resizable and self._resizeTask.available
def _getOptions(self): return self.mountopts or ",".join(self._mount.options) @@ -1178,7 +1178,7 @@ def _sizeOption(self, size): This is not impossible, since a special option for mounting is size=<percentage>%. """ - return "size=%s" % (self._resize.size_fmt % size.convertTo(self._resize.unit)) + return "size=%s" % (self._resizeTask.size_fmt % size.convertTo(self._resizeTask.unit))
def _getOptions(self): # Returns the regular mount options with the special size option, diff --git a/tests/formats_test/fs_test.py b/tests/formats_test/fs_test.py index 70216d3..e981408 100644 --- a/tests/formats_test/fs_test.py +++ b/tests/formats_test/fs_test.py @@ -122,7 +122,7 @@ def testResize(self): newsize = self.an_fs.currentSize * 2 self.an_fs.targetSize = newsize self.assertIsNone(self.an_fs.doResize()) - self.assertEqual(self.an_fs.size, newsize.roundToNearest(self.an_fs._resize.unit, rounding=ROUND_DOWN)) + self.assertEqual(self.an_fs.size, newsize.roundToNearest(self.an_fs._resizeTask.unit, rounding=ROUND_DOWN))
def testShrink(self): # Can not shrink tmpfs, because its minimum size is its current size diff --git a/tests/formats_test/fstesting.py b/tests/formats_test/fstesting.py index fd205f2..bf654f8 100644 --- a/tests/formats_test/fstesting.py +++ b/tests/formats_test/fstesting.py @@ -16,7 +16,7 @@ def can_resize(an_fs):
:param an_fs: a filesystem object """ - resize_tasks = (an_fs._resize, an_fs._sizeinfo, an_fs._minsize) + resize_tasks = (an_fs._resizeTask, an_fs._sizeinfo, an_fs._minsize) return not any(t.availabilityErrors for t in resize_tasks)
@add_metaclass(abc.ABCMeta) @@ -209,7 +209,7 @@ def testResize(self): self.assertEqual(an_fs.targetSize, TARGET_SIZE) self.assertNotEqual(an_fs._size, TARGET_SIZE) self.assertIsNone(an_fs.doResize()) - ACTUAL_SIZE = TARGET_SIZE.roundToNearest(an_fs._resize.unit, rounding=ROUND_DOWN) + 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) self._test_sizes(an_fs) @@ -294,7 +294,7 @@ def testShrink(self): if isinstance(an_fs, fs.NTFS): return self.assertIsNone(an_fs.doResize()) - ACTUAL_SIZE = TARGET_SIZE.roundToNearest(an_fs._resize.unit, rounding=ROUND_DOWN) + ACTUAL_SIZE = TARGET_SIZE.roundToNearest(an_fs._resizeTask.unit, rounding=ROUND_DOWN) self.assertEqual(an_fs._size, ACTUAL_SIZE) self._test_sizes(an_fs)
@@ -357,7 +357,7 @@ def testTooBig2(self):
# CHECKME: size and target size will be adjusted attempted values # while currentSize will be actual value - TARGET_SIZE = BIG_SIZE.roundToNearest(an_fs._resize.unit, rounding=ROUND_DOWN) + TARGET_SIZE = BIG_SIZE.roundToNearest(an_fs._resizeTask.unit, rounding=ROUND_DOWN) self.assertEqual(an_fs.targetSize, TARGET_SIZE) self.assertEqual(an_fs.size, an_fs.targetSize) self.assertEqual(an_fs.currentSize, old_size)
From: mulhern amulhern@redhat.com
Related: #56
Use it where appropriate.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/__init__.py | 23 ++++++++++++++++++++--- blivet/formats/fs.py | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/blivet/formats/__init__.py b/blivet/formats/__init__.py index 7fa25cb..0a69ef8 100644 --- a/blivet/formats/__init__.py +++ b/blivet/formats/__init__.py @@ -359,6 +359,24 @@ def updateSizeInfo(self): """ pass
+ def _alignToResizeUnit(self, newsize, direction=ROUND_DOWN): + """ Returns a new size aligned to the units of the resize tool. + + :param :class:`~.Size` newsize: the proposed new size + :returns: newsize modified to yield an aligned size + :param direction: one of (decimal.ROUND_UP, decimal.ROUND_DOWN) + :rtype: :class:`~.Size` + + The default is to round down. + + If no resize unit available, returns newsize. + """ + try: + return newsize.roundToNearest(self._resizeTask.unit, rounding=direction) + except NotImplementedError: + log.warning("Trying to align size to resize unit, but no resize unit available.") + return newsize + def _deviceCheck(self, devspec): """ Verifies that device spec has a proper format.
@@ -600,12 +618,11 @@ def resize(self, **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) + rounded = self._alignToResizeUnit(self.targetSize)
# 1. target size was between the min size and max size values prior to # rounding (see _setTargetSize) diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py index f4547ca..bf23ba9 100644 --- a/blivet/formats/fs.py +++ b/blivet/formats/fs.py @@ -281,7 +281,7 @@ def _padSize(self, size):
# make sure the padded and rounded min size is not larger than # the current size - padded = min(padded.roundToNearest(self._resizeTask.unit, rounding=ROUND_UP), self.currentSize) + padded = min(self._alignToResizeUnit(padded, ROUND_UP), self.currentSize)
return padded
From: mulhern amulhern@redhat.com
Related: #56
Eliminate FS._preResize which seems to be more generic than I had originally thought.
Raise an exception in _preResize if resize operation is unavailable. Consistantly treat inability to resize by raising an exception. Return False only if there is no point in resizing because the resize operation will have no effect.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/__init__.py | 78 ++++++++++++++++++++++++---------------------- blivet/formats/fs.py | 24 ++------------ 2 files changed, 42 insertions(+), 60 deletions(-)
diff --git a/blivet/formats/__init__.py b/blivet/formats/__init__.py index 0a69ef8..dd45984 100644 --- a/blivet/formats/__init__.py +++ b/blivet/formats/__init__.py @@ -377,6 +377,35 @@ def _alignToResizeUnit(self, newsize, direction=ROUND_DOWN): log.warning("Trying to align size to resize unit, but no resize unit available.") return newsize
+ def _alignTargetSize(self): + """ Align the targetSize within various bounds. + + This is a last minute step before actually resizing the format. + + Makes sure that: + * The targetSize is no less than the minimum size. + * The targetSize is either equal to the current size or differs + from the current size by at least one resize unit. + + and if the above constraints do not conflict: + * The targetSize is rounded down to the resize unit + """ + # We always round down because the format 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. + newsize = self._alignToResizeUnit(self.targetSize) + + if newsize < self.minSize: + log.info("Setting target size on %s to minimum size %s", self.device, newsize) + newsize = self.minSize + + if newsize < self.currentSize < self.targetSize: + log.info("rounding target size down to next %s obviated resize of " + "format on %s", unitStr(self._resizeTask.unit), self.device) + newsize = self.currentSize + + self.targetSize = newsize + def _deviceCheck(self, devspec): """ Verifies that device spec has a proper format.
@@ -618,35 +647,6 @@ def resize(self, **kwargs):
def _resize(self, **kwargs): """ Do generic resizing actions. """ - - # 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._alignToResizeUnit(self.targetSize) - - # 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): @@ -661,24 +661,26 @@ def _preResize(self, **kwargs): """ Do all preparatory checks for resizing a format.
:rtype: bool - :returns: True if resize should proceed, otherwise False + :returns: False if resize would be pointless, otherwise True + :raises DeviceFormatError: if it is not possible to resize """ 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 + raise DeviceFormatError("no facility for resizing format")
if self._deviceRequiredForResize() and not os.path.exists(self.device): raise DeviceFormatError("device %s does not exist" % self.device)
- return True + self.updateSizeInfo() + + if not self.resizable: + raise DeviceFormatError("format is not resizable") + + self._alignTargetSize() + + return self.targetSize != self.currentSize
def _postResize(self, **kwargs): """ Do what must be done after resizing a format. """ diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py index bf23ba9..b4548e9 100644 --- a/blivet/formats/fs.py +++ b/blivet/formats/fs.py @@ -38,7 +38,7 @@ from ..tasks import fssync from ..tasks import fswritelabel from ..errors import FormatCreateError, FSError, FSReadLabelError -from ..errors import FSWriteLabelError, FSResizeError +from ..errors import FSWriteLabelError from . import DeviceFormat, register_device_format from .. import util from .. import platform @@ -329,7 +329,7 @@ def _postCreate(self, **kwargs): def doResize(self): """ Resize this filesystem based on this instance's targetSize attr.
- :raises: FSResizeError, FSError, DeviceFormatError + :raises: FSError, DeviceFormatError
.. versionchanged:: 1.5 Raises :class:`~.errors.DeviceFormatError` @@ -339,26 +339,6 @@ def doResize(self): """ self.resize()
- 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 - # so run the check one last time and bump up the size if it was too - # small. - self.updateSizeInfo() - - # Check again if resizable is True, as updateSizeInfo() can change that - if not self.resizable: - raise FSResizeError("filesystem not resizable", self.device) - - if self.targetSize < self.minSize: - self.targetSize = self.minSize - log.info("Minimum size changed, setting targetSize on %s to %s", - self.device, self.targetSize) - return True - def _postResize(self, **kwargs): self.doCheck() super(FS, self)._postResize(**kwargs)
From: mulhern amulhern@redhat.com
Related: #56
It is no longer thrown anywhere.
This means that a little bit of code in anaconda can be thrown out, but it does not cause a dependency.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/osinstall.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/blivet/osinstall.py b/blivet/osinstall.py index 1c63302..05f249b 100644 --- a/blivet/osinstall.py +++ b/blivet/osinstall.py @@ -31,7 +31,7 @@
from .storage_log import log_exception_info from .devices import FileDevice, NFSDevice, NoDevice, OpticalDevice, NetworkStorageDevice, DirectoryDevice -from .errors import FSTabTypeMismatchError, UnrecognizedFSTabEntryError, StorageError, FSResizeError, UnknownSourceDeviceError +from .errors import FSTabTypeMismatchError, UnrecognizedFSTabEntryError, StorageError, UnknownSourceDeviceError from .formats import get_device_format_class from .formats import getFormat from .flags import flags @@ -1055,10 +1055,7 @@ def turnOnFilesystems(storage, mountOnly=False, callbacks=None):
try: storage.doIt(callbacks) - except FSResizeError as e: - if errorHandler.cb(e) == ERROR_RAISE: - raise - except Exception as e: + except Exception: raise
storage.turnOnSwap()
What about this? https://github.com/rhinstaller/anaconda/blob/master/pyanaconda/errors.py#L31...
From: mulhern amulhern@redhat.com
Related: #56
Except for LUKS devices, nothing is changed. However, a LUKS device is resizable if it's slave's format, which is always a LUKS format, does not exist or is resizable (it should never have type None).
Signed-off-by: mulhern amulhern@redhat.com --- blivet/devices/storage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/blivet/devices/storage.py b/blivet/devices/storage.py index e63c7f9..7f4e157 100644 --- a/blivet/devices/storage.py +++ b/blivet/devices/storage.py @@ -287,9 +287,9 @@ def formatArgs(self): @property def resizable(self): """ Can this device be resized? """ + fmt = self.raw_device.format return (self._resizable and self.exists and - (self.format.type is None or self.format.resizable or - not self.format.exists)) + (fmt.type is None or fmt.resizable or not fmt.exists))
def notifyKernel(self): """ Send a 'change' uevent to the kernel for this device. """
From: mulhern amulhern@redhat.com
Related: #56
Signed-off-by: mulhern amulhern@redhat.com --- blivet/devices/luks.py | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/blivet/devices/luks.py b/blivet/devices/luks.py index 197926f..9a87626 100644 --- a/blivet/devices/luks.py +++ b/blivet/devices/luks.py @@ -59,6 +59,16 @@ def __init__(self, name, fmt=None, size=None, uuid=None, def raw_device(self): return self.slave
+ def _setTargetSize(self, newsize): + if newsize > self.slave.size: + raise ValueError("requested size %s is larger than size of slave device %s" % (newsize, self.slave.name)) + + if newsize < self.format.size: + raise ValueError("requested size %s is smaller size of existing format %s on device" % (newsize, self.format)) + + super(LUKSDevice, self)._setTargetSize(newsize) + self.slave.format.targetSize = newsize + @property def size(self): if not self.exists: @@ -82,6 +92,9 @@ def _postTeardown(self, recursive=False): def dracutSetupArgs(self): return set(["rd.luks.uuid=luks-%s" % self.slave.format.uuid])
+ def resize(self): + self.slave.format.resize() + def populateKSData(self, data): self.slave.populateKSData(data) data.encrypted = True
I could make the commit message for aaaeede more explicit, something like:
anaconda's pyananaconda.errors.ErrorHandler class defines an error handler _fsResizeHandler(), and a lookup function, cb(), which does lookup on this precise error type. The error handler just provides a little more information, and then re-raises the exception. The errorHandler, and the lookup for FSResizeError should both be removed at some point, as they will have become dead as a result of this patch. However, no anaconda error should result from this patch, and the anaconda spec does not need to be updated for this patch.
Also, since we're moving into deprecation for real, it might make sense to deprecate FSResizeError class.
Luks resize implemented in PR #308
Closed.
anaconda-patches@lists.fedorahosted.org