Now with fewer commits.
From: mulhern amulhern@redhat.com
Related: #12
See commit 3171acf404c31b541c4c57bb6aaeaaa45dcbd0fa for a further explanation.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/fslabeling.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/blivet/formats/fslabeling.py b/blivet/formats/fslabeling.py index ef5b4ae..0abeaf7 100644 --- a/blivet/formats/fslabeling.py +++ b/blivet/formats/fslabeling.py @@ -61,8 +61,8 @@ def labelingArgs(self, label):
class Ext2FSLabeling(FSLabeling):
- default_label = property(lambda s: "") - label_app = property(lambda s: fslabel.E2Label) + default_label = "" + label_app = fslabel.E2Label
def labelFormatOK(self, label): return len(label) < 17 @@ -72,8 +72,8 @@ def labelingArgs(self, label):
class FATFSLabeling(FSLabeling):
- default_label = property(lambda s: "NO NAME") - label_app = property(lambda s: fslabel.DosFsLabel) + default_label = "NO NAME" + label_app = fslabel.DosFsLabel
def labelFormatOK(self, label): return len(label) < 12 @@ -83,8 +83,8 @@ def labelingArgs(self, label):
class JFSLabeling(FSLabeling):
- default_label = property(lambda s: "") - label_app = property(lambda s: fslabel.JFSTune) + default_label = "" + label_app = fslabel.JFSTune
def labelFormatOK(self, label): return len(label) < 17 @@ -94,8 +94,8 @@ def labelingArgs(self, label):
class ReiserFSLabeling(FSLabeling):
- default_label = property(lambda s: "") - label_app = property(lambda s: fslabel.ReiserFSTune) + default_label = "" + label_app = fslabel.ReiserFSTune
def labelFormatOK(self, label): return len(label) < 17 @@ -105,8 +105,8 @@ def labelingArgs(self, label):
class XFSLabeling(FSLabeling):
- default_label = property(lambda s: "") - label_app = property(lambda s: fslabel.XFSAdmin) + default_label = "" + label_app = fslabel.XFSAdmin
def labelFormatOK(self, label): return ' ' not in label and len(label) < 13 @@ -116,8 +116,8 @@ def labelingArgs(self, label):
class HFSLabeling(FSLabeling):
- default_label = property(lambda s: "Untitled") - label_app = property(lambda s: None) + default_label = "Untitled" + label_app = None
def labelFormatOK(self, label): return ':' not in label and len(label) < 28 and len(label) > 0 @@ -127,8 +127,8 @@ def labelingArgs(self, label):
class HFSPlusLabeling(FSLabeling):
- default_label = property(lambda s: "Untitled") - label_app = property(lambda s: None) + default_label = "Untitled" + label_app = None
def labelFormatOK(self, label): return ':' not in label and 0 < len(label) < 129 @@ -138,8 +138,8 @@ def labelingArgs(self, label):
class NTFSLabeling(FSLabeling):
- default_label = property(lambda s: "") - label_app = property(lambda s: fslabel.NTFSLabel) + default_label = "" + label_app = fslabel.NTFSLabel
def labelFormatOK(self, label): return len(label) < 129
I think the commit hash in the commit message above needs to be updated to reference the refactored commit.
Probably just a copy-paste error, fixed: 3b50bf259ab8e1ce98969cf15e672459bbef5e18
From: mulhern amulhern@redhat.com
Related: #12
After a few more patches they will be required to access instance variables.
Does not affect downstream, which always uses instance when invoking, anyway.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/__init__.py | 8 +++---- blivet/formats/fs.py | 15 +++++------- blivet/formats/swap.py | 6 ++--- tests/formats_test/labeling_test.py | 46 ++++++++++++++++++------------------- 4 files changed, 34 insertions(+), 41 deletions(-)
diff --git a/blivet/formats/__init__.py b/blivet/formats/__init__.py index d881a7c..ba15c62 100644 --- a/blivet/formats/__init__.py +++ b/blivet/formats/__init__.py @@ -222,13 +222,11 @@ def dict(self): "resizable": self.resizable} return d
- @classmethod - def labeling(cls): + def labeling(self): """Returns False by default since most formats are non-labeling.""" return False
- @classmethod - def labelFormatOK(cls, label): + def labelFormatOK(self, label): """Checks whether the format of the label is OK for whatever application is used by blivet to write a label for this format. If there is no application that blivet uses to write a label, @@ -240,7 +238,7 @@ def labelFormatOK(cls, label): :return: True if the format of the label is OK, otherwise False """ # pylint: disable=unused-argument - return cls.labeling() + return self.labeling()
def _setLabel(self, label): """Sets the label for this format. diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py index ad48d6e..9e91083 100644 --- a/blivet/formats/fs.py +++ b/blivet/formats/fs.py @@ -151,25 +151,22 @@ def dict(self): "mountable": self.mountable}) return d
- @classmethod - def labeling(cls): + def labeling(self): """Returns True if this filesystem uses labels, otherwise False.
:rtype: bool """ - return cls._labelfs is not None + return self._labelfs is not None
- @classmethod - def relabels(cls): + def relabels(self): """Returns True if it is possible to relabel this filesystem after creation, otherwise False.
:rtype: bool """ - return cls._labelfs is not None and cls._labelfs.label_app is not None + return self._labelfs is not None and self._labelfs.label_app is not None
- @classmethod - def labelFormatOK(cls, label): + def labelFormatOK(self, label): """Return True if the label has an acceptable format for this filesystem. None, which represents accepting the default for this device, is always acceptable. @@ -177,7 +174,7 @@ def labelFormatOK(cls, label): :param label: A possible label :type label: str or None """ - return label is None or (cls._labelfs is not None and cls._labelfs.labelFormatOK(label)) + return label is None or (self._labelfs is not None and self._labelfs.labelFormatOK(label))
label = property(lambda s: s._getLabel(), lambda s,l: s._setLabel(l), doc="this filesystem's label") diff --git a/blivet/formats/swap.py b/blivet/formats/swap.py index 2e2e910..4eab044 100644 --- a/blivet/formats/swap.py +++ b/blivet/formats/swap.py @@ -80,13 +80,11 @@ def dict(self): d.update({"priority": self.priority, "label": self.label}) return d
- @classmethod - def labeling(cls): + def labeling(self): """Returns True as mkswap can write a label to the swap space.""" return True
- @classmethod - def labelFormatOK(cls, label): + def labelFormatOK(self, label): """Returns True since no known restrictions on the label.""" return True
diff --git a/tests/formats_test/labeling_test.py b/tests/formats_test/labeling_test.py index 82a61b7..dfa6e79 100755 --- a/tests/formats_test/labeling_test.py +++ b/tests/formats_test/labeling_test.py @@ -15,41 +15,41 @@ def testLabels(self): """Initialize some filesystems with valid and invalid labels."""
# Ext2FS has a maximum length of 16 - self.assertFalse(fs.Ext2FS.labelFormatOK("root___filesystem")) - self.assertTrue(fs.Ext2FS.labelFormatOK("root__filesystem")) + self.assertFalse(fs.Ext2FS().labelFormatOK("root___filesystem")) + self.assertTrue(fs.Ext2FS().labelFormatOK("root__filesystem"))
# FATFS has a maximum length of 11 - self.assertFalse(fs.FATFS.labelFormatOK("rtfilesystem")) - self.assertTrue(fs.FATFS.labelFormatOK("rfilesystem")) + self.assertFalse(fs.FATFS().labelFormatOK("rtfilesystem")) + self.assertTrue(fs.FATFS().labelFormatOK("rfilesystem"))
# JFS has a maximum length of 16 - self.assertFalse(fs.JFS.labelFormatOK("root___filesystem")) - self.assertTrue(fs.JFS.labelFormatOK("root__filesystem")) + self.assertFalse(fs.JFS().labelFormatOK("root___filesystem")) + self.assertTrue(fs.JFS().labelFormatOK("root__filesystem"))
# ReiserFS has a maximum length of 16 - self.assertFalse(fs.ReiserFS.labelFormatOK("root___filesystem")) - self.assertTrue(fs.ReiserFS.labelFormatOK("root__filesystem")) + self.assertFalse(fs.ReiserFS().labelFormatOK("root___filesystem")) + self.assertTrue(fs.ReiserFS().labelFormatOK("root__filesystem"))
#XFS has a maximum length 12 and does not allow spaces - self.assertFalse(fs.XFS.labelFormatOK("root_filesyst")) - self.assertFalse(fs.XFS.labelFormatOK("root file")) - self.assertTrue(fs.XFS.labelFormatOK("root_filesys")) + self.assertFalse(fs.XFS().labelFormatOK("root_filesyst")) + self.assertFalse(fs.XFS().labelFormatOK("root file")) + self.assertTrue(fs.XFS().labelFormatOK("root_filesys"))
#HFS has a maximum length of 27, minimum length of 1, and does not allow colons - self.assertFalse(fs.HFS.labelFormatOK("n" * 28)) - self.assertFalse(fs.HFS.labelFormatOK("root:file")) - self.assertFalse(fs.HFS.labelFormatOK("")) - self.assertTrue(fs.HFS.labelFormatOK("n" * 27)) + self.assertFalse(fs.HFS().labelFormatOK("n" * 28)) + self.assertFalse(fs.HFS().labelFormatOK("root:file")) + self.assertFalse(fs.HFS().labelFormatOK("")) + self.assertTrue(fs.HFS().labelFormatOK("n" * 27))
#HFSPlus has a maximum length of 128, minimum length of 1, and does not allow colons - self.assertFalse(fs.HFSPlus.labelFormatOK("n" * 129)) - self.assertFalse(fs.HFSPlus.labelFormatOK("root:file")) - self.assertFalse(fs.HFSPlus.labelFormatOK("")) - self.assertTrue(fs.HFSPlus.labelFormatOK("n" * 128)) + self.assertFalse(fs.HFSPlus().labelFormatOK("n" * 129)) + self.assertFalse(fs.HFSPlus().labelFormatOK("root:file")) + self.assertFalse(fs.HFSPlus().labelFormatOK("")) + self.assertTrue(fs.HFSPlus().labelFormatOK("n" * 128))
# NTFS has a maximum length of 128 - self.assertFalse(fs.NTFS.labelFormatOK("n" * 129)) - self.assertTrue(fs.NTFS.labelFormatOK("n" * 128)) + self.assertFalse(fs.NTFS().labelFormatOK("n" * 129)) + self.assertTrue(fs.NTFS().labelFormatOK("n" * 128))
# all devices are permitted to be passed a label argument of None # some will ignore it completely @@ -62,7 +62,7 @@ class MethodsTestCase(unittest.TestCase): def setUp(self): self.fs = {} for k, v in device_formats.items(): - if issubclass(v, fs.FS) and v.labeling(): + if issubclass(v, fs.FS) and v().labeling(): self.fs[k] = v(device="/dev", label="myfs")
@@ -87,7 +87,7 @@ def testGetLabelArgs(self): # all of the remaining are non-labeling so will accept any label label = "Houston, we have a problem!" for name, klass in device_formats.items(): - if issubclass(klass, fs.FS) and not klass.labeling() and not issubclass(klass, fs.NFS): + if issubclass(klass, fs.FS) and not klass().labeling() and not issubclass(klass, fs.NFS): self.assertEqual(klass(device="/dev", label=label).label, label, msg=name)
class XFSTestCase(fslabeling.CompleteLabelingAsRoot):
From: mulhern amulhern@redhat.com
Related: #12
Because labeling errors are sometimes not as severe as other errors, so we do want to distinguish.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/errors.py | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/blivet/errors.py b/blivet/errors.py index 4a31987..179c823 100644 --- a/blivet/errors.py +++ b/blivet/errors.py @@ -77,6 +77,12 @@ class MultipathMemberError(DeviceFormatError): class FSError(DeviceFormatError): pass
+class FSWriteLabelError(FSError): + pass + +class FSReadLabelError(FSError): + pass + class FSResizeError(FSError): def __init__(self, message, details): FSError.__init__(self, message)
From: mulhern amulhern@redhat.com
Related: #12
This way, tasks can know what the kernel filesystems are if they need to. Avoids circular imports of filesystems, task, then filesystem again.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/devicetree.py | 2 +- blivet/formats/fs.py | 14 ++------------ blivet/formats/fslib.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 blivet/formats/fslib.py
diff --git a/blivet/devicetree.py b/blivet/devicetree.py index 765e4ce..e86c8c3 100644 --- a/blivet/devicetree.py +++ b/blivet/devicetree.py @@ -1013,7 +1013,7 @@ def handleNodevFilesystems(self): except ValueError: log.error("failed to parse /proc/mounts line: %s", line) continue - if fstype in formats.fs.nodev_filesystems: + if fstype in formats.fslib.nodev_filesystems: if not flags.include_nodev: continue
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py index 9e91083..3bb0ebe 100644 --- a/blivet/formats/fs.py +++ b/blivet/formats/fs.py @@ -41,21 +41,11 @@ from .. import udev from ..mounts import mountsCache
+from .fslib import kernel_filesystems, update_kernel_filesystems + import logging log = logging.getLogger("blivet")
-kernel_filesystems = [] -nodev_filesystems = [] - -def update_kernel_filesystems(): - for line in open("/proc/filesystems").readlines(): - fields = line.split() - kernel_filesystems.append(fields[-1]) - if fields[0] == "nodev": - nodev_filesystems.append(fields[-1]) - -update_kernel_filesystems() - class FS(DeviceFormat): """ Filesystem base class. """ _type = "Abstract Filesystem Class" # fs type name diff --git a/blivet/formats/fslib.py b/blivet/formats/fslib.py new file mode 100644 index 0000000..092439d --- /dev/null +++ b/blivet/formats/fslib.py @@ -0,0 +1,34 @@ +# fslib.py +# Library to support filesystem 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): Dave Lehman dlehman@redhat.com +# David Cantrell dcantrell@redhat.com +# Anne Mulhern amulhern@redhat.com + +kernel_filesystems = [] +nodev_filesystems = [] + +def update_kernel_filesystems(): + for line in open("/proc/filesystems").readlines(): + fields = line.split() + kernel_filesystems.append(fields[-1]) + if fields[0] == "nodev": + nodev_filesystems.append(fields[-1]) + +update_kernel_filesystems()
From: mulhern amulhern@redhat.com
Related: #12
Signed-off-by: mulhern amulhern@redhat.com --- blivet/errors.py | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/blivet/errors.py b/blivet/errors.py index 179c823..6677fce 100644 --- a/blivet/errors.py +++ b/blivet/errors.py @@ -194,3 +194,7 @@ class UnknownSourceDeviceError(StorageError): # factories class DeviceFactoryError(StorageError): pass + +class AvailabilityError(StorageError): + """ Raised if problem determining availability of external resource. """ + pass
From: mulhern amulhern@redhat.com
Related: #12
Signed-off-by: mulhern amulhern@redhat.com --- blivet/tasks/__init__.py | 0 blivet/tasks/availability.py | 192 +++++++++++++++++++++++++++++++++++++++++++ blivet/tasks/task.py | 86 +++++++++++++++++++ python-blivet.spec | 1 + setup.py | 2 +- 5 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 blivet/tasks/__init__.py create mode 100644 blivet/tasks/availability.py create mode 100644 blivet/tasks/task.py
diff --git a/blivet/tasks/__init__.py b/blivet/tasks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py new file mode 100644 index 0000000..b868e39 --- /dev/null +++ b/blivet/tasks/availability.py @@ -0,0 +1,192 @@ +# availability.py +# Class for tracking availability of an application. +# +# Copyright (C) 2014-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 distutils.version import LooseVersion + +from six import add_metaclass + +from gi.repository import BlockDev as blockdev + +from .. import util +from ..errors import AvailabilityError + +class ExternalResource(object): + """ An application. """ + + def __init__(self, method, name): + """ Initializes an instance of an application. + + :param method: A method object + :type method: :class:`Method` + :param str name: the name of the application + """ + self._method = method + self.name = name + + def __str__(self): + return self.name + + # Results of these methods may change at runtime, depending on system + # state. + + @property + def available(self): + """ Whether the resource is available. + + :returns: True if the resource is available, otherwise False + :rtype: bool + """ + return self._method.available(self) + +@add_metaclass(abc.ABCMeta) +class Method(object): + """ A collection of methods for a particular application task. """ + + @abc.abstractmethod + def available(self, resource): + """ Returns True if the resource is available. + + :param resource: any external resource + :type resource: :class:`ExternalResource` + + :returns: True if the application is available + :rtype: bool + """ + raise NotImplementedError() + +class Path(Method): + """ Methods for when application is found in PATH. """ + + def available(self, resource): + """ Returns True if the name of the application is in the path. + + :param resource: any application + :type resource: :class:`ExternalResource` + + :returns: True if the name of the application is in the path + :rtype: bool + """ + return bool(util.find_program_in_path(resource.name)) + +Path = Path() + +class PackageInfo(object): + + def __init__(self, package_name, required_version=None): + """ Initializer. + + :param str package_name: the name of the package + :param required_version: the required version for this package + :type required_version: :class:`distutils.LooseVersion` or NoneType + """ + self.package_name = package_name + self.required_version = required_version + + def __str__(self): + return "%s-%s" % (self.package_name, self.required_version) + +class PackageMethod(Method): + """ Methods for checking the package version of the external resource. """ + + def __init__(self, package=None): + """ Initializer. + + :param :class:`PackageInfo` package: + """ + self.package = package + + @property + def packageVersion(self): + args = ["rpm", "-q", "--queryformat", "%{VERSION}", self.package.package_name] + try: + (rc, out) = util.run_program_and_capture_output(args) + if rc != 0: + raise AvailabilityError("Could not determine package version for %s" % self.package.package_name) + except OSError as e: + raise AvailabilityError("Could not determine package version for %s: %s" % (self.package.package_name, e)) + + return LooseVersion(out) + + def available(self, resource): + return Path.available(resource) and \ + self.package.required_version is None or self.packageVersion >= self.package.required_version + +class BlockDevMethod(Method): + """ Methods for when application is actually a libblockdev plugin. """ + + def available(self, resource): + """ Returns True if the plugin is loaded. + + :param resource: a libblockdev plugin + :type resource: :class:`ExternalResource` + + :returns: True if the name of the plugin is loaded + :rtype: bool + """ + return resource.name in blockdev.get_available_plugin_names() + +BlockDevMethod = BlockDevMethod() + +def application(name): + """ Construct an external resource that is an application. + + This application will be available if its name can be found in $PATH. + """ + return ExternalResource(Path, name) + +def application_by_package(name, package_method): + """ Construct an external resource that is an application. + + This application will be available if its name can be found in $PATH + AND its package version is at least the required version. + + :param :class:`PackageMethod` package_method: the package method + """ + return ExternalResource(package_method, name) + +def blockdev_plugin(name): + """ Construct an external resource that is a libblockdev plugin. """ + return ExternalResource(BlockDevMethod, name) + +# blockdev plugins +BLOCKDEV_BTRFS_PLUGIN = blockdev_plugin("btrfs") +BLOCKDEV_CRYPTO_PLUGIN = blockdev_plugin("crypto") +BLOCKDEV_DM_PLUGIN = blockdev_plugin("dm") +BLOCKDEV_LOOP_PLUGIN = blockdev_plugin("loop") +BLOCKDEV_LVM_PLUGIN = blockdev_plugin("lvm") +BLOCKDEV_MDRAID_PLUGIN = blockdev_plugin("mdraid") +BLOCKDEV_MPATH_PLUGIN = blockdev_plugin("mpath") +BLOCKDEV_SWAP_PLUGIN = blockdev_plugin("swap") + +# packages +E2FSPROGS_PACKAGE = PackageMethod(PackageInfo("e2fsprogs", LooseVersion("1.41.0"))) + +# applications +DOSFSLABEL_APP = application("dosfslabel") +E2LABEL_APP = application_by_package("e2label", E2FSPROGS_PACKAGE) +JFSTUNE_APP = application("jfs_tune") +NTFSLABEL_APP = application("ntfslabel") +NTFSRESIZE_APP = application("ntfsresize") +RESIZE2FS_APP = application_by_package("resize2fs", E2FSPROGS_PACKAGE) +XFSADMIN_APP = application("xfs_admin") + +MOUNT_APP = application("mount") diff --git a/blivet/tasks/task.py b/blivet/tasks/task.py new file mode 100644 index 0000000..9a519ff --- /dev/null +++ b/blivet/tasks/task.py @@ -0,0 +1,86 @@ +# task.py +# Abstract class for tasks. +# +# 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 + +@add_metaclass(abc.ABCMeta) +class Task(object): + """ An abstract class that represents some task. """ + + # Whether or not the functionality is implemented in the task class. + # It is True by default. Only NotImplementedClass and its descendants + # should have a False value. + implemented = True + + description = abc.abstractproperty(doc="Brief description for this task.") + + @property + def availabilityErrors(self): + """ Reasons if this task or the tasks it depends on are unavailable. """ + return self._availabilityErrors + \ + [e for t in self.dependsOn for e in t.availabilityErrors] + + _availabilityErrors = abc.abstractproperty( + doc="Reasons if the necessary external tools are unavailable.") + + dependsOn = abc.abstractproperty(doc="tasks that this task depends on") + + @abc.abstractmethod + def doTask(self, *args, **kwargs): + """ Do the task for this class. """ + raise NotImplementedError() + +class UnimplementedTask(Task): + """ A null Task, which returns a negative or empty for all properties.""" + + description = "an unimplemented task" + implemented = False + + @property + def _availabilityErrors(self): + return ["Not implemented task can not succeed."] + + dependsOn = [] + + def doTask(self, *args, **kwargs): + raise NotImplementedError() + +@add_metaclass(abc.ABCMeta) +class BasicApplication(Task): + """ A task representing an application. """ + + ext = abc.abstractproperty(doc="The object representing the external resource.") + + # TASK methods + + @property + def _availabilityErrors(self): + errors = [] + if not self.ext.available: + errors.append("application %s is not available" % self.ext) + + return errors + + @property + def dependsOn(self): + return [] diff --git a/python-blivet.spec b/python-blivet.spec index c01f462..73d981b 100644 --- a/python-blivet.spec +++ b/python-blivet.spec @@ -41,6 +41,7 @@ Requires: libselinux-python Requires: libblockdev >= %{libblockdevver} Requires: libblockdev-plugins-all >= %{libblockdevver} Requires: libselinux-python +Requires: rpm
%description The python-blivet package is a python module for examining and modifying diff --git a/setup.py b/setup.py index 83a23ff..e7ac91c 100644 --- a/setup.py +++ b/setup.py @@ -36,4 +36,4 @@ def add_member_order_option(files): author='David Lehman', author_email='dlehman@redhat.com', url='http://fedoraproject.org/wiki/blivet', data_files=data_files, - packages=['blivet', 'blivet.devices', 'blivet.devicelibs', 'blivet.formats']) + packages=['blivet', 'blivet.devices', 'blivet.devicelibs', 'blivet.formats', 'blivet.tasks'])
In reply to line 131 of blivet/tasks/availability.py:
I think adding parentheses around the whole expression (instead of using ````) and probably also around the >= part would make this a bit better readable.
In reply to line 40 of blivet/tasks/availability.py:
The docstrings should be updated to match the new name of the class (no longer ``Application``)
In reply to line 44 of python-blivet.spec:
More out of curiosity than anything else, but -- is there any reason why not to use *python-rpm* and do the version checks as python code instead of running the ``rpm`` binary?
In reply to line 40 of blivet/tasks/availability.py:
Done.
In reply to line 44 of python-blivet.spec:
That seems better.
From: mulhern amulhern@redhat.com
Related: #12
Signed-off-by: mulhern amulhern@redhat.com --- blivet/tasks/fsck.py | 157 ++++++++++++++++++++++++ blivet/tasks/fsinfo.py | 106 +++++++++++++++++ blivet/tasks/fslabeling.py | 98 +++++++++++++++ blivet/tasks/fsminsize.py | 187 +++++++++++++++++++++++++++++ blivet/tasks/fsmkfs.py | 231 ++++++++++++++++++++++++++++++++++++ blivet/tasks/fsmount.py | 195 ++++++++++++++++++++++++++++++ blivet/tasks/fsreadlabel.py | 133 +++++++++++++++++++++ blivet/tasks/fsresize.py | 148 +++++++++++++++++++++++ blivet/tasks/fssize.py | 166 ++++++++++++++++++++++++++ blivet/tasks/fssync.py | 89 ++++++++++++++ blivet/tasks/fswritelabel.py | 116 ++++++++++++++++++ tests/pylint/pylint-false-positives | 3 + 12 files changed, 1629 insertions(+) create mode 100644 blivet/tasks/fsck.py create mode 100644 blivet/tasks/fsinfo.py create mode 100644 blivet/tasks/fslabeling.py create mode 100644 blivet/tasks/fsminsize.py create mode 100644 blivet/tasks/fsmkfs.py create mode 100644 blivet/tasks/fsmount.py create mode 100644 blivet/tasks/fsreadlabel.py create mode 100644 blivet/tasks/fsresize.py create mode 100644 blivet/tasks/fssize.py create mode 100644 blivet/tasks/fssync.py create mode 100644 blivet/tasks/fswritelabel.py
diff --git a/blivet/tasks/fsck.py b/blivet/tasks/fsck.py new file mode 100644 index 0000000..a70e45e --- /dev/null +++ b/blivet/tasks/fsck.py @@ -0,0 +1,157 @@ +# fsck.py +# Filesystem check functionality. +# +# 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 ..errors import FSError +from .. import util + +from . import availability +from . import task + +_UNKNOWN_RC_MSG = "Unknown return code: %d" + +@add_metaclass(abc.ABCMeta) +class FSCK(task.BasicApplication): + """An abstract class that represents actions associated with + checking consistency of a filesystem. + """ + description = "fsck" + + options = abc.abstractproperty( + doc="Options for invoking the application.") + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + # IMPLEMENTATION methods + + @abc.abstractmethod + def _errorMessage(self, rc): + """ Error message corresponding to rc. + + :param int rc: the fsck program return code + :returns: an error message corresponding to the code, or None + :rtype: str or NoneType + + A return value of None indicates no error. + """ + raise NotImplementedError() + + @property + def _fsckCommand(self): + """The command to check the filesystem. + + :return: the command + :rtype: list of str + """ + return [str(self.ext)] + self.options + [self.fs.device] + + def doTask(self): + """ Check the filesystem. + + :raises FSError: on failure + """ + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + try: + rc = util.run_program(self._fsckCommand) + except OSError as e: + raise FSError("filesystem check failed: %s" % e) + + error_msg = self._errorMessage(rc) + if error_msg is not None: + hdr = "%(type)s filesystem check failure on %(device)s: " % \ + {"type": self.fs.type, "device": self.fs.device} + + raise FSError(hdr + error_msg) + + +class DosFSCK(FSCK): + _fsckErrors = {1: "Recoverable errors have been detected or dosfsck has " + "discovered an internal inconsistency.", + 2: "Usage error."} + + ext = availability.application("dosfsck") + options = ["-n"] + + def _errorMessage(self, rc): + if rc < 1: + return None + try: + return self._fsckErrors[rc] + except KeyError: + return _UNKNOWN_RC_MSG % rc + + +class Ext2FSCK(FSCK): + _fsckErrors = {4: "File system errors left uncorrected.", + 8: "Operational error.", + 16: "Usage or syntax error.", + 32: "e2fsck cancelled by user request.", + 128: "Shared library error."} + + ext = availability.application_by_package("e2fsck", availability.E2FSPROGS_PACKAGE) + options = ["-f", "-p", "-C", "0"] + + def _errorMessage(self, rc): + msgs = (self._fsckErrors[c] for c in self._fsckErrors.keys() if rc & c) + return "\n".join(msgs) or None + +class HFSPlusFSCK(FSCK): + _fsckErrors = {3: "Quick check found a dirty filesystem; no repairs done.", + 4: "Root filesystem was dirty. System should be rebooted.", + 8: "Corrupt filesystem, repairs did not succeed.", + 47: "Major error found; no repairs attempted."} + ext = availability.application("fsck.hfsplus") + options = [] + + def _errorMessage(self, rc): + if rc < 1: + return None + try: + return self._fsckErrors[rc] + except KeyError: + return _UNKNOWN_RC_MSG % rc + +class NTFSFSCK(FSCK): + ext = availability.NTFSRESIZE_APP + options = ["-c"] + + def _errorMessage(self, rc): + return _UNKNOWN_RC_MSG % (rc,) if rc != 0 else None + +class UnimplementedFSCK(task.UnimplementedTask): + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs diff --git a/blivet/tasks/fsinfo.py b/blivet/tasks/fsinfo.py new file mode 100644 index 0000000..18eeb79 --- /dev/null +++ b/blivet/tasks/fsinfo.py @@ -0,0 +1,106 @@ +# fsinfo.py +# Filesystem information gathering 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 ..errors import FSError +from .. import util + +from . import availability +from . import task + +@add_metaclass(abc.ABCMeta) +class FSInfo(task.BasicApplication): + """ An abstract class that represents an information gathering app. """ + + description = "filesystem info" + + options = abc.abstractproperty( + doc="Options for invoking the application.") + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + @property + def _infoCommand(self): + """ Returns the command for reading filesystem information. + + :returns: a list of appropriate options + :rtype: list of str + """ + return [str(self.ext)] + self.options + [self.fs.device] + + def doTask(self): + """ Returns information from the command. + + :returns: a string representing the output of the command + :rtype: str + :raises FSError: if info cannot be obtained + """ + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + error_msg = None + try: + (rc, out) = util.run_program_and_capture_output(self._infoCommand) + if rc: + error_msg = "failed to gather fs info: %s" % rc + except OSError as e: + error_msg = "failed to gather fs info: %s" % e + if error_msg: + raise FSError(error_msg) + return out + +class Ext2FSInfo(FSInfo): + ext = availability.application_by_package("dumpe2fs", availability.E2FSPROGS_PACKAGE) + options = ["-h"] + +class JFSInfo(FSInfo): + ext = availability.JFSTUNE_APP + options = ["-l"] + +class NTFSInfo(FSInfo): + ext = availability.application("ntfsinfo") + options = ["-m"] + +class ReiserFSInfo(FSInfo): + ext = availability.application("debugreiserfs") + options = [] + +class XFSInfo(FSInfo): + ext = availability.application("xfs_db") + options = ["-c", "sb 0", "-c", "p dblocks", "-c", "p blocksize"] + +class UnimplementedFSInfo(task.UnimplementedTask): + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs diff --git a/blivet/tasks/fslabeling.py b/blivet/tasks/fslabeling.py new file mode 100644 index 0000000..b0a4c7e --- /dev/null +++ b/blivet/tasks/fslabeling.py @@ -0,0 +1,98 @@ +# fslabeling.py +# Filesystem labeling classes for anaconda's storage configuration module. +# +# Copyright (C) 2014 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 + +@add_metaclass(abc.ABCMeta) +class FSLabeling(object): + """An abstract class that represents filesystem labeling actions. + """ + + default_label = abc.abstractproperty( + doc="Default label set on this filesystem at creation.") + + @abc.abstractmethod + def labelFormatOK(self, label): + """Returns True if this label is correctly formatted for this + filesystem, otherwise False. + + :param str label: the label for this filesystem + :rtype: bool + """ + raise NotImplementedError + +class Ext2FSLabeling(FSLabeling): + + default_label = "" + + def labelFormatOK(self, label): + return len(label) < 17 + +class FATFSLabeling(FSLabeling): + + default_label = "NO NAME" + + def labelFormatOK(self, label): + return len(label) < 12 + +class JFSLabeling(FSLabeling): + + default_label = "" + + def labelFormatOK(self, label): + return len(label) < 17 + +class ReiserFSLabeling(FSLabeling): + + default_label = "" + + def labelFormatOK(self, label): + return len(label) < 17 + +class XFSLabeling(FSLabeling): + + default_label = "" + + def labelFormatOK(self, label): + return ' ' not in label and len(label) < 13 + +class HFSLabeling(FSLabeling): + + default_label = "Untitled" + + def labelFormatOK(self, label): + return ':' not in label and len(label) < 28 and len(label) > 0 + +class HFSPlusLabeling(FSLabeling): + + default_label = "Untitled" + + def labelFormatOK(self, label): + return ':' not in label and 0 < len(label) < 129 + +class NTFSLabeling(FSLabeling): + + default_label = "" + + def labelFormatOK(self, label): + return len(label) < 129 diff --git a/blivet/tasks/fsminsize.py b/blivet/tasks/fsminsize.py new file mode 100644 index 0000000..5044190 --- /dev/null +++ b/blivet/tasks/fsminsize.py @@ -0,0 +1,187 @@ +# fsminsize.py +# Filesystem size gathering 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 ..errors import FSError +from .. import util +from ..size import Size + +from . import availability +from . import task + +@add_metaclass(abc.ABCMeta) +class FSMinSize(task.BasicApplication): + """ An abstract class that represents min size information extraction. """ + + description = "minimum filesystem size" + + options = abc.abstractproperty(doc="Options for use with app.") + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + def _resizeCommand(self): + return [str(self.ext)] + self.options + [self.fs.device] + + def _getResizeInfo(self): + """ Get info from fsresize program. + + :rtype: str + :returns: output returned by fsresize program + """ + error_msg = None + try: + (rc, out) = util.run_program_and_capture_output(self._resizeCommand()) + except OSError as e: + error_msg = "failed to gather info from resize program: %s" % e + if rc: + error_msg = "failed to gather info from resize program: %s" % e + + if error_msg: + raise FSError(error_msg) + return out + + @abc.abstractmethod + def doTask(self): + """ Returns the minimum size for this filesystem object. + + :rtype: :class:`~.size.Size` + :returns: the minimum size + :raises FSError: if filesystem can not be obtained + """ + raise NotImplementedError() + +class Ext2FSMinSize(FSMinSize): + + ext = availability.RESIZE2FS_APP + options = ["-P"] + + @property + def dependsOn(self): + return [self.fs._info] + + def _extractBlockSize(self): + """ Extract block size from filesystem info. + + :returns: block size of fileystem or None + :rtype: :class:`~.size.Size` or NoneType + """ + if self.fs._current_info is None: + return None + + blockSize = None + for line in (l.strip() for l in self.fs._current_info.splitlines() if l.startswith("Block size:")): + try: + blockSize = int(line.split(" ")[-1]) + break + except ValueError: + continue + + return Size(blockSize) if blockSize else None + + def _extractNumBlocks(self, info): + """ Extract the number of blocks from the resizefs info. + + :returns: the number of blocks or None + :rtype: int or NoneType + """ + numBlocks = None + for line in info.splitlines(): + (text, _sep, value) = line.partition(":") + if "minimum size of the filesystem" not in text: + continue + + try: + numBlocks = int(value.strip()) + break + except ValueError: + break + + return numBlocks + + def doTask(self): + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + blockSize = self._extractBlockSize() + if blockSize is None: + raise FSError("failed to get block size for %s filesystem on %s" % (self.fs.mountType, self.fs.device.name)) + + resize_info = self._getResizeInfo() + numBlocks = self._extractNumBlocks(resize_info) + if numBlocks is None: + raise FSError("failed to get minimum block number for %s filesystem on %s" % (self.fs.mountType, self.fs.device.name)) + + return blockSize * numBlocks + +class NTFSMinSize(FSMinSize): + + ext = availability.NTFSRESIZE_APP + options = ["-m"] + + def _extractMinSize(self, info): + """ Extract the minimum size from the resizefs info. + + :param str info: info obtained from resizefs prog + :rtype: :class:`~.size.Size` or NoneType + :returns: the minimum size, or None + """ + minSize = None + for line in info.splitlines(): + (text, _sep, value) = line.partition(":") + if "Minsize" not in text: + continue + + try: + minSize = Size("%d MB" % int(value.strip())) + except ValueError: + break + + return minSize + + + def doTask(self): + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + resize_info = self._getResizeInfo() + minSize = self._extractMinSize(resize_info) + if minSize is None: + raise FSError("Unable to discover minimum size of filesystem on %s" % self.fs.device) + return minSize + +class UnimplementedFSMinSize(task.UnimplementedTask): + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs diff --git a/blivet/tasks/fsmkfs.py b/blivet/tasks/fsmkfs.py new file mode 100644 index 0000000..33cd50f --- /dev/null +++ b/blivet/tasks/fsmkfs.py @@ -0,0 +1,231 @@ +# fsmkfs.py +# Filesystem formatting 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 ..errors import FSError, FSWriteLabelError +from .. import util + +from . import availability +from . import task + +@add_metaclass(abc.ABCMeta) +class FSMkfsTask(task.Task): + + can_label = abc.abstractproperty(doc="whether this task labels") + +@add_metaclass(abc.ABCMeta) +class FSMkfs(task.BasicApplication, FSMkfsTask): + """An abstract class that represents filesystem creation actions. """ + description = "mkfs" + + label_option = abc.abstractproperty( + doc="Option for setting a filesystem label.") + + args = abc.abstractproperty(doc="options for creating filesystem") + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + # IMPLEMENTATION methods + + @property + def can_label(self): + """ Whether this task can label the filesystem. + + :returns: True if this task can label the filesystem + :rtype: bool + """ + return self.label_option is not None + + @property + def _labelOptions(self): + """ Any labeling options that a particular filesystem may use. + + :returns: labeling options + :rtype: list of str + """ + # Do not know how to set label while formatting. + if self.label_option is None: + return [] + + # No label to set + if self.fs.label is None: + return [] + + if self.fs.labelFormatOK(self.fs.label): + return [self.label_option, self.fs.label] + else: + raise FSWriteLabelError("Choosing not to apply label (%s) during creation of filesystem %s. Label format is unacceptable for this filesystem." % (self.fs.label, self.fs.type)) + + def _formatOptions(self, options=None, label=False): + """Get a list of format options to be used when creating the + filesystem. + + :param options: any special options + :type options: list of str or None + :param bool label: if True, label if possible, default is False + """ + options = options or [] + + if not isinstance(options, list): + raise FSError("options parameter must be a list.") + + label_options = self._labelOptions if label else [] + return options + self.args + label_options + [self.fs.device] + + def _mkfsCommand(self, options, label): + """Return the command to make the filesystem. + + :param options: any special options + :type options: list of str or None + :returns: the mkfs command + :rtype: list of str + """ + return [str(self.ext)] + self._formatOptions(options, label) + + def doTask(self, options=None, label=False): + """Create the format on the device and label if possible and desired. + + :param options: any special options, may be None + :type options: list of str or NoneType + :param bool label: whether to label while creating, default is False + """ + # pylint: disable=arguments-differ + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + options = options or [] + try: + ret = util.run_program(self._mkfsCommand(options, label)) + except OSError as e: + raise FSError(e) + + if ret: + raise FSError("format failed: %s" % ret) + +class BTRFSMkfs(FSMkfs): + ext = availability.application("mkfs.btrfs") + label_option = None + + @property + def args(self): + return [] + +class Ext2FSMkfs(FSMkfs): + ext = availability.application_by_package("mke2fs", availability.E2FSPROGS_PACKAGE) + label_option = "-L" + + _opts = [] + + @property + def args(self): + return self._opts + (["-T", self.fs.fsprofile] if self.fs.fsprofile else []) + +class Ext3FSMkfs(Ext2FSMkfs): + _opts = ["-t", "ext3"] + +class Ext4FSMkfs(Ext3FSMkfs): + _opts = ["-t", "ext4"] + +class FATFSMkfs(FSMkfs): + ext = availability.application("mkdosfs") + label_option = "-n" + + @property + def args(self): + return [] + +class GFS2Mkfs(FSMkfs): + ext = availability.application("mkfs.gfs2") + label_option = None + + @property + def args(self): + return ["-j", "1", "-p", "lock_nolock", "-O"] + +class HFSMkfs(FSMkfs): + ext = availability.application("hformat") + label_option = "-l" + + @property + def args(self): + return [] + +class HFSPlusMkfs(FSMkfs): + ext = availability.application("mkfs.hfsplus") + label_option = "-v" + + @property + def args(self): + return [] + +class JFSMkfs(FSMkfs): + ext = availability.application("mkfs.jfs") + label_option = "-L" + + @property + def args(self): + return ["-q"] + +class NTFSMkfs(FSMkfs): + ext = availability.application("mkntfs") + label_option = "-L" + + @property + def args(self): + return [] + +class ReiserFSMkfs(FSMkfs): + ext = availability.application("mkreiserfs") + label_option = "-l" + + @property + def args(self): + return ["-f", "-f"] + +class XFSMkfs(FSMkfs): + ext = availability.application("mkfs.xfs") + label_option = "-L" + + @property + def args(self): + return ["-f"] + +class UnimplementedFSMkfs(task.UnimplementedTask, FSMkfsTask): + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + @property + def can_label(self): + return False diff --git a/blivet/tasks/fsmount.py b/blivet/tasks/fsmount.py new file mode 100644 index 0000000..39a11b7 --- /dev/null +++ b/blivet/tasks/fsmount.py @@ -0,0 +1,195 @@ +# fsmount.py +# Filesystem mounting 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 os + +from ..errors import FSError +from ..flags import flags +from .. import util +from ..formats import fslib + +from . import availability +from . import task + +class FSMount(task.BasicApplication): + """An abstract class that represents filesystem mounting actions. """ + description = "mount a filesystem" + + options = ["defaults"] + # type argument to pass to mount, if different from filesystem type + fstype = None + + ext = availability.MOUNT_APP + + def __init__(self, an_fs): + self.fs = an_fs + + # TASK methods + + @property + def _availabilityErrors(self): + errors = super(FSMount, self)._availabilityErrors + + canmount = (self.mountType in fslib.kernel_filesystems) or \ + (os.access("/sbin/mount.%s" % (self.mountType,), os.X_OK)) + + # Still consider the filesystem type mountable if there exists + # an appropriate filesystem driver in the kernel modules directory. + if not canmount: + modpath = os.path.realpath(os.path.join("/lib/modules", os.uname()[2])) + if os.path.isdir(modpath): + modname = "%s.ko" % self.mountType + for _root, _dirs, files in os.walk(modpath): + if any(x.startswith(modname) for x in files): + canmount = True + break + + if not canmount: + errors.append("mounting filesystem %s is not supported" % self.mountType) + return errors + + # IMPLEMENTATION methods + + @property + def mountType(self): + """ Mount type string to pass to mount command. + + :returns: mount type string + :rtype: str + """ + return self.fstype or self.fs._type + + def _modifyOptions(self, options): + """ Any mandatory options can be added in this method. + + :param str options: an option string + :returns: a modified option string + :rtype: str + """ + return options + + def mountOptions(self, options): + """ The options used for mounting. + + :param options: mount options + :type options: str or None + :returns: the options used by the task + :rtype: str + """ + if not options or not isinstance(options, str): + options = self.fs.mountopts or ",".join(self.options) + + return self._modifyOptions(options) + + def doTask(self, mountpoint, options=None): + """Create the format on the device and label if possible and desired. + + :param str mountpoint: mountpoint that overrides self.mountpoint + :param options: mount options + :type options: str or None + """ + # pylint: disable=arguments-differ + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + try: + rc = util.mount(self.fs.device, mountpoint, + fstype=self.mountType, + options=self.mountOptions(options)) + except OSError as e: + raise FSError("mount failed: %s" % e) + + if rc: + raise FSError("mount failed: %s" % rc) + +class AppleBootstrapFSMount(FSMount): + fstype = "hfs" + +class BindFSMount(FSMount): + + @property + def _availabilityErrors(self): + errors = [] + if not self.ext.available: + errors.append("application %s is not available" % self.ext) + + return errors + + def _modifyOptions(self, options): + return ",".join(["bind", options]) + +class DevPtsFSMount(FSMount): + options = ["gid=5", "mode=620"] + +class FATFSMount(FSMount): + options = ["umask=0077", "shortname=winnt"] + +class EFIFSMount(FATFSMount): + fstype = "vfat" + +class HFSPlusMount(FSMount): + fstype = "hfsplus" + +class Iso9660FSMount(FSMount): + options = ["ro"] + +class NoDevFSMount(FSMount): + + @property + def mountType(self): + return self.fs.device + +class NFSMount(FSMount): + + def _availabilityErrors(self): + return ["nfs filesystem can't be mounted"] + +class NTFSMount(FSMount): + options = ["default", "ro"] + +class SELinuxFSMount(NoDevFSMount): + + @property + def _availabilityErrors(self): + errors = super(SELinuxFSMount, self)._availabilityErrors + if not flags.selinux: + errors.append("selinux not enabled") + return errors + +class TmpFSMount(NoDevFSMount): + + def _modifyOptions(self, options): + # This duplicates some code in fs.TmpFS._getOptions. + # There seems to be no way around that. + if self.fs._accept_default_size: + size_opt = None + else: + size_opt = self.fs._sizeOption(self.fs._size) + return ",".join(o for o in (options, size_opt) if o) + + @property + def _availabilityErrors(self): + errors = [] + if not self.ext.available: + errors.append("application %s is not available" % self.ext) + + return errors diff --git a/blivet/tasks/fsreadlabel.py b/blivet/tasks/fsreadlabel.py new file mode 100644 index 0000000..8b61b02 --- /dev/null +++ b/blivet/tasks/fsreadlabel.py @@ -0,0 +1,133 @@ +# fsreadlabel.py +# Filesystem label reading 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 +import re + +from six import add_metaclass + +from ..errors import FSReadLabelError +from .. import util + +from . import availability +from . import task + +@add_metaclass(abc.ABCMeta) +class FSReadLabel(task.BasicApplication): + """ An abstract class that represents reading a filesystem's label. """ + description = "read filesystem label" + + label_regex = abc.abstractproperty( + doc="Matches the string output by the reading application.") + + args = abc.abstractproperty(doc="arguments for reading a label.") + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + # IMPLEMENTATION methods + + @property + def _readCommand(self): + """Get the command to read the filesystem label. + + :return: the command + :rtype: list of str + """ + return [str(self.ext)] + self.args + + def _extractLabel(self, labelstr): + """Extract the label from an output string. + + :param str labelstr: the string containing the label information + + :return: the label + :rtype: str + + Raises an FSReadLabelError if the label can not be extracted. + """ + match = re.match(self.label_regex, labelstr) + if match is None: + raise FSReadLabelError("Unknown format for application %s" % self._app()) + return match.group('label') + + def doTask(self): + """ Get the label. + + :returns: the filesystem label + :rtype: str + """ + error_msgs = self.availabilityErrors + if error_msgs: + raise FSReadLabelError("\n".join(error_msgs)) + + (rc, out) = util.run_program_and_capture_output(self._readCommand) + if rc != 0: + raise FSReadLabelError("read label failed") + + label = out.strip() + + return label if label == "" else self._extractLabel(label) + +class DosFSReadLabel(FSReadLabel): + ext = availability.DOSFSLABEL_APP + label_regex = r'(?P<label>.*)' + + @property + def args(self): + return [self.fs.device] + +class Ext2FSReadLabel(FSReadLabel): + ext = availability.E2LABEL_APP + label_regex = r'(?P<label>.*)' + + @property + def args(self): + return [self.fs.device] + +class NTFSReadLabel(FSReadLabel): + ext = availability.NTFSLABEL_APP + label_regex = r'(?P<label>.*)' + + @property + def args(self): + return [self.fs.device] + +class XFSReadLabel(FSReadLabel): + ext = availability.XFSADMIN_APP + label_regex = r'label = "(?P<label>.*)"' + + @property + def args(self): + return ["-l", self.fs.device] + +class UnimplementedFSReadLabel(task.UnimplementedTask): + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs diff --git a/blivet/tasks/fsresize.py b/blivet/tasks/fsresize.py new file mode 100644 index 0000000..46023d1 --- /dev/null +++ b/blivet/tasks/fsresize.py @@ -0,0 +1,148 @@ +# fsresize.py +# Filesystem 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 ..errors import FSError +from ..size import B, KiB, MiB, GiB, KB, MB, GB +from ..import util + +from . import availability +from . import task + +@add_metaclass(abc.ABCMeta) +class FSResizeTask(task.Task): + """ 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) +class FSResize(task.BasicApplication, FSResizeTask): + """ An abstract class for resizing a filesystem. """ + + description = "resize filesystem" + + args = abc.abstractproperty(doc="Resize arguments.") + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + # IMPLEMENTATION methods + + @abc.abstractmethod + def sizeSpec(self): + """ Returns a string specification for the target size of the command. + :returns: size specification + :rtype: str + """ + raise NotImplementedError() + + def _resizeCommand(self): + return [str(self.ext)] + self.args + + def doTask(self): + """ Resize the device. + + :raises FSError: on failure + """ + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + try: + ret = util.run_program(self._resizeCommand()) + except OSError as e: + raise FSError(e) + + if ret: + raise FSError("resize failed: %s" % ret) + +class Ext2FSResize(FSResize): + ext = availability.RESIZE2FS_APP + unit = MiB + # No unit specifier is interpreted not as bytes, but block size + size_fmt = {KiB: "%dK", MiB: "%dM", GiB: "%dG"}[unit] + + def sizeSpec(self): + return self.size_fmt % self.fs.targetSize.convertTo(self.unit) + + @property + def args(self): + return ["-p", self.fs.device, self.sizeSpec()] + +class NTFSResize(FSResize): + ext = availability.NTFSRESIZE_APP + unit = B + size_fmt = {B: "%d", KB: "%dK", MB: "%dM", GB: "%dG"}[unit] + + def sizeSpec(self): + return self.size_fmt % self.fs.targetSize.convertTo(self.unit) + + @property + def args(self): + return [ + "-ff", # need at least two 'f's to fully supress interaction + "-s", self.sizeSpec(), + self.fs.device + ] + +class TmpFSResize(FSResize): + + ext = availability.MOUNT_APP + unit = MiB + size_fmt = {KiB: "%dk", MiB: "%dm", GiB: "%dg"}[unit] + + def sizeSpec(self): + return "size=%s" % (self.size_fmt % self.fs.targetSize.convertTo(self.unit)) + + @property + def args(self): + # This is too closely mixed in w/ TmpFS object, due to the + # fact that resizing is done by mounting and that the options are + # therefore mount options. The situation is hard to avoid, though. + opts = self.fs.mountopts or ",".join(self.fs._mount.options) + 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() + + @property + def size_fmt(self): + raise NotImplementedError() diff --git a/blivet/tasks/fssize.py b/blivet/tasks/fssize.py new file mode 100644 index 0000000..e4a7507 --- /dev/null +++ b/blivet/tasks/fssize.py @@ -0,0 +1,166 @@ +# fssize.py +# Filesystem size gathering 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 collections import namedtuple + +from six import add_metaclass + +from ..errors import FSError +from ..size import Size +from .. import util + +from . import availability +from . import task + +_tags = ("count", "size") +_Tags = namedtuple("_Tags", _tags) + +@add_metaclass(abc.ABCMeta) +class FSSize(task.Task): + """ An abstract class that represents size information extraction. """ + description = "current filesystem size" + + tags = abc.abstractproperty( + doc="Strings used for extracting components of size.") + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + # TASK methods + + @property + def _availabilityErrors(self): + return [] + + @property + def dependsOn(self): + return [self.fs._info] + + # IMPLEMENTATION methods + + def doTask(self): + """ Returns the size of the filesystem. + + :returns: the size of the filesystem or None + :rtype: :class:`~.size.Size` or NoneType + :raises FSError: on failure + """ + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + if self.fs._current_info is None: + raise FSError("No info available for size computation.") + + # Setup initial values + values = {} + for k in _tags: + values[k] = None + + # Attempt to set values from info + for line in (l.strip() for l in self.fs._current_info.splitlines()): + key = next((k for k in _tags if line.startswith(getattr(self.tags, k))), None) + if not key: + continue + + if values[key] is not None: + raise FSError("found two matches for key %s" % key) + + # Look for last numeric value in matching line + fields = line.split() + fields.reverse() + for field in fields: + try: + values[key] = int(field) + break + except ValueError: + continue + + # Raise an error if a value is missing + missing = next((k for k in _tags if values[k] is None), None) + if missing is not None: + raise FSError("Failed to parse info for %s." % missing) + + return values["count"] * Size(values["size"]) + +class Ext2FSSize(FSSize): + tags = _Tags(size="Block size:", count="Block count:") + +class JFSSize(FSSize): + tags = _Tags(size="Physical block size:", count="Aggregate size:") + +class NTFSSize(FSSize): + tags = _Tags(size="Cluster Size:", count="Volume Size in Clusters:") + +class ReiserFSSize(FSSize): + tags = _Tags(size="Blocksize:", count="Count of blocks on the device:") + +class XFSSize(FSSize): + tags = _Tags(size="blocksize =", count="dblocks =") + +class TmpFSSize(task.BasicApplication): + description = "current filesystem size" + + ext = availability.application("df") + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + @property + def _sizeCommand(self): + return [str(self.ext), self.fs.systemMountpoint, "--output=size"] + + def doTask(self): + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + try: + (ret, out) = util.run_program_and_capture_output(self._sizeCommand) + if ret: + raise FSError("Failed to execute command %s." % self._sizeCommand) + except OSError: + raise FSError("Failed to execute command %s." % self._sizeCommand) + + lines = out.splitlines() + if len(lines) != 2 or lines[0].strip() != "1K-blocks": + raise FSError("Failed to parse output of command %s." % self._sizeCommand) + + return Size("%s KiB" % lines[1]) + + +class UnimplementedFSSize(task.UnimplementedTask): + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs diff --git a/blivet/tasks/fssync.py b/blivet/tasks/fssync.py new file mode 100644 index 0000000..5cf82b6 --- /dev/null +++ b/blivet/tasks/fssync.py @@ -0,0 +1,89 @@ +# fssync.py +# Filesystem syncing classes. +# +# Copyright (C) 2014 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 ..errors import FSError +from .. import util + +from . import availability +from . import task + +@add_metaclass(abc.ABCMeta) +class FSSync(task.BasicApplication): + """ An abstract class that represents syncing a filesystem. """ + + description = "filesystem syncing" + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + @abc.abstractmethod + def doTask(self): + raise NotImplementedError() + +class XFSSync(FSSync): + """ Info application for XFS. """ + + ext = availability.application("xfs_freeze") + + def _freezeCommand(self): + return [str(self.ext), "-f", self.fs.systemMountpoint] + + def _unfreezeCommand(self): + return [str(self.ext), "-u", self.fs.systemMountpoint] + + def doTask(self, root="/"): + # pylint: disable=arguments-differ + error_msgs = self.availabilityErrors + if error_msgs: + raise FSError("\n".join(error_msgs)) + + error_msg = None + try: + rc = util.run_program(self._freezeCommand(), root=root) + except OSError as e: + error_msg = "failed to sync filesytem: %s" % e + error_msg = error_msg or rc + + try: + rc = util.run_program(self._unfreezeCommand(), root=root) + except OSError as e: + error_msg = error_msg or "failed to sync filesystem: %s" % e + error_msg = error_msg or rc + + if error_msg: + raise FSError(error_msg) + +class UnimplementedFSSync(task.UnimplementedTask): + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs diff --git a/blivet/tasks/fswritelabel.py b/blivet/tasks/fswritelabel.py new file mode 100644 index 0000000..bcdff7c --- /dev/null +++ b/blivet/tasks/fswritelabel.py @@ -0,0 +1,116 @@ +# fswritelabel.py +# Filesystem label writing 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 util +from ..errors import FSWriteLabelError + +from . import availability +from . import task + +@add_metaclass(abc.ABCMeta) +class FSWriteLabel(task.BasicApplication): + """ An abstract class that represents writing a label for a filesystem. """ + + description = "write filesystem label" + + args = abc.abstractproperty(doc="arguments for writing a label") + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs + + # IMPLEMENTATION methods + + @property + def _setCommand(self): + """Get the command to label the filesystem. + + :return: the command + :rtype: list of str + """ + return [str(self.ext)] + self.args + + def doTask(self): + error_msgs = self.availabilityErrors + if error_msgs: + raise FSWriteLabelError("\n".join(error_msgs)) + + rc = util.run_program(self._setCommand) + if rc: + raise FSWriteLabelError("label failed") + +class DosFSWriteLabel(FSWriteLabel): + ext = availability.DOSFSLABEL_APP + + @property + def args(self): + return [self.fs.device, self.fs.label] + +class Ext2FSWriteLabel(FSWriteLabel): + ext = availability.E2LABEL_APP + + @property + def args(self): + return [self.fs.device, self.fs.label] + +class JFSWriteLabel(FSWriteLabel): + ext = availability.JFSTUNE_APP + + @property + def args(self): + return ["-L", self.fs.label, self.fs.device] + +class NTFSWriteLabel(FSWriteLabel): + ext = availability.NTFSLABEL_APP + + @property + def args(self): + return [self.fs.device, self.fs.label] + +class ReiserFSWriteLabel(FSWriteLabel): + ext = availability.application("reiserfstune") + + @property + def args(self): + return ["-l", self.fs.label, self.fs.device] + +class XFSWriteLabel(FSWriteLabel): + ext = availability.XFSADMIN_APP + + @property + def args(self): + return ["-L", self.fs.label if self.fs.label != "" else "--", self.fs.device] + +class UnimplementedFSWriteLabel(task.UnimplementedTask): + + def __init__(self, an_fs): + """ Initializer. + + :param FS an_fs: a filesystem object + """ + self.fs = an_fs diff --git a/tests/pylint/pylint-false-positives b/tests/pylint/pylint-false-positives index d33c566..c02fff7 100644 --- a/tests/pylint/pylint-false-positives +++ b/tests/pylint/pylint-false-positives @@ -20,6 +20,9 @@ ^blivet/blivet.py:[[:digit:]]+: [E1101(no-member), Blivet.savePassphrase] Instance of 'DeviceTree' has no '_DeviceTree__luksDevs' member$ ^blivet/blivet.py:[[:digit:]]+: [E1101(no-member), Blivet.savePassphrase] Instance of 'DeviceTree' has no '_DeviceTree__passphrases' member$ ^blivet/formats/__init__.py:[[:digit:]]+: [W0201(attribute-defined-outside-init), DeviceFormat._setDevice] Attribute '_device' defined outside __init__$ +^blivet/tasks/.*py:[[:digit:]]+: [W0223(abstract-method), .*] Method 'available' is abstract in class 'Task' but is not overridden$ +^blivet/tasks/.*py:[[:digit:]]+: [W0223(abstract-method), .*] Method 'doTask' is abstract in class 'Task' but is not overridden$ +^blivet/tasks/.*py:[[:digit:]]+: [W0223(abstract-method), .*] Method 'doTask' is abstract in class 'UnimplementedTask' but is not overridden$ ^dm.c: [[:digit:]]+: not running as root returning empty list$ ^tests/devicelibs_test/mdraid_test.py:[[:digit:]]+: [E1003(bad-super-call), ([[:alnum:].]+).__init__] Bad first argument YES given to super()$ ^tests/devicelibs_test/raid_test.py:[[:digit:]]+: [E1120(no-value-for-parameter), [[:alnum:].]+] No value [[:alnum:] ]+ 'member_count' in [[:alnum:] ]+ call$
In reply to line 98 of blivet/tasks/fsminsize.py:
I think it would be nicer to assign the generator to a variable with a self-explanatory name and then use it in the for cycle.
In reply to line 139 of blivet/tasks/fsminsize.py:
Please split the two long lines here into multiple lines.
In reply to line 57 of blivet/tasks/fsmkfs.py:
This should be ``canLabel`` to match the coding style of the other properties and methods.
In reply to line 63 of blivet/tasks/fsmount.py:
``return errors`` here instead of ``break`` would be nicer.
In reply to line 87 of blivet/tasks/fsmount.py:
Something missing in this method?
In reply to line 97 of blivet/tasks/fsmount.py:
I think we shouldn't check ``options`` for being of a proper type and hide that bug if not. The docstring says it should be ``str`` or ``None`` (**neatpick:** should be ``NoneType``)
In reply to line 39 of blivet/tasks/fsresize.py:
extra blank line here
In reply to line 90 of blivet/tasks/fsresize.py:
I don't get how the comment relates to the line below it.
In reply to line 110 of blivet/tasks/fsresize.py:
We may as well fix the typo in ``supress`` while doing this. :)
In reply to line 50 of blivet/tasks/fssync.py:
"*Sync application*" maybe?
In reply to line 57 of blivet/tasks/fsmkfs.py:
ok.
In reply to line 87 of blivet/tasks/fsmount.py:
No. It gets overridden in a few places...but usually should do nothing.
In reply to line 97 of blivet/tasks/fsmount.py:
This is just preserving prior semantics...so I'll stick with it for now. also fixed.
In reply to line 39 of blivet/tasks/fsresize.py:
gone.
In reply to line 90 of blivet/tasks/fsresize.py:
Hopefully this is clearer. ``` + # No bytes specification is described in the man pages. A number without + # any suffix is interpreted as indicating the number of filesystem blocks. + # A suffix of "s" specifies a 512 byte sector. It is omitted here because + # the lookup is only by standard binary units.
```
In reply to line 110 of blivet/tasks/fsresize.py:
done!
In reply to line 50 of blivet/tasks/fssync.py:
Done.
In reply to line 90 of blivet/tasks/fsresize.py:
Absolutely. Thanks!
From: mulhern amulhern@redhat.com
Related: #12
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/fs.py | 846 ++++++++++-------------------------- blivet/formats/fslabel.py | 205 --------- blivet/formats/fslabeling.py | 148 ------- tests/formats_test/fs_test.py | 2 +- tests/formats_test/fslabeling.py | 8 +- tests/formats_test/fstesting.py | 14 +- tests/formats_test/labeling_test.py | 10 +- 7 files changed, 238 insertions(+), 995 deletions(-) delete mode 100644 blivet/formats/fslabel.py delete mode 100644 blivet/formats/fslabeling.py
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py index 3bb0ebe..2a9b488 100644 --- a/blivet/formats/fs.py +++ b/blivet/formats/fs.py @@ -26,8 +26,19 @@ import os import tempfile
-from . import fslabeling -from ..errors import FormatCreateError, FSError, FSResizeError +from ..tasks import fsck +from ..tasks import fsinfo +from ..tasks import fslabeling +from ..tasks import fsminsize +from ..tasks import fsmkfs +from ..tasks import fsmount +from ..tasks import fsreadlabel +from ..tasks import fsresize +from ..tasks import fssize +from ..tasks import fssync +from ..tasks import fswritelabel +from ..errors import FormatCreateError, FSError, FSReadLabelError +from ..errors import FSWriteLabelError, FSResizeError from . import DeviceFormat, register_device_format from .. import util from .. import platform @@ -36,8 +47,7 @@ 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 B, KiB, MiB, GiB, KB, MB, GB -from ..i18n import _, N_ +from ..i18n import N_ from .. import udev from ..mounts import mountsCache
@@ -49,22 +59,19 @@ class FS(DeviceFormat): """ Filesystem base class. """ _type = "Abstract Filesystem Class" # fs type name - _mountType = None # like _type but for passing to mount _name = None - _mkfs = "" # mkfs utility _modules = [] # kernel modules required for support - _resizefs = "" # resize utility _labelfs = None # labeling functionality - _fsck = "" # fs check utility - _fsckErrors = {} # fs check command error codes & msgs - _infofs = "" # fs info utility - _defaultFormatOptions = [] # default options passed to mkfs - _defaultMountOptions = ["defaults"] # default options passed to mount - _defaultCheckOptions = [] - _defaultInfoOptions = [] - _existingSizeFields = [] - _resizefsUnit = None - _fsProfileSpecifier = None # mkfs option specifying fsprofile + _fsckClass = fsck.UnimplementedFSCK + _infoClass = fsinfo.UnimplementedFSInfo + _minsizeClass = fsminsize.UnimplementedFSMinSize + _mkfsClass = fsmkfs.UnimplementedFSMkfs + _mountClass = fsmount.FSMount + _readlabelClass = fsreadlabel.UnimplementedFSReadLabel + _resizeClass = fsresize.UnimplementedFSResize + _sizeinfoClass = fssize.UnimplementedFSSize + _syncClass = fssync.UnimplementedFSSync + _writelabelClass = fswritelabel.UnimplementedFSWriteLabel
def __init__(self, **kwargs): """ @@ -87,10 +94,28 @@ def __init__(self, **kwargs): that you can specify the device at the last moment by specifying it via the 'device' kwarg to the :meth:`create` method. """ + if self.__class__ is FS: raise TypeError("FS is an abstract class.")
DeviceFormat.__init__(self, **kwargs) + + # Create task objects + self._info = self._infoClass(self) + self._fsck = self._fsckClass(self) + self._mkfs = self._mkfsClass(self) + self._mount = self._mountClass(self) + self._readlabel = self._readlabelClass(self) + self._resize = self._resizeClass(self) + self._sync = self._syncClass(self) + self._writelabel = self._writelabelClass(self) + + # These two may depend on info class, so create them after + self._minsize = self._minsizeClass(self) + self._sizeinfo = self._sizeinfoClass(self) + + self._current_info = None # info obtained by _info task + self.mountpoint = kwargs.get("mountpoint") self.mountopts = kwargs.get("mountopts") self.label = kwargs.get("label") @@ -103,7 +128,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.resizefsProg: + if flags.installer_mode and not self._resize.availabilityErrors: # if you want current/min size you have to call updateSizeInfo try: self.updateSizeInfo() @@ -146,7 +171,7 @@ def labeling(self):
:rtype: bool """ - return self._labelfs is not None + return (self._mkfs.can_label and not self._mkfs.availabilityErrors) or not self._writelabel.availabilityErrors
def relabels(self): """Returns True if it is possible to relabel this filesystem @@ -154,7 +179,7 @@ def relabels(self):
:rtype: bool """ - return self._labelfs is not None and self._labelfs.label_app is not None + return not self._writelabel.availabilityErrors
def labelFormatOK(self, label): """Return True if the label has an acceptable format for this @@ -206,11 +231,23 @@ def _getSize(self):
def updateSizeInfo(self): """ Update this filesystem's current and minimum size (for resize). """ - if not self.exists: - return
- self._size = Size(0) - self._minSize = self.__class__._minSize + # This method ensures: + # * If there are fsck errors, self._resizable is False. + # Note that if there is no fsck program, no errors are possible. + # * If it is not possible to obtain the current size of the + # filesystem by interrogating the filesystem, self._resizable + # is False (and self._size is 0). + # * _minInstanceSize is obtained or it is set to _size. Effectively + # this means that it is the actual minimum size, or if that + # cannot be obtained the actual current size of the device. + # If it was not possible to obtain the current size of the device + # then _minInstanceSize is 0, but since _resizable is False + # that information can not be used to shrink the filesystem below + # its unknown actual minimum size. + # * self._getMinSize() is only run if fsck succeeds and a current + # existing size can be obtained. + self._current_info = None self._minInstanceSize = Size(0) self._resizable = self.__class__._resizable
@@ -218,123 +255,69 @@ def updateSizeInfo(self): try: self.doCheck() except FSError: - errors = True + self._resizable = False raise - else: - errors = False finally: # try to gather current size info anyway - info = self._getFSInfo() - self._size = self._getExistingSize(info=info) - self._minSize = self._size # default to current size + self._size = Size(0) + try: + if self._info.implemented: + self._current_info = self._info.doTask() + except FSError as e: + log.info("Failed to obtain info for device %s: %s", self.device, e) + try: + self._size = self._sizeinfo.doTask() + self._minInstanceSize = self._size + except (FSError, NotImplementedError) as e: + log.warning("Failed to obtain current size for device %s: %s", self.device, e) + # We absolutely need a current size to enable resize. To shrink the # filesystem we need a real minimum size provided by the resize # tool. Failing that, we can default to the current size, # effectively disabling shrink. - if errors or self._size == Size(0): + if self._size == Size(0): self._resizable = False
- self._getMinSize(info=info) # force calculation of minimum size - - def _getMinSize(self, info=None): - pass + try: + result = self._minsize.doTask() + size = self._padSize(result) + if result < size: + log.debug("padding min size from %s up to %s", result, size) + else: + log.debug("using current size %s as min size", size) + self._minInstanceSize = size + except (FSError, NotImplementedError) as e: + log.warning("Failed to obtain minimum size for device %s: %s", self.device, e)
- def _getFSInfo(self): - buf = "" - if self.infofsProg and self.exists and \ - util.find_program_in_path(self.infofsProg): - argv = self._defaultInfoOptions + [ self.device ] - try: - buf = util.capture_output([self.infofsProg] + argv) - except OSError as e: - log.error("failed to gather fs info: %s", e) - - return buf - - def _getExistingSize(self, info=None): - """ Determine the size of this filesystem. - - Filesystem must exist. Each filesystem varies, but the general - procedure is to run the filesystem dump or info utility and read - the block size and number of blocks for the filesystem - and compute megabytes from that. - - The loop that reads the output from the infofsProg is meant - to be simple, but take in to account variations in output. - The general procedure: - 1) Capture output from infofsProg. - 2) Iterate over each line of the output: - a) Trim leading and trailing whitespace. - b) Break line into fields split on ' ' - c) If line begins with any of the strings in - _existingSizeFields, start at the end of - fields and take the first one that converts - to an int. Store this in the values list. - d) Repeat until the values list length equals - the _existingSizeFields length. - 3) If the length of the values list equals the length - of _existingSizeFields, compute the size of this - filesystem by multiplying all of the values together - to get bytes, then convert to megabytes. Return - this value. - 4) If we were unable to capture all fields, return 0. - - The caller should catch exceptions from this method. Any - exception raised indicates a need to change the fields we - are looking for, the command to run and arguments, or - something else. If you catch an exception from this method, - assume the filesystem cannot be resized. - - :keyword info: filesystem info buffer - :type info: str (output of :attr:`infofsProg`) - :returns: size of existing fs in MiB. - :rtype: float. + @property + def minSize(self): + # If self._minInstanceSize is not 0, then it should be no less than + # self._minSize, by definition, and since a non-zero value indicates + # that it was obtained, it is the preferred value. + # If self._minInstanceSize is less than self._minSize, + # but not 0, then there must be some mistake, so better to use + # self._minSize. + return max(self._minInstanceSize, self._minSize) + + def _padSize(self, size): + """ Return a size padded according to some inflating rules. + + This method was originally designed solely for minimum sizes, + and may only apply to them. + + :param size: any size + :type size: :class:`~.size.Size` + :returns: a padded size + :rtype: :class:`~.size.Size` """ - if not self._existingSizeFields: - return Size(0) - - size = Size(0) - if self.exists: - if info is None: - info = self._getFSInfo() - - try: - values = [] - for line in info.splitlines(): - found = False + # add some padding to the min size + padded = min(size * Decimal('1.1'), size + Size("500 MiB"))
- line = line.strip() - tmp = line.split() - tmp.reverse() + # 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)
- for field in self._existingSizeFields: - if line.startswith(field): - for subfield in tmp: - try: - values.append(int(subfield)) - found = True - break - except ValueError: - continue - - if found: - break - - if len(values) == len(self._existingSizeFields): - break - - if len(values) != len(self._existingSizeFields): - return Size(0) - - size = 1 - for value in values: - size *= value - - size = Size(size) - except Exception: # pylint: disable=broad-except - log_exception_info(log.error, "failed to obtain size of filesystem on %s", [self.device]) - - return size + return padded
@property def currentSize(self): @@ -348,34 +331,10 @@ def free(self): """ return max(Size(0), self.currentSize - self.minSize)
- def _getFormatOptions(self, options=None, do_labeling=False): - """Get a list of format options to be used when creating the - filesystem. - - :param options: any special options - :type options: list of str or None - :param bool do_labeling: True if labeling during filesystem creation, - otherwise False - """ - argv = [] - if options and isinstance(options, list): - argv.extend(options) - argv.extend(self.defaultFormatOptions) - if self._fsProfileSpecifier and self.fsprofile: - argv.extend([self._fsProfileSpecifier, self.fsprofile]) - - if do_labeling and self.label is not None: - if self.labelFormatOK(self.label): - argv.extend(self._labelfs.labelingArgs(self.label)) - else: - log.warning("Choosing not to apply label (%s) during creation of filesystem %s. Label format is unacceptable for this filesystem.", self.label, self.type) - - argv.append(self.device) - return argv
def _preCreate(self, **kwargs): super(FS, self)._preCreate(**kwargs) - if not self.mkfsProg: + if self._mkfs.availabilityErrors: return
def _create(self, **kwargs): @@ -390,19 +349,14 @@ def _create(self, **kwargs): if not self.formattable: return
- argv = self._getFormatOptions(options=kwargs.get("options"), - do_labeling=not self.relabels()) - super(FS, self)._create() - ret = 0 try: - ret = util.run_program([self.mkfsProg] + argv) - except OSError as e: + self._mkfs.doTask(options=kwargs.get("options"), label=not self.relabels()) + except FSWriteLabelError as e: + log.warning("Choosing not to apply label (%s) during creation of filesystem %s. Label format is unacceptable for this filesystem.", self.label, self.type) + except FSError as e: raise FormatCreateError(e, self.device)
- if ret: - raise FormatCreateError("format failed: %s" % ret, self.device) - def _postCreate(self, **kwargs): super(FS, self)._postCreate(**kwargs) if self.label is not None and self.relabels(): @@ -411,17 +365,6 @@ def _postCreate(self, **kwargs): except FSError as e: log.warning("Failed to write label (%s) for filesystem %s: %s", self.label, self.type, e)
- @property - def resizeArgs(self): - """ Returns the arguments for resizing the filesystem. - - Must be overridden by every class that has non-None _resizefs. - - :returns: arguments for resizing a filesystem. - :rtype: list of str - """ - return [] - def doResize(self): """ Resize this filesystem based on this instance's targetSize attr.
@@ -436,7 +379,7 @@ def doResize(self): if self.targetSize == self.currentSize: return
- if not self.resizefsProg: + if self._resize.availabilityErrors: return
# tmpfs mounts don't need an existing device node @@ -462,7 +405,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._resizefsUnit, + rounded = self.targetSize.roundToNearest(self._resize.unit, rounding=ROUND_DOWN)
# 1. target size was between the min size and max size values prior to @@ -483,38 +426,22 @@ 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._resizefsUnit), self.device) + "filesystem on %s", unitStr(self._resize.unit), self.device) return else: self.targetSize = rounded
try: - ret = util.run_program([self.resizefsProg] + self.resizeArgs) - except OSError as e: + self._resize.doTask() + except FSError as e: raise FSResizeError(e, self.device)
- if ret: - raise FSResizeError("resize failed: %s" % ret, self.device) - self.doCheck()
# XXX must be a smarter way to do this self._size = self.targetSize self.notifyKernel()
- def _getCheckArgs(self): - argv = [] - argv.extend(self.defaultCheckOptions) - argv.append(self.device) - return argv - - def _fsckFailed(self, rc): - # pylint: disable=unused-argument - return False - - def _fsckErrorMessage(self, rc): - return _("Unknown return code: %d.") % (rc,) - def doCheck(self): """ Run a filesystem check.
@@ -523,23 +450,13 @@ def doCheck(self): if not self.exists: raise FSError("filesystem has not been created")
- if not self.fsckProg: + if self._fsck.availabilityErrors: return
if not os.path.exists(self.device): raise FSError("device does not exist")
- try: - ret = util.run_program([self.fsckProg] + self._getCheckArgs()) - except OSError as e: - raise FSError("filesystem check failed: %s" % e) - - if self._fsckFailed(ret): - hdr = _("%(type)s filesystem check failure on %(device)s: ") % \ - {"type": self.type, "device": self.device} - - msg = self._fsckErrorMessage(ret) - raise FSError(hdr + msg) + self._fsck.doTask()
def loadModule(self): """Load whatever kernel module is required to support this filesystem.""" @@ -631,24 +548,6 @@ def _preSetup(self, **kwargs): chrootedMountpoint = os.path.normpath("%s/%s" % (chroot, mountpoint)) return self.systemMountpoint != chrootedMountpoint
- def _setupMountOptions(self, options): - """ The options actually used to mount the filesystem. - - These are not necessarily the same as either the default options - or the options argument. - - :param str options: the mount options passed to setup() method. - :returns: the mount options used by setup - :rtype: str - """ - if not options or not isinstance(options, str): - options = self.options - - if isinstance(self, BindFS): - options = "bind," + options - - return options - def _setup(self, **kwargs): """ Mount this filesystem.
@@ -658,7 +557,7 @@ def _setup(self, **kwargs): :keyword mountpoint: mountpoint (overrides self.mountpoint) :raises: FSError """ - options = self._setupMountOptions(kwargs.get("options", "")) + options = kwargs.get("options", "") chroot = kwargs.get("chroot", "/") mountpoint = kwargs.get("mountpoint") or self.mountpoint
@@ -668,23 +567,14 @@ def _setup(self, **kwargs): # #mountpoint = os.path.join(chroot, mountpoint) chrootedMountpoint = os.path.normpath("%s/%s" % (chroot, mountpoint)) - - try: - rc = util.mount(self.device, chrootedMountpoint, - fstype=self.mountType, - options=options) - except Exception as e: - raise FSError("mount failed: %s" % e) - - if rc: - raise FSError("mount failed: %s" % rc) + self._mount.doTask(chrootedMountpoint, options=options)
def _postSetup(self, **kwargs): - options = self._setupMountOptions(kwargs.get("options", "")) + options = kwargs.get("options", "") chroot = kwargs.get("chroot", "/") mountpoint = kwargs.get("mountpoint") or self.mountpoint
- if flags.selinux and "ro" not in options.split(",") and flags.installer_mode: + if flags.selinux and "ro" not in self._mount.mountOptions(options).split(",") and flags.installer_mode: ret = util.reset_file_context(mountpoint, chroot) if not ret: log.warning("Failed to reset SElinux context for newly mounted filesystem root directory to default.") @@ -734,27 +624,17 @@ def readLabel(self): :return: the filesystem's label :rtype: str
- Raises a FSError if the label can not be read. + Raises a FSReadLabelError if the label can not be read. """ if not self.exists: - raise FSError("filesystem has not been created") + raise FSReadLabelError("filesystem has not been created")
if not os.path.exists(self.device): - raise FSError("device does not exist") + raise FSReadLabelError("device does not exist")
- if not self.relabels() or not self._labelfs.label_app.reads: - raise FSError("no application to read label for filesystem %s" % self.type) - - (rc, out) = util.run_program_and_capture_output(self._labelfs.label_app.readLabelCommand(self)) - if rc: - raise FSError("read label failed") - - label = out.strip() - - if label == "": - return "" - else: - return self._labelfs.label_app.extractLabel(label) + if self._readlabel.availabilityErrors: + raise FSReadLabelError("can not read label for filesystem %s" % self.type) + return self._readlabel.doTask()
def writeLabel(self): """ Create a label for this filesystem. @@ -772,119 +652,55 @@ def writeLabel(self): if not self.exists: raise FSError("filesystem has not been created")
- if not self.relabels(): + if self._writelabel.availabilityErrors: raise FSError("no application to set label for filesystem %s" % self.type)
if not self.labelFormatOK(self.label): - raise FSError("bad label format for labelling application %s" % self._labelfs.label_app.name) + raise FSError("bad label format for labelling application %s" % self._writelabel)
if not os.path.exists(self.device): raise FSError("device does not exist")
- rc = util.run_program(self._labelfs.label_app.setLabelCommand(self)) - if rc: - raise FSError("label failed") - + self._writelabel.doTask() self.notifyKernel()
@property - def mkfsProg(self): - """ Program used to create filesystems of this type. """ - return self._mkfs - - @property - def fsckProg(self): - """ Program used to check filesystems of this type. """ - return self._fsck - - @property - def resizefsProg(self): - """ Program used to resize filesystems of this type. """ - return self._resizefs - - @property - def labelfsProg(self): - """ Program used to manage labels for this filesystem type. - - May be None if no such program exists. - """ - if self._labelfs and self._labelfs.label_app: - return self._labelfs.label_app.name - else: - return None - - @property - def infofsProg(self): - """ Program used to get information for this filesystem type. """ - return self._infofs - - @property def utilsAvailable(self): # we aren't checking for fsck because we shouldn't need it - for prog in [self.mkfsProg, self.resizefsProg, self.labelfsProg, - self.infofsProg]: - if not prog: - continue - - if not util.find_program_in_path(prog): - return False - - return True + tasks = (self._mkfs, self._resize, self._writelabel, self._info) + return all(not (t.implemented and t.availabilityErrors) for t in tasks)
@property def supported(self): log_method_call(self, supported=self._supported) - return self._supported and self.utilsAvailable + return super(FS, self).supported and self.utilsAvailable
@property - def mountable(self): - canmount = (self.mountType in kernel_filesystems) or \ - (os.access("/sbin/mount.%s" % (self.mountType,), os.X_OK)) - - # Still consider the filesystem type mountable if there exists - # an appropriate filesystem driver in the kernel modules directory. - if not canmount: - modpath = os.path.realpath(os.path.join("/lib/modules", os.uname()[2])) - if os.path.isdir(modpath): - modname = "%s.ko" % self.mountType - for _root, _dirs, files in os.walk(modpath): - if any(x.startswith(modname) for x in files): - return True - - return canmount + def controllable(self): + return super(FS, self).controllable and self.mountable
@property - def resizable(self): - """ Can formats of this filesystem type be resized? """ - return super(FS, self).resizable and self.utilsAvailable - - @property - def defaultFormatOptions(self): - """ Default options passed to mkfs for this filesystem type. """ - # return a copy to prevent modification - return self._defaultFormatOptions[:] + def mountable(self): + return not self._mount.availabilityErrors
@property - def defaultMountOptions(self): - """ Default options passed to mount for this filesystem type. """ - # return a copy to prevent modification - return self._defaultMountOptions[:] + def formattable(self): + return super(FS, self).formattable and not self._mkfs.availabilityErrors
@property - def defaultCheckOptions(self): - """ Default options passed to checker for this filesystem type. """ - # return a copy to prevent modification - return self._defaultCheckOptions[:] + def resizable(self): + """ Can formats of this filesystem type be resized? """ + return super(FS, self).resizable and not self._resize.availabilityErrors
def _getOptions(self): - return self.mountopts or ",".join(self.defaultMountOptions) + return self.mountopts or ",".join(self._mount.options)
def _setOptions(self, options): self.mountopts = options
@property def mountType(self): - return self._mountType or self._type + return self._mount.mountType
# These methods just wrap filesystem-specific methods in more # generically named methods so filesystems and formatted devices @@ -923,8 +739,21 @@ def status(self): return False return self.systemMountpoint is not None
- def sync(self, root="/"): - pass + def sync(self, root='/'): + """ Ensure that data we've written is at least in the journal. + + This is a little odd because xfs_freeze will only be + available under the install root. + """ + if not self.status or not self.systemMountpoint or \ + not self.systemMountpoint.startswith(root) or \ + self._sync.availabilityErrors: + return + + try: + self._sync.doTask(root) + except FSError as e: + log.error(e)
def populateKSData(self, data): super(FS, self).populateKSData(data) @@ -940,128 +769,35 @@ def populateKSData(self, data): class Ext2FS(FS): """ ext2 filesystem. """ _type = "ext2" - _mkfs = "mke2fs" _modules = ["ext2"] - _resizefs = "resize2fs" _labelfs = fslabeling.Ext2FSLabeling() - _fsck = "e2fsck" - _fsckErrors = {4: N_("File system errors left uncorrected."), - 8: N_("Operational error."), - 16: N_("Usage or syntax error."), - 32: N_("e2fsck cancelled by user request."), - 128: N_("Shared library error.")} _packages = ["e2fsprogs"] _formattable = True _supported = True _resizable = True _linuxNative = True _maxSize = Size("8 TiB") - _defaultCheckOptions = ["-f", "-p", "-C", "0"] _dump = True _check = True - _infofs = "dumpe2fs" - _defaultInfoOptions = ["-h"] - _existingSizeFields = ["Block count:", "Block size:"] - _resizefsUnit = MiB - _fsProfileSpecifier = "-T" + _fsckClass = fsck.Ext2FSCK + _infoClass = fsinfo.Ext2FSInfo + _minsizeClass = fsminsize.Ext2FSMinSize + _mkfsClass = fsmkfs.Ext2FSMkfs + _readlabelClass = fsreadlabel.Ext2FSReadLabel + _resizeClass = fsresize.Ext2FSResize + _sizeinfoClass = fssize.Ext2FSSize + _writelabelClass = fswritelabel.Ext2FSWriteLabel partedSystem = fileSystemType["ext2"]
- def _fsckFailed(self, rc): - for errorCode in self._fsckErrors.keys(): - if rc & errorCode: - return True - return False - - def _fsckErrorMessage(self, rc): - msg = '' - - for errorCode in self._fsckErrors.keys(): - if rc & errorCode: - msg += "\n" + _(self._fsckErrors[errorCode]) - - return msg.strip() - - def _getMinSize(self, info=None): - """ Set the minimum size for this filesystem in MiB. - - :keyword info: filesystem info buffer - :type info: str (output of :attr:`infofsProg`) - :rtype: None. - """ - size = self._minSize - blockSize = None - - if self.exists and os.path.exists(self.device): - if info is None: - # get block size - info = self._getFSInfo() - - for line in info.splitlines(): - if line.startswith("Block size:"): - blockSize = int(line.split(" ")[-1]) - - if blockSize is None: - raise FSError("failed to get block size for %s filesystem " - "on %s" % (self.mountType, self.device)) - - # get minimum size according to resize2fs - buf = util.capture_output([self.resizefsProg, - "-P", self.device]) - _size = None - for line in buf.splitlines(): - # line will look like: - # Estimated minimum size of the filesystem: 1148649 - (text, _sep, minSize) = line.partition(":") - if "minimum size of the filesystem" not in text: - continue - minSize = minSize.strip() - if not minSize: - break - _size = Size(int(minSize) * blockSize) - break - - if not _size: - log.warning("failed to get minimum size for %s filesystem " - "on %s", self.mountType, self.device) - else: - size = _size - orig_size = size - log.debug("size=%s, current=%s", size, self.currentSize) - # add some padding - size = min(size * Decimal('1.1'), - size + Size("500 MiB")) - # make sure that the padded and rounded min size is not larger - # than the current size - size = min(size.roundToNearest(self._resizefsUnit, - rounding=ROUND_UP), - self.currentSize) - if orig_size < size: - log.debug("padding min size from %s up to %s", orig_size, size) - else: - log.debug("using current size %s as min size", size) - - self._minInstanceSize = size - - @property - def minSize(self): - return self._minInstanceSize - - @property - def resizeArgs(self): - # No unit specifier is interpreted not as bytes, but block size. - FMT = {KiB: "%dK", MiB: "%dM", GiB: "%dG"}[self._resizefsUnit] - size_spec = FMT % self.targetSize.convertTo(self._resizefsUnit) - return ["-p", self.device, size_spec] - register_device_format(Ext2FS)
class Ext3FS(Ext2FS): """ ext3 filesystem. """ _type = "ext3" - _defaultFormatOptions = ["-t", "ext3"] _modules = ["ext3"] partedSystem = fileSystemType["ext3"] + _mkfsClass = fsmkfs.Ext3FSMkfs
# It is possible for a user to specify an fsprofile that defines a blocksize # smaller than the default of 4096 bytes and therefore to make liars of us @@ -1075,8 +811,8 @@ class Ext3FS(Ext2FS): class Ext4FS(Ext3FS): """ ext4 filesystem. """ _type = "ext4" - _defaultFormatOptions = ["-t", "ext4"] _modules = ["ext4"] + _mkfsClass = fsmkfs.Ext4FSMkfs partedSystem = fileSystemType["ext4"]
register_device_format(Ext4FS) @@ -1085,39 +821,29 @@ class Ext4FS(Ext3FS): class FATFS(FS): """ FAT filesystem. """ _type = "vfat" - _mkfs = "mkdosfs" _modules = ["vfat"] _labelfs = fslabeling.FATFSLabeling() - _fsck = "dosfsck" - _fsckErrors = {1: N_("Recoverable errors have been detected or dosfsck has " - "discovered an internal inconsistency."), - 2: N_("Usage error.")} _supported = True _formattable = True _maxSize = Size("1 TiB") _packages = [ "dosfstools" ] - _defaultMountOptions = ["umask=0077", "shortname=winnt"] - _defaultCheckOptions = ["-n"] + _fsckClass = fsck.DosFSCK + _mkfsClass = fsmkfs.FATFSMkfs + _mountClass = fsmount.FATFSMount + _readlabelClass = fsreadlabel.DosFSReadLabel + _writelabelClass = fswritelabel.DosFSWriteLabel # FIXME this should be fat32 in some cases partedSystem = fileSystemType["fat16"]
- def _fsckFailed(self, rc): - if rc >= 1: - return True - return False - - def _fsckErrorMessage(self, rc): - return _(self._fsckErrors[rc]) - register_device_format(FATFS)
class EFIFS(FATFS): _type = "efi" - _mountType = "vfat" _name = N_("EFI System Partition") _minSize = Size("50 MiB") _check = True + _mountClass = fsmount.EFIFSMount
@property def supported(self): @@ -1129,7 +855,6 @@ def supported(self): class BTRFS(FS): """ btrfs filesystem """ _type = "btrfs" - _mkfs = "mkfs.btrfs" _modules = ["btrfs"] _formattable = True _linuxNative = True @@ -1137,6 +862,7 @@ class BTRFS(FS): _packages = ["btrfs-progs"] _minSize = Size("256 MiB") _maxSize = Size("16 EiB") + _mkfsClass = fsmkfs.BTRFSMkfs # FIXME parted needs to be taught about btrfs so that we can set the # partition table type correctly for btrfs partitions # partedSystem = fileSystemType["btrfs"] @@ -1167,14 +893,13 @@ def _preSetup(self, **kwargs): class GFS2(FS): """ gfs2 filesystem. """ _type = "gfs2" - _mkfs = "mkfs.gfs2" _modules = ["dlm", "gfs2"] _formattable = True - _defaultFormatOptions = ["-j", "1", "-p", "lock_nolock", "-O"] _linuxNative = True _dump = True _check = True _packages = ["gfs2-utils"] + _mkfsClass = fsmkfs.GFS2Mkfs # FIXME parted needs to be thaught about btrfs so that we can set the # partition table type correctly for btrfs partitions # partedSystem = fileSystemType["gfs2"] @@ -1190,18 +915,17 @@ def supported(self): class JFS(FS): """ JFS filesystem """ _type = "jfs" - _mkfs = "mkfs.jfs" _modules = ["jfs"] _labelfs = fslabeling.JFSLabeling() - _defaultFormatOptions = ["-q"] _maxSize = Size("8 TiB") _formattable = True _linuxNative = True _dump = True _check = True - _infofs = "jfs_tune" - _defaultInfoOptions = ["-l"] - _existingSizeFields = ["Physical block size:", "Aggregate size:"] + _infoClass = fsinfo.JFSInfo + _mkfsClass = fsmkfs.JFSMkfs + _sizeinfoClass = fssize.JFSSize + _writelabelClass = fswritelabel.JFSWriteLabel partedSystem = fileSystemType["jfs"]
@property @@ -1215,18 +939,18 @@ def supported(self): class ReiserFS(FS): """ reiserfs filesystem """ _type = "reiserfs" - _mkfs = "mkreiserfs" _labelfs = fslabeling.ReiserFSLabeling() _modules = ["reiserfs"] - _defaultFormatOptions = ["-f", "-f"] _maxSize = Size("16 TiB") _formattable = True _linuxNative = True _dump = True _check = True _packages = ["reiserfs-utils"] - _infofs = "debugreiserfs" - _existingSizeFields = ["Count of blocks on the device:", "Blocksize:"] + _infoClass = fsinfo.ReiserFSInfo + _mkfsClass = fsmkfs.ReiserFSMkfs + _sizeinfoClass = fssize.ReiserFSSize + _writelabelClass = fswritelabel.ReiserFSWriteLabel partedSystem = fileSystemType["reiserfs"]
@property @@ -1240,50 +964,30 @@ def supported(self): class XFS(FS): """ XFS filesystem """ _type = "xfs" - _mkfs = "mkfs.xfs" _modules = ["xfs"] _labelfs = fslabeling.XFSLabeling() - _defaultFormatOptions = ["-f"] _maxSize = Size("16 EiB") _formattable = True _linuxNative = True _supported = True _packages = ["xfsprogs"] - _infofs = "xfs_db" - _defaultInfoOptions = ["-c", "sb 0", "-c", "p dblocks", - "-c", "p blocksize"] - _existingSizeFields = ["dblocks =", "blocksize ="] + _infoClass = fsinfo.XFSInfo + _mkfsClass = fsmkfs.XFSMkfs + _readlabelClass = fsreadlabel.XFSReadLabel + _sizeinfoClass = fssize.XFSSize + _syncClass = fssync.XFSSync + _writelabelClass = fswritelabel.XFSWriteLabel partedSystem = fileSystemType["xfs"]
- def sync(self, root='/'): - """ Ensure that data we've written is at least in the journal. - - This is a little odd because xfs_freeze will only be - available under the install root. - """ - if not self.status or not self.systemMountpoint or \ - not self.systemMountpoint.startswith(root): - return - - try: - util.run_program(["xfs_freeze", "-f", self.systemMountpoint], root=root) - except OSError as e: - log.error("failed to run xfs_freeze: %s", e) - - try: - util.run_program(["xfs_freeze", "-u", self.systemMountpoint], root=root) - except OSError as e: - log.error("failed to run xfs_freeze: %s", e)
register_device_format(XFS)
- class HFS(FS): _type = "hfs" - _mkfs = "hformat" _modules = ["hfs"] _labelfs = fslabeling.HFSLabeling() _formattable = True + _mkfsClass = fsmkfs.HFSMkfs partedSystem = fileSystemType["hfs"]
register_device_format(HFS) @@ -1291,11 +995,11 @@ class HFS(FS):
class AppleBootstrapFS(HFS): _type = "appleboot" - _mountType = "hfs" _name = N_("Apple Bootstrap") _minSize = Size("768 KiB") _maxSize = Size("1 MiB") _supported = True + _mountClass = fsmount.AppleBootstrapFSMount
@property def supported(self): @@ -1308,16 +1012,16 @@ class HFSPlus(FS): _type = "hfs+" _modules = ["hfsplus"] _udevTypes = ["hfsplus"] - _mkfs = "mkfs.hfsplus" - _fsck = "fsck.hfsplus" _packages = ["hfsplus-tools"] _labelfs = fslabeling.HFSPlusLabeling() _formattable = True - _mountType = "hfsplus" _minSize = Size("1 MiB") _maxSize = Size("2 TiB") _check = True partedSystem = fileSystemType["hfs+"] + _fsckClass = fsck.HFSPlusFSCK + _mkfsClass = fsmkfs.HFSPlusMkfs + _mountClass = fsmount.HFSPlusMount
register_device_format(HFSPlus)
@@ -1344,82 +1048,22 @@ def __init__(self, **kwargs): class NTFS(FS): """ ntfs filesystem. """ _type = "ntfs" - _mkfs = "mkntfs" - _resizefs = "ntfsresize" _labelfs = fslabeling.NTFSLabeling() - _fsck = "ntfsresize" _resizable = True _minSize = Size("1 MiB") _maxSize = Size("16 TiB") - _defaultMountOptions = ["defaults", "ro"] - _defaultCheckOptions = ["-c"] _packages = ["ntfsprogs"] - _infofs = "ntfsinfo" - _defaultInfoOptions = ["-m"] - _existingSizeFields = ["Cluster Size:", "Volume Size in Clusters:"] - _resizefsUnit = B + _fsckClass = fsck.NTFSFSCK + _infoClass = fsinfo.NTFSInfo + _minsizeClass = fsminsize.NTFSMinSize + _mkfsClass = fsmkfs.NTFSMkfs + _mountClass = fsmount.NTFSMount + _readlabelClass = fsreadlabel.NTFSReadLabel + _resizeClass = fsresize.NTFSResize + _sizeinfoClass = fssize.NTFSSize + _writelabelClass = fswritelabel.NTFSWriteLabel partedSystem = fileSystemType["ntfs"]
- def _fsckFailed(self, rc): - if rc != 0: - return True - return False - - def _getMinSize(self, info=None): - """ Set the minimum size for this filesystem. - - :keyword info: filesystem info buffer - :type info: str (output of :attr:`infofsProg`) - :rtype: None - """ - size = self._minSize - if self.exists and os.path.exists(self.device) and \ - util.find_program_in_path(self.resizefsProg): - minSize = None - buf = util.capture_output([self.resizefsProg, "-m", self.device]) - for l in buf.split("\n"): - if not l.startswith("Minsize"): - continue - try: - # ntfsresize uses SI unit prefixes - minSize = Size("%d mb" % int(l.split(":")[1].strip())) - except (IndexError, ValueError) as e: - minSize = None - log.warning("Unable to parse output for minimum size on %s: %s", self.device, e) - - if minSize is None: - log.warning("Unable to discover minimum size of filesystem " - "on %s", self.device) - else: - # add some padding to the min size - size = min(minSize * Decimal('1.1'), - minSize + Size("500 MiB")) - # make sure the padded and rounded min size is not larger than - # the current size - size = min(size.roundToNearest(self._resizefsUnit, - rounding=ROUND_UP), - self.currentSize) - if minSize < size: - log.debug("padding min size from %s up to %s", minSize, size) - else: - log.debug("using current size %s as min size", size) - - self._minInstanceSize = size - - @property - def minSize(self): - return self._minInstanceSize - - @property - def resizeArgs(self): - FMT = {B: "%d", KB: "%dK", MB: "%dM", GB: "%dG"}[self._resizefsUnit] - size_spec = FMT % self.targetSize.convertTo(self._resizefsUnit) - - # You must supply at least two '-f' options to ntfsresize or - # the proceed question will be presented to you. - return ["-ff", "-s", size_spec, self.device] - - register_device_format(NTFS)
@@ -1428,16 +1072,13 @@ class NFS(FS): """ NFS filesystem. """ _type = "nfs" _modules = ["nfs"] + _mountClass = fsmount.NFSMount
def _deviceCheck(self, devspec): if devspec is not None and ":" not in devspec: return "device must be of the form <host>:<path>" return None
- @property - def mountable(self): - return False - register_device_format(NFS)
@@ -1453,7 +1094,7 @@ class Iso9660FS(FS): """ ISO9660 filesystem. """ _type = "iso9660" _supported = True - _defaultMountOptions = ["ro"] + _mountClass = fsmount.Iso9660FSMount
register_device_format(Iso9660FS)
@@ -1461,6 +1102,7 @@ class Iso9660FS(FS): class NoDevFS(FS): """ nodev filesystem base class """ _type = "nodev" + _mountClass = fsmount.NoDevFSMount
def __init__(self, **kwargs): FS.__init__(self, **kwargs) @@ -1474,10 +1116,6 @@ def _deviceCheck(self, devspec): def type(self): return self.device
- @property - def mountType(self): - return self.device # this is probably set to the real/specific fstype - def notifyKernel(self): # NoDevFS should not need to tell the kernel anything. pass @@ -1488,7 +1126,7 @@ def notifyKernel(self): class DevPtsFS(NoDevFS): """ devpts filesystem. """ _type = "devpts" - _defaultMountOptions = ["gid=5", "mode=620"] + _mountClass = fsmount.DevPtsFSMount
register_device_format(DevPtsFS)
@@ -1511,8 +1149,6 @@ class TmpFS(NoDevFS): _supported = True # remounting can be used to change # the size of a live tmpfs mount - _resizefs = "mount" - _resizefsUnit = MiB # as tmpfs is part of the Linux kernel, # it is Linux-native _linuxNative = True @@ -1520,6 +1156,9 @@ class TmpFS(NoDevFS): # in the regard that the format is automatically created # once mounted _formattable = True + _sizeinfoClass = fssize.TmpFSSize + _mountClass = fsmount.TmpFSMount + _resizeClass = fsresize.TmpFSResize
def __init__(self, **kwargs): NoDevFS.__init__(self, **kwargs) @@ -1552,35 +1191,6 @@ def destroy(self, *args, **kwargs): """ pass
- def _getExistingSize(self, info=None): - """ Get current size of tmpfs filesystem using df. - - :param NoneType info: a dummy parameter - :rtype: Size - :returns: the current size of the filesystem, 0 if not found. - """ - if not self.status: - return Size(0) - - df = ["df", self.systemMountpoint, "--output=size"] - try: - (ret, out) = util.run_program_and_capture_output(df) - except OSError: - return Size(0) - - if ret: - return Size(0) - - lines = out.split() - if len(lines) != 2 or lines[0] != "1K-blocks": - return Size(0) - - return Size("%s KiB" % lines[1]) - - @property - def mountable(self): - return True - def _sizeOption(self, size): """ Returns a size option string appropriate for mounting tmpfs.
@@ -1593,8 +1203,7 @@ def _sizeOption(self, size): This is not impossible, since a special option for mounting is size=<percentage>%. """ - FMT = {KiB: "%dk", MiB: "%dm", GiB: "%dg"}[self._resizefsUnit] - return "size=%s" % (FMT % size.convertTo(self._resizefsUnit)) + return "size=%s" % (self._resize.size_fmt % size.convertTo(self._resize.unit))
def _getOptions(self): # Returns the regular mount options with the special size option, @@ -1640,12 +1249,6 @@ def _setDevice(self, value): # same, nothing actually needs to be set pass
- @property - def resizeArgs(self): - opts = super(TmpFS, self)._getOptions() - options = ("remount", opts, self._sizeOption(self.targetSize)) - return ['-o', ",".join(options), self._type, self.systemMountpoint] - def doResize(self): # Override superclass method to record whether mount options # should include an explicit size specification. @@ -1658,20 +1261,14 @@ def doResize(self):
class BindFS(FS): _type = "bind" - - @property - def mountable(self): - return True + _mountClass = fsmount.BindFSMount
register_device_format(BindFS)
class SELinuxFS(NoDevFS): _type = "selinuxfs" - - @property - def mountable(self): - return flags.selinux and super(SELinuxFS, self).mountable + _mountClass = fsmount.SELinuxFSMount
register_device_format(SELinuxFS)
@@ -1680,4 +1277,3 @@ class USBFS(NoDevFS): _type = "usbfs"
register_device_format(USBFS) - diff --git a/blivet/formats/fslabel.py b/blivet/formats/fslabel.py deleted file mode 100644 index 81c18e6..0000000 --- a/blivet/formats/fslabel.py +++ /dev/null @@ -1,205 +0,0 @@ -# fslabel.py -# Filesystem labeling classes for anaconda's storage configuration module. -# -# Copyright (C) 2013 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 -import re - -from six import add_metaclass - -from .. import errors - -@add_metaclass(abc.ABCMeta) -class FSLabelApp(object): - """An abstract class that represents actions associated with a - filesystem's labeling application. - """ - - name = abc.abstractproperty( - doc="The name of the filesystem labeling application.") - - reads = abc.abstractproperty( - doc="Whether this application can read a label as well as write one.") - - _label_regex = abc.abstractproperty( - doc="Matches the string output by the label reading application.") - - @abc.abstractmethod - def _writeLabelArgs(self, fs): - """Returns a list of the arguments for writing a label. - - :param FS fs: a filesystem object - - :return: the arguments - :rtype: list of str - - It can be assumed in this function that fs.label is a str. - """ - raise NotImplementedError - - def setLabelCommand(self, fs): - """Get the command to label the filesystem. - - :param FS fs: a filesystem object - :return: the command - :rtype: list of str - - Raises an exception if fs.label is None. - """ - if fs.label is None: - raise errors.FSError("makes no sense to write a label when accepting default label") - return [self.name] + self._writeLabelArgs(fs) - - @abc.abstractmethod - def _readLabelArgs(self, fs): - """Returns a list of arguments for reading a label. - - :param FS fs: a filesystem object - :return: the arguments - :rtype: list of str - """ - raise NotImplementedError - - def readLabelCommand(self, fs): - """Get the command to read the filesystem label. - - :param FS fs: a filesystem object - :return: the command - :rtype: list of str - - Raises an FSError if this application can not read the label. - """ - if not self.reads: - raise errors.FSError("Application %s can not read the filesystem label." % self.name) - return [self.name] + self._readLabelArgs(fs) - - def extractLabel(self, labelstr): - """Extract the label from an output string. - - :param str labelstr: the string containing the label information - - :return: the label - :rtype: str - - Raises an FSError if the label can not be extracted. - """ - if not self.reads or self._label_regex is None: - raise errors.FSError("Unknown format for application %s" % self.name) - match = re.match(self._label_regex, labelstr) - if match is None: - raise errors.FSError("Unknown format for application %s" % self.name) - return match.group('label') - - -class E2Label(FSLabelApp): - """Application used by ext2 and its descendants.""" - - name = "e2label" - reads = True - - _label_regex = r'(?P<label>.*)' - - def _writeLabelArgs(self, fs): - return [fs.device, fs.label] - - def _readLabelArgs(self, fs): - return [fs.device] - -E2Label = E2Label() - -class DosFsLabel(FSLabelApp): - """Application used by FATFS.""" - - name = "dosfslabel" - reads = True - - _label_regex = r'(?P<label>.*)' - - def _writeLabelArgs(self, fs): - return [fs.device, fs.label] - - def _readLabelArgs(self, fs): - return [fs.device] - -DosFsLabel = DosFsLabel() - -class JFSTune(FSLabelApp): - """Application used by JFS.""" - - name = "jfs_tune" - reads = False - - _label_regex = property(lambda s: None) - - def _writeLabelArgs(self, fs): - return ["-L", fs.label, fs.device] - - def _readLabelArgs(self, fs): - raise NotImplementedError - -JFSTune = JFSTune() - -class ReiserFSTune(FSLabelApp): - """Application used by ReiserFS.""" - - name = "reiserfstune" - reads = False - - _label_regex = None - - def _writeLabelArgs(self, fs): - return ["-l", fs.label, fs.device] - - def _readLabelArgs(self, fs): - raise NotImplementedError - -ReiserFSTune = ReiserFSTune() - -class XFSAdmin(FSLabelApp): - """Application used by XFS.""" - - name = "xfs_admin" - reads = True - - _label_regex = r'label = "(?P<label>.*)"' - - def _writeLabelArgs(self, fs): - return ["-L", fs.label if fs.label != "" else "--", fs.device] - - def _readLabelArgs(self, fs): - return ["-l", fs.device] - -XFSAdmin = XFSAdmin() - -class NTFSLabel(FSLabelApp): - """Application used by NTFS.""" - - name = "ntfslabel" - reads = True - - _label_regex = r'(?P<label>.*)' - - def _writeLabelArgs(self, fs): - return [fs.device, fs.label] - - def _readLabelArgs(self, fs): - return [fs.device] - -NTFSLabel = NTFSLabel() diff --git a/blivet/formats/fslabeling.py b/blivet/formats/fslabeling.py deleted file mode 100644 index 0abeaf7..0000000 --- a/blivet/formats/fslabeling.py +++ /dev/null @@ -1,148 +0,0 @@ -# fslabeling.py -# Filesystem labeling classes for anaconda's storage configuration module. -# -# Copyright (C) 2014 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 fslabel - -@add_metaclass(abc.ABCMeta) -class FSLabeling(object): - """An abstract class that represents filesystem labeling actions. - """ - - default_label = abc.abstractproperty( - doc="Default label set on this filesystem at creation.") - - label_app = abc.abstractproperty( - doc="Post creation filesystem labeling application.") - - @abc.abstractmethod - def labelFormatOK(self, label): - """Returns True if this label is correctly formatted for this - filesystem, otherwise False. - - :param str label: the label for this filesystem - :rtype: bool - """ - raise NotImplementedError - - @abc.abstractmethod - def labelingArgs(self, label): - """Returns the arguments for writing the label during filesystem - creation. These arguments are intended to be passed to the - appropriate mkfs application. - - :param str label: the label to use - :return: the arguments - :rtype: list of str - """ - raise NotImplementedError - - -class Ext2FSLabeling(FSLabeling): - - default_label = "" - label_app = fslabel.E2Label - - def labelFormatOK(self, label): - return len(label) < 17 - - def labelingArgs(self, label): - return ["-L", label] - -class FATFSLabeling(FSLabeling): - - default_label = "NO NAME" - label_app = fslabel.DosFsLabel - - def labelFormatOK(self, label): - return len(label) < 12 - - def labelingArgs(self, label): - return ["-n", label] - -class JFSLabeling(FSLabeling): - - default_label = "" - label_app = fslabel.JFSTune - - def labelFormatOK(self, label): - return len(label) < 17 - - def labelingArgs(self, label): - return ["-L", label] - -class ReiserFSLabeling(FSLabeling): - - default_label = "" - label_app = fslabel.ReiserFSTune - - def labelFormatOK(self, label): - return len(label) < 17 - - def labelingArgs(self, label): - return ["-l", label] - -class XFSLabeling(FSLabeling): - - default_label = "" - label_app = fslabel.XFSAdmin - - def labelFormatOK(self, label): - return ' ' not in label and len(label) < 13 - - def labelingArgs(self, label): - return ["-L", label] - -class HFSLabeling(FSLabeling): - - default_label = "Untitled" - label_app = None - - def labelFormatOK(self, label): - return ':' not in label and len(label) < 28 and len(label) > 0 - - def labelingArgs(self, label): - return ["-l", label] - -class HFSPlusLabeling(FSLabeling): - - default_label = "Untitled" - label_app = None - - def labelFormatOK(self, label): - return ':' not in label and 0 < len(label) < 129 - - def labelingArgs(self, label): - return ["-v", label] - -class NTFSLabeling(FSLabeling): - - default_label = "" - label_app = fslabel.NTFSLabel - - def labelFormatOK(self, label): - return len(label) < 129 - - def labelingArgs(self, label): - return ["-L", label] diff --git a/tests/formats_test/fs_test.py b/tests/formats_test/fs_test.py index 834c759..2b22de3 100755 --- a/tests/formats_test/fs_test.py +++ b/tests/formats_test/fs_test.py @@ -137,7 +137,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._resizefsUnit, rounding=ROUND_DOWN)) + self.assertEqual(self.an_fs.size, newsize.roundToNearest(self.an_fs._resize.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/fslabeling.py b/tests/formats_test/fslabeling.py index 9c6dabb..4c76265 100644 --- a/tests/formats_test/fslabeling.py +++ b/tests/formats_test/fslabeling.py @@ -4,7 +4,7 @@ from six import add_metaclass
from tests import loopbackedtestcase -from blivet.errors import FSError +from blivet.errors import FSError, FSReadLabelError from blivet.size import Size
@add_metaclass(abc.ABCMeta) @@ -40,11 +40,11 @@ def testLabeling(self): an_fs = self._fs_class(device=self.loopDevices[0], label=self._invalid_label) self.assertIsNone(an_fs.create())
- with self.assertRaisesRegexp(FSError, "no application to read label"): + with self.assertRaises(FSReadLabelError): an_fs.readLabel()
an_fs.label = "an fs" - with self.assertRaisesRegexp(FSError, "no application to set label for filesystem"): + with self.assertRaises(FSError): an_fs.writeLabel()
def testCreating(self): @@ -81,7 +81,7 @@ def testLabeling(self): an_fs = self._fs_class(device=self.loopDevices[0], label=self._invalid_label) self.assertIsNone(an_fs.create())
- with self.assertRaisesRegexp(FSError, "no application to read label"): + with self.assertRaises(FSReadLabelError): an_fs.readLabel()
an_fs.label = "an fs" diff --git a/tests/formats_test/fstesting.py b/tests/formats_test/fstesting.py index 9a5d92e..59f8100 100644 --- a/tests/formats_test/fstesting.py +++ b/tests/formats_test/fstesting.py @@ -47,7 +47,7 @@ def _test_sizes(self, an_fs): else: expected_size = _size # If the size can be obtained it will not be 0 - if an_fs._infofs: + if an_fs._info.implemented: self.assertNotEqual(expected_size, Size(0)) self.assertTrue(expected_size <= self._DEVICE_SIZE) # Otherwise it will be 0, assuming the device was not initialized @@ -57,7 +57,7 @@ def _test_sizes(self, an_fs): self.assertEqual(an_fs.size, expected_size)
# Only the resizable filesystems can figure out their current min size - if an_fs._resizefs: + if an_fs._resize: expected_min_size = min_size else: expected_min_size = an_fs._minSize @@ -82,7 +82,7 @@ def testInstantiation(self): self.assertFalse(an_fs.exists) self.assertIsNone(an_fs.device) self.assertIsNone(an_fs.uuid) - self.assertEqual(an_fs.options, ",".join(an_fs.defaultMountOptions)) + self.assertEqual(an_fs.options, ",".join(an_fs._mount.options)) self.assertEqual(an_fs.resizable, False)
# sizes @@ -185,7 +185,7 @@ def testMountpoint(self):
def testResize(self): an_fs = self._fs_class() - if not an_fs.formattable: + if not an_fs.formattable or not an_fs.resizable: return an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) @@ -210,7 +210,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._resizefsUnit, rounding=ROUND_DOWN) + ACTUAL_SIZE = TARGET_SIZE.roundToNearest(an_fs._resize.unit, rounding=ROUND_DOWN) self.assertEqual(an_fs.size, ACTUAL_SIZE) self.assertEqual(an_fs._size, ACTUAL_SIZE) self._test_sizes(an_fs) @@ -288,7 +288,7 @@ def testShrink(self): if isinstance(an_fs, fs.NTFS): return self.assertIsNone(an_fs.doResize()) - ACTUAL_SIZE = TARGET_SIZE.roundToNearest(an_fs._resizefsUnit, rounding=ROUND_DOWN) + ACTUAL_SIZE = TARGET_SIZE.roundToNearest(an_fs._resize.unit, rounding=ROUND_DOWN) self.assertEqual(an_fs._size, ACTUAL_SIZE) self._test_sizes(an_fs)
@@ -345,7 +345,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._resizefsUnit, rounding=ROUND_DOWN) + TARGET_SIZE = BIG_SIZE.roundToNearest(an_fs._resize.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) diff --git a/tests/formats_test/labeling_test.py b/tests/formats_test/labeling_test.py index dfa6e79..4f69e81 100755 --- a/tests/formats_test/labeling_test.py +++ b/tests/formats_test/labeling_test.py @@ -71,18 +71,18 @@ def testGetLabelArgs(self):
# ReiserFS uses a -l flag reiserfs = self.fs["reiserfs"] - self.assertEqual(reiserfs._labelfs.label_app.setLabelCommand(reiserfs), + self.assertEqual(reiserfs._writelabel._setCommand, ["reiserfstune", "-l", "myfs", "/dev"], msg="reiserfs")
# JFS, XFS use a -L flag lflag_classes = [fs.JFS, fs.XFS] - for name, klass in ((k, v) for k, v in self.fs.items() if any(isinstance(v, c) for c in lflag_classes)): - self.assertEqual(klass._labelfs.label_app.setLabelCommand(klass), [klass._labelfs.label_app.name, "-L", "myfs", "/dev"], msg=name) + for name, klass in [(k, v) for k, v in self.fs.items() if any(isinstance(v, c) for c in lflag_classes)]: + self.assertEqual(klass._writelabel._setCommand, [str(klass._writelabel.ext), "-L", "myfs", "/dev"], msg=name)
# Ext2FS and descendants and FATFS do not use a flag noflag_classes = [fs.Ext2FS, fs.FATFS] - for name, klass in ((k, v) for k, v in self.fs.items() if any(isinstance(v, c) for c in noflag_classes)): - self.assertEqual(klass._labelfs.label_app.setLabelCommand(klass), [klass._labelfs.label_app.name, "/dev", "myfs"], msg=name) + for name, klass in [(k, v) for k, v in self.fs.items() if any(isinstance(v, c) for c in noflag_classes)]: + self.assertEqual(klass._writelabel._setCommand, [str(klass._writelabel.ext), "/dev", "myfs"], msg=name)
# all of the remaining are non-labeling so will accept any label label = "Houston, we have a problem!"
From: mulhern amulhern@redhat.com
Related: #12
Make use of the availability information in method formattable, controllable, supported, destroyable.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/formats/__init__.py | 16 ++++++++++++++++ blivet/formats/luks.py | 18 ++++++++++++++++++ blivet/formats/lvmpv.py | 14 ++++++++++++++ blivet/formats/mdraid.py | 14 ++++++++++++++ blivet/formats/swap.py | 14 ++++++++++++++ 5 files changed, 76 insertions(+)
diff --git a/blivet/formats/__init__.py b/blivet/formats/__init__.py index ba15c62..4ec93c4 100644 --- a/blivet/formats/__init__.py +++ b/blivet/formats/__init__.py @@ -438,6 +438,12 @@ def _postDestroy(self, **kwargs): self.exists = False self.notifyKernel()
+ @property + def destroyable(self): + """ Do we have the facilities to destroy a format of this type. """ + # assumes wipefs is always available + return True + def setup(self, **kwargs): """ Activate the formatting.
@@ -459,6 +465,16 @@ def setup(self, **kwargs): self._setup(**kwargs) self._postSetup(**kwargs)
+ @property + def controllable(self): + """ Are external utilities available to allow this format to be both + setup and teared down. + + :returns: True if this format can be set up, otherwise False + :rtype: bool + """ + return True + def _preSetup(self, **kwargs): """ Return True if setup should proceed. """ if not self.exists: diff --git a/blivet/formats/luks.py b/blivet/formats/luks.py index 2d1353f..5fd116c 100644 --- a/blivet/formats/luks.py +++ b/blivet/formats/luks.py @@ -29,6 +29,7 @@ from . import DeviceFormat, register_device_format from ..flags import flags from ..i18n import _, N_ +from ..tasks import availability
import logging log = logging.getLogger("blivet") @@ -44,6 +45,7 @@ class LUKS(DeviceFormat): _linuxNative = True # for clearpart _packages = ["cryptsetup"] # required packages _minSize = crypto.LUKS_METADATA_SIZE + _plugin = availability.BLOCKDEV_CRYPTO_PLUGIN
def __init__(self, **kwargs): """ @@ -150,6 +152,18 @@ def hasKey(self): (self._key_file and os.access(self._key_file, os.R_OK)))
@property + def formattable(self): + return super(LUKS, self).formattable and self._plugin.available + + @property + def supported(self): + return super(LUKS, self).supported and self._plugin.available + + @property + def controllable(self): + return super(LUKS, self).controllable and self._plugin.available + + @property def configured(self): """ To be ready we need a key or passphrase and a map name. """ return self.hasKey and self.mapName @@ -203,6 +217,10 @@ def _postCreate(self, **kwargs): self.mapName = "luks-%s" % self.uuid
@property + def destroyable(self): + return self._plugin.available + + @property def keyFile(self): """ Path to key file to be used in /etc/crypttab """ return self._key_file diff --git a/blivet/formats/lvmpv.py b/blivet/formats/lvmpv.py index 3c9e5c0..c834d03 100644 --- a/blivet/formats/lvmpv.py +++ b/blivet/formats/lvmpv.py @@ -27,6 +27,7 @@ from ..storage_log import log_method_call from parted import PARTITION_LVM from ..devicelibs import lvm +from ..tasks import availability from ..i18n import N_ from ..size import Size from . import DeviceFormat, register_device_format @@ -47,6 +48,7 @@ class LVMPhysicalVolume(DeviceFormat): _minSize = lvm.LVM_PE_SIZE * 2 # one for metadata and one for data _packages = ["lvm2"] # required packages _ksMountpoint = "pv." + _plugin = availability.BLOCKDEV_LVM_PLUGIN
def __init__(self, **kwargs): """ @@ -95,6 +97,14 @@ def dict(self): "peStart": self.peStart, "dataAlignment": self.dataAlignment}) return d
+ @property + def formattable(self): + return super(LVMPhysicalVolume, self).formattable and self._plugin.available + + @property + def supported(self): + return super(LVMPhysicalVolume, self).supported and self._plugin.available + def _create(self, **kwargs): log_method_call(self, device=self.device, type=self.type, status=self.status) @@ -122,6 +132,10 @@ def _destroy(self, **kwargs): blockdev.lvm_pvscan(self.device)
@property + def destroyable(self): + return self._plugin.available + + @property def status(self): # XXX hack return (self.exists and self.vgName and diff --git a/blivet/formats/mdraid.py b/blivet/formats/mdraid.py index 1c7a02a..4590393 100644 --- a/blivet/formats/mdraid.py +++ b/blivet/formats/mdraid.py @@ -27,6 +27,7 @@ from . import DeviceFormat, register_device_format from ..flags import flags from ..i18n import N_ +from ..tasks import availability
import logging log = logging.getLogger("blivet") @@ -43,6 +44,7 @@ class MDRaidMember(DeviceFormat): _linuxNative = True # for clearpart _packages = ["mdadm"] # required packages _ksMountpoint = "raid." + _plugin = availability.BLOCKDEV_MDRAID_PLUGIN
def __init__(self, **kwargs): """ @@ -74,10 +76,22 @@ def dict(self): d.update({"mdUUID": self.mdUuid, "biosraid": self.biosraid}) return d
+ @property + def formattable(self): + return super(MDRaidMember, self).formattable and self._plugin.available + + @property + def supported(self): + return super(MDRaidMember, self).supported and self._plugin.available + def _destroy(self, **kwargs): blockdev.md_destroy(self.device)
@property + def destroyable(self): + return self._plugin.available + + @property def status(self): # XXX hack -- we don't have a nice way to see if the array is active return False diff --git a/blivet/formats/swap.py b/blivet/formats/swap.py index 4eab044..4983073 100644 --- a/blivet/formats/swap.py +++ b/blivet/formats/swap.py @@ -22,6 +22,7 @@
from parted import PARTITION_SWAP, fileSystemType from ..storage_log import log_method_call +from ..tasks import availability from . import DeviceFormat, register_device_format from ..size import Size from gi.repository import BlockDev as blockdev @@ -40,6 +41,7 @@ class SwapSpace(DeviceFormat): _formattable = True # can be formatted _supported = True # is supported _linuxNative = True # for clearpart + _plugin = availability.BLOCKDEV_SWAP_PLUGIN
#see rhbz#744129 for details _maxSize = Size("128 GiB") @@ -80,6 +82,18 @@ def dict(self): d.update({"priority": self.priority, "label": self.label}) return d
+ @property + def formattable(self): + return super(SwapSpace, self).formattable and self._plugin.available + + @property + def supported(self): + return super(SwapSpace, self).supported and self._plugin.available + + @property + def controllable(self): + return super(SwapSpace, self).controllable and self._plugin.available + def labeling(self): """Returns True as mkswap can write a label to the swap space.""" return True
From: mulhern amulhern@redhat.com
Related: #12
Signed-off-by: mulhern amulhern@redhat.com --- blivet/devices/btrfs.py | 3 +++ blivet/devices/disk.py | 3 +++ blivet/devices/dm.py | 4 ++++ blivet/devices/loop.py | 2 ++ blivet/devices/lvm.py | 2 ++ blivet/devices/md.py | 2 ++ blivet/devices/storage.py | 34 ++++++++++++++++++++++++++++++++++ 7 files changed, 50 insertions(+)
diff --git a/blivet/devices/btrfs.py b/blivet/devices/btrfs.py index 8c59443..f7cfe3b 100644 --- a/blivet/devices/btrfs.py +++ b/blivet/devices/btrfs.py @@ -42,10 +42,13 @@ from .container import ContainerDevice from .raid import RaidDevice
+from ..tasks import availability + class BTRFSDevice(StorageDevice): """ Base class for BTRFS volume and sub-volume devices. """ _type = "btrfs" _packages = ["btrfs-progs"] + _external_dependencies = [availability.BLOCKDEV_BTRFS_PLUGIN]
def __init__(self, *args, **kwargs): """ Passing None or no name means auto-generate one like btrfs.%d """ diff --git a/blivet/devices/disk.py b/blivet/devices/disk.py index 5f35ce6..a274bda 100644 --- a/blivet/devices/disk.py +++ b/blivet/devices/disk.py @@ -29,6 +29,7 @@ from ..storage_log import log_method_call from .. import udev from ..size import Size +from ..tasks import availability
from ..fcoe import fcoe
@@ -163,6 +164,7 @@ class DMRaidArrayDevice(DMDevice, ContainerDevice): _isDisk = True _formatClassName = property(lambda s: "dmraidmember") _formatUUIDAttr = property(lambda s: None) + _external_dependencies = [availability.BLOCKDEV_DM_PLUGIN]
def __init__(self, name, fmt=None, size=None, parents=None, sysfsPath=''): @@ -242,6 +244,7 @@ class MultipathDevice(DMDevice): _packages = ["device-mapper-multipath"] _partitionable = True _isDisk = True + _external_dependencies = [availability.application("multipath")]
def __init__(self, name, fmt=None, size=None, serial=None, parents=None, sysfsPath=''): diff --git a/blivet/devices/dm.py b/blivet/devices/dm.py index e834abf..ed1b682 100644 --- a/blivet/devices/dm.py +++ b/blivet/devices/dm.py @@ -28,6 +28,7 @@ from .. import util from ..storage_log import log_method_call from .. import udev +from ..tasks import availability
import logging log = logging.getLogger("blivet") @@ -39,6 +40,9 @@ class DMDevice(StorageDevice): """ A device-mapper device """ _type = "dm" _devDir = "/dev/mapper" + _external_dependencies = \ + [availability.application("kpartx"), availability.BLOCKDEV_DM_PLUGIN] +
def __init__(self, name, fmt=None, size=None, dmUuid=None, uuid=None, target=None, exists=False, parents=None, sysfsPath=''): diff --git a/blivet/devices/loop.py b/blivet/devices/loop.py index 6410443..3e2d5c4 100644 --- a/blivet/devices/loop.py +++ b/blivet/devices/loop.py @@ -24,6 +24,7 @@
from .. import errors from ..storage_log import log_method_call +from ..tasks import availability
import logging log = logging.getLogger("blivet") @@ -33,6 +34,7 @@ class LoopDevice(StorageDevice): """ A loop device. """ _type = "loop" + _external_dependencies = [availability.BLOCKDEV_LOOP_PLUGIN]
def __init__(self, name=None, fmt=None, size=None, sysfsPath=None, exists=False, parents=None): diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index a32dd60..5f01879 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -36,6 +36,7 @@ from ..storage_log import log_method_call from .. import udev from ..size import Size, KiB, MiB, ROUND_UP, ROUND_DOWN +from ..tasks import availability
import logging log = logging.getLogger("blivet") @@ -446,6 +447,7 @@ class LVMLogicalVolumeDevice(DMDevice): _resizable = True _packages = ["lvm2"] _containerClass = LVMVolumeGroupDevice + _external_dependencies = default=[availability.BLOCKDEV_LVM_PLUGIN]
def __init__(self, name, parents=None, size=None, uuid=None, copies=1, logSize=None, segType=None, diff --git a/blivet/devices/md.py b/blivet/devices/md.py index 588ef1e..7e9e090 100644 --- a/blivet/devices/md.py +++ b/blivet/devices/md.py @@ -33,6 +33,7 @@ from ..storage_log import log_method_call from .. import udev from ..size import Size +from ..tasks import availability
import logging log = logging.getLogger("blivet") @@ -48,6 +49,7 @@ class MDRaidArrayDevice(ContainerDevice, RaidDevice): _devDir = "/dev/md" _formatClassName = property(lambda s: "mdmember") _formatUUIDAttr = property(lambda s: "mdUuid") + _external_dependencies = [availability.BLOCKDEV_MDRAID_PLUGIN]
def __init__(self, name, level=None, major=None, minor=None, size=None, memberDevices=None, totalDevices=None, diff --git a/blivet/devices/storage.py b/blivet/devices/storage.py index 052b397..016435e 100644 --- a/blivet/devices/storage.py +++ b/blivet/devices/storage.py @@ -56,6 +56,7 @@ class StorageDevice(Device): _partitionable = False _isDisk = False _encrypted = False + _external_dependencies = []
def __init__(self, name, fmt=None, uuid=None, size=None, major=None, minor=None, @@ -748,3 +749,36 @@ def isNameValid(cls, name):
badchars = any(c in ('\x00', '/') for c in name) return not(badchars or name == '.' or name == '..') + + @property + def typeExternalDependencies(self): + """ A list of external dependencies of this device type. + + :returns: a set of external dependencies + :rtype: set of availability.ExternalResource + + The external dependencies include the dependencies of this + device type and of all superclass device types. + """ + return set( + d for p in self.__class__.__mro__ if issubclass(p, StorageDevice) for d in p._external_dependencies + ) + + @property + def externalDependencies(self): + """ A list of external dependencies of this device and its parents. + + :returns: the external dependencies of this device and all parents. + :rtype: set of availability.ExternalResource + """ + return set(d for p in self.ancestors for d in p.typeExternalDependencies) + + @property + def unavailableDependencies(self): + """ Any unavailable external dependencies of this device or its + parents. + + :returns: A list of unavailable external dependencies. + :rtype: set of availability.externalResource + """ + return set(e for e in self.externalDependencies if not e.available)
In reply to line 45 of blivet/devices/dm.py:
you can put this onto a single line
In reply to line 765 of blivet/devices/storage.py:
If possible (should be, I think), it would be better to split this into lines as "one ``for`` at a line"
From: mulhern amulhern@redhat.com
Related: #12
Signed-off-by: mulhern amulhern@redhat.com --- blivet/deviceaction.py | 11 +++++ tests/devices_test/dependencies_test.py | 86 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tests/devices_test/dependencies_test.py
diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py index 4d10765..212dee3 100644 --- a/blivet/deviceaction.py +++ b/blivet/deviceaction.py @@ -148,6 +148,11 @@ def __init__(self, device): util.ObjectID.__init__(self) if not isinstance(device, StorageDevice): raise ValueError("arg 1 must be a StorageDevice instance") + + unavailable_dependencies = device.unavailableDependencies + if unavailable_dependencies: + raise ValueError("device type %s requires unavailable_dependencies: %s" % (device.type, ", ".join(str(d) for d in unavailable_dependencies))) + self.device = device self.container = getattr(self.device, "container", None) self._applied = False @@ -516,6 +521,9 @@ def __init__(self, device, fmt=None): if self._format.exists: raise ValueError("specified format already exists")
+ if not fmt.formattable: + raise ValueError("resource to create this format %s is unavailable" % fmt) + def apply(self): """ apply changes related to the action to the device(s) """ if self._applied: @@ -638,6 +646,9 @@ def __init__(self, device): DeviceAction.__init__(self, device) self.origFormat = self.device.format
+ if not device.format.destroyable: + raise ValueError("resource to destroy this format type %s is unavailable" % device.format.type) + def apply(self): if self._applied: return diff --git a/tests/devices_test/dependencies_test.py b/tests/devices_test/dependencies_test.py new file mode 100644 index 0000000..2077216 --- /dev/null +++ b/tests/devices_test/dependencies_test.py @@ -0,0 +1,86 @@ +#!/usr/bin/python +# vim:set fileencoding=utf-8 + +import unittest + +from blivet.deviceaction import ActionCreateDevice +from blivet.deviceaction import ActionDestroyDevice + +from blivet.deviceaction import ActionCreateFormat +from blivet.deviceaction import ActionDestroyFormat + +from blivet.devices import DiskDevice +from blivet.devices import LUKSDevice +from blivet.devices import MDRaidArrayDevice +from blivet.devices import PartitionDevice + +from blivet.formats import getFormat + +from blivet.tasks import availability + +class FalseMethod(availability.Method): + def available(self, resource): + return False + +class TrueMethod(availability.Method): + def available(self, resource): + return True + +class DeviceDependenciesTestCase(unittest.TestCase): + """Test external device dependencies. """ + + def testDependencies(self): + dev1 = DiskDevice("name", fmt=getFormat("mdmember")) + dev2 = DiskDevice("other", fmt=getFormat("mdmember")) + dev = MDRaidArrayDevice("dev", level="raid1", parents=[dev1,dev2]) + luks = LUKSDevice("luks", parents=[dev]) + + # a parent's dependencies are a subset of its child's. + for d in dev.externalDependencies: + self.assertIn(d, luks.externalDependencies) + + # make sure that there's at least something in these dependencies + self.assertTrue(len(luks.externalDependencies) > 0) + +class MockingDeviceDependenciesTestCase(unittest.TestCase): + """Test availability of external device dependencies. """ + + def setUp(self): + dev1 = DiskDevice("name", fmt=getFormat("mdmember")) + dev2 = DiskDevice("other") + self.part = PartitionDevice("part", fmt=getFormat("mdmember"), parents=[dev2]) + self.dev = MDRaidArrayDevice("dev", level="raid1", parents=[dev1, self.part], fmt=getFormat("luks")) + self.luks = LUKSDevice("luks", parents=[self.dev], fmt=getFormat("ext4")) + + self.mdraid_method = availability.BLOCKDEV_MDRAID_PLUGIN._method + + def testAvailabilityMDRAIDplugin(self): + + # if the plugin is not in, there's nothing to test + self.assertIn(availability.BLOCKDEV_MDRAID_PLUGIN, self.luks.externalDependencies) + + # dev is not among its unavailable dependencies + availability.BLOCKDEV_MDRAID_PLUGIN._method = TrueMethod() + self.assertNotIn(availability.BLOCKDEV_MDRAID_PLUGIN, self.luks.unavailableDependencies) + self.assertIsNotNone(ActionCreateDevice(self.luks)) + self.assertIsNotNone(ActionDestroyDevice(self.luks)) + self.assertIsNotNone(ActionCreateFormat(self.luks, fmt=getFormat("macefi"))) + self.assertIsNotNone(ActionDestroyFormat(self.luks)) + + # dev is among the unavailable dependencies + availability.BLOCKDEV_MDRAID_PLUGIN._method = FalseMethod() + self.assertIn(availability.BLOCKDEV_MDRAID_PLUGIN, self.luks.unavailableDependencies) + with self.assertRaises(ValueError): + ActionCreateDevice(self.luks) + with self.assertRaises(ValueError): + ActionDestroyDevice(self.dev) + with self.assertRaises(ValueError): + ActionCreateFormat(self.dev) + with self.assertRaises(ValueError): + ActionDestroyFormat(self.dev) + + def tearDown(self): + availability.BLOCKDEV_MDRAID_PLUGIN._method = self.mdraid_method + +if __name__ == "__main__": + unittest.main()
In reply to line 154 of blivet/deviceaction.py:
This could be split into two lines by assigning ``", ".join(str(d) for d in unavailable_dependencies)`` to a variable (even to ``unavailable_dependencies``, I think).
In reply to line 154 of blivet/deviceaction.py:
done.
From: mulhern amulhern@redhat.com
Related: #12
But log info about what plugin was missing.
Signed-off-by: mulhern amulhern@redhat.com --- blivet/__init__.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/blivet/__init__.py b/blivet/__init__.py index e450970..8fc6c18 100644 --- a/blivet/__init__.py +++ b/blivet/__init__.py @@ -63,17 +63,27 @@ def cb(self, exn): log_bd_message = lambda level, msg: program_log.info(msg)
# initialize the libblockdev library +from gi.repository import GLib from gi.repository import BlockDev as blockdev -_REQUIRED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm")) -_required_plugins = blockdev.plugin_specs_from_names(_REQUIRED_PLUGIN_NAMES) +_REQUESTED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm")) +_requested_plugins = blockdev.plugin_specs_from_names(_REQUESTED_PLUGIN_NAMES) if not blockdev.is_initialized(): - if not blockdev.try_init(require_plugins=_required_plugins, log_func=log_bd_message): - raise RuntimeError("Failed to initialize the libblockdev library with all required plugins") + try: + blockdev.try_init(require_plugins=_requested_plugins, log_func=log_bd_message) + except GLib.GError: + pass else: avail_plugs = set(blockdev.get_available_plugin_names()) - if avail_plugs != _REQUIRED_PLUGIN_NAMES: - if not blockdev.reinit(require_plugins=_required_plugins, reload=False, log_func=log_bd_message): - raise RuntimeError("Failed to initialize the libblockdev library with all required plugins") + if avail_plugs != _REQUESTED_PLUGIN_NAMES: + try: + blockdev.reinit(require_plugins=_requested_plugins, reload=False, log_func=log_bd_message) + except GLib.GError: + pass + +avail_plugs = set(blockdev.get_available_plugin_names()) +missing_plugs = _REQUESTED_PLUGIN_NAMES - avail_plugs +for p in missing_plugs: + log.info("Failed to load plugin %s", p)
def enable_installer_mode(): """ Configure the module for use by anaconda (OS installer). """
From: mulhern amulhern@redhat.com
Related: #12
All plugin are no longer required, but libblockdev is, to run the plugins. dosfstools is no longer required. e2fsprogs remains because it is blivet's default filesystem type.
Signed-off-by: mulhern amulhern@redhat.com --- python-blivet.spec | 4 ---- 1 file changed, 4 deletions(-)
diff --git a/python-blivet.spec b/python-blivet.spec index 73d981b..605bae0 100644 --- a/python-blivet.spec +++ b/python-blivet.spec @@ -34,12 +34,10 @@ Requires: util-linux >= %{utillinuxver} Requires: python-pyudev Requires: parted >= %{partedver} Requires: pyparted >= %{pypartedver} -Requires: dosfstools Requires: e2fsprogs >= %{e2fsver} Requires: lsof Requires: libselinux-python Requires: libblockdev >= %{libblockdevver} -Requires: libblockdev-plugins-all >= %{libblockdevver} Requires: libselinux-python Requires: rpm
@@ -58,9 +56,7 @@ Requires: parted >= %{partedver} Requires: python3-pyparted >= %{pypartedver} Requires: libselinux-python3 Requires: libblockdev >= %{libblockdevver} -Requires: libblockdev-plugins-all >= %{libblockdevver} Requires: util-linux >= %{utillinuxver} -Requires: dosfstools Requires: e2fsprogs >= %{e2fsver} Requires: lsof
From: mulhern amulhern@redhat.com
Related: #12
Use the methods that check the availability of the specific tasks required. With the additional granularity can test a bunch more things.
Note that it is not possible to use resizable to check availability of test.
Signed-off-by: mulhern amulhern@redhat.com --- tests/formats_test/fslabeling.py | 18 ++++++++++++-- tests/formats_test/fstesting.py | 51 ++++++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 20 deletions(-)
diff --git a/tests/formats_test/fslabeling.py b/tests/formats_test/fslabeling.py index 4c76265..200cffe 100644 --- a/tests/formats_test/fslabeling.py +++ b/tests/formats_test/fslabeling.py @@ -26,8 +26,10 @@ def __init__(self, methodName='runTest'):
def setUp(self): an_fs = self._fs_class() - if not an_fs.utilsAvailable: - self.skipTest("utilities unavailable for filesystem %s" % an_fs.name) + if not an_fs.formattable: + self.skipTest("can not create filesystem %s" % an_fs.name) + if not an_fs.labeling(): + self.skipTest("can not label filesystem %s" % an_fs.name) super(LabelingAsRoot, self).setUp()
def testLabeling(self): @@ -38,6 +40,8 @@ def testLabeling(self): * raise an exception when relabeling the filesystem """ an_fs = self._fs_class(device=self.loopDevices[0], label=self._invalid_label) + if an_fs._readlabel.availabilityErrors or not an_fs.relabels(): + self.skipTest("can not read or write label for filesystem %s" % an_fs.name) self.assertIsNone(an_fs.create())
with self.assertRaises(FSReadLabelError): @@ -79,6 +83,8 @@ def testLabeling(self): * raise an exception when relabeling with an invalid label """ an_fs = self._fs_class(device=self.loopDevices[0], label=self._invalid_label) + if an_fs._readlabel.availabilityErrors or not an_fs.relabels(): + self.skipTest("can not read or write label for filesystem %s" % an_fs.name) self.assertIsNone(an_fs.create())
with self.assertRaises(FSReadLabelError): @@ -116,6 +122,8 @@ def testLabeling(self): * raise an exception when relabeling with an invalid label """ an_fs = self._fs_class(device=self.loopDevices[0], label=self._invalid_label) + if an_fs._readlabel.availabilityErrors or not an_fs.relabels(): + self.skipTest("can not read or write label for filesystem %s" % an_fs.name) self.assertIsNone(an_fs.create()) self.assertEqual(an_fs.readLabel(), an_fs._labelfs.default_label)
@@ -140,6 +148,8 @@ def testCreating(self): Verify that the filesystem has that label. """ an_fs = self._fs_class(device=self.loopDevices[0], label="start") + if an_fs._readlabel.availabilityErrors: + self.skipTest("can not read label for filesystem %s" % an_fs.name) self.assertIsNone(an_fs.create()) self.assertEqual(an_fs.readLabel(), "start")
@@ -148,6 +158,8 @@ def testCreatingNone(self): Verify that the filesystem has the default label. """ an_fs = self._fs_class(device=self.loopDevices[0], label=None) + if an_fs._readlabel.availabilityErrors: + self.skipTest("can not read label for filesystem %s" % an_fs.name) self.assertIsNone(an_fs.create()) self.assertEqual(an_fs.readLabel(), an_fs._labelfs.default_label)
@@ -156,5 +168,7 @@ def testCreatingEmpty(self): Verify that the filesystem has the empty label. """ an_fs = self._fs_class(device=self.loopDevices[0], label="") + if an_fs._readlabel.availabilityErrors: + self.skipTest("can not read label for filesystem %s" % an_fs.name) self.assertIsNone(an_fs.create()) self.assertEqual(an_fs.readLabel(), "") diff --git a/tests/formats_test/fstesting.py b/tests/formats_test/fstesting.py index 59f8100..25646da 100644 --- a/tests/formats_test/fstesting.py +++ b/tests/formats_test/fstesting.py @@ -26,9 +26,6 @@ def __init__(self, methodName='runTest'): super(FSAsRoot, self).__init__(methodName=methodName, deviceSpec=[self._DEVICE_SIZE])
def setUp(self): - an_fs = self._fs_class() - if not an_fs.utilsAvailable: - self.skipTest("utilities unavailable for filesystem %s" % an_fs.name) super(FSAsRoot, self).setUp()
def _test_sizes(self, an_fs): @@ -112,7 +109,7 @@ def testInstantiation(self): def testCreation(self): an_fs = self._fs_class() if not an_fs.formattable: - return + self.skipTest("can not create filesystem %s" % an_fs.name) an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) self.assertEqual(an_fs.resizable, False) @@ -131,7 +128,7 @@ def testCreation(self): def testLabeling(self): an_fs = self._fs_class() if not an_fs.labeling(): - return + self.skipTest("can not label filesystem %s" % an_fs.name) an_fs.device = self.loopDevices[0] an_fs.label = "label" self.assertTrue(an_fs.labelFormatOK("label")) @@ -145,7 +142,7 @@ def testLabeling(self): def testRelabeling(self): an_fs = self._fs_class() if not an_fs.labeling(): - return + self.skipTest("can not label filesystem %s" % an_fs.name) an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) an_fs.label = "label" @@ -160,9 +157,9 @@ def testMounting(self): an_fs = self._fs_class() # FIXME: BTRFS fails to mount if isinstance(an_fs, fs.BTRFS): - return + self.skipTest("no mounting filesystem %s" % an_fs.name) if not an_fs.formattable: - return + self.skipTest("can not create filesystem %s" % an_fs.name) an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) self.assertTrue(an_fs.testMount()) @@ -171,9 +168,9 @@ def testMountpoint(self): an_fs = self._fs_class() # FIXME: BTRFS fails to mount if isinstance(an_fs, fs.BTRFS): - return + self.skipTest("no mounting filesystem %s" % an_fs.name) if not an_fs.formattable or not an_fs.mountable: - return + self.skipTest("can not create or mount filesystem %s" % an_fs.name) an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) mountpoint = tempfile.mkdtemp() @@ -185,8 +182,8 @@ def testMountpoint(self):
def testResize(self): an_fs = self._fs_class() - if not an_fs.formattable or not an_fs.resizable: - return + if not an_fs.formattable: + self.skipTest("can not create filesystem %s" % an_fs.name) an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) an_fs.updateSizeInfo() @@ -225,9 +222,12 @@ def testNoExplicitTargetSize(self): # gets value of _size in constructor, so if _size is set to not-zero # in constructor call behavior would be different. if not self._resizable: - return + self.skipTest("Not checking resize for this test category.")
an_fs = self._fs_class() + if not an_fs.formattable: + self.skipTest("can not create filesystem %s" % an_fs.name) + an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) an_fs.updateSizeInfo() @@ -245,10 +245,13 @@ def testNoExplicitTargetSize2(self): resize action resizes filesystem to that size. """ if not self._resizable: - return + self.skipTest("Not checking resize for this test category.")
SIZE = Size("64 MiB") an_fs = self._fs_class(size=SIZE) + if not an_fs.formattable: + self.skipTest("can not create filesystem %s" % an_fs.name) + an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) an_fs.updateSizeInfo() @@ -264,9 +267,12 @@ def testNoExplicitTargetSize2(self):
def testShrink(self): if not self._resizable: - return + self.skipTest("Not checking resize for this test category.")
an_fs = self._fs_class() + if not an_fs.formattable: + self.skipTest("can not create filesystem %s" % an_fs.name) + an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) an_fs.updateSizeInfo() @@ -294,9 +300,12 @@ def testShrink(self):
def testTooSmall(self): if not self._resizable: - return + self.skipTest("Not checking resize for this test category.")
an_fs = self._fs_class() + if not an_fs.formattable: + self.skipTest("can not create or resize filesystem %s" % an_fs.name) + an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) an_fs.updateSizeInfo() @@ -311,9 +320,12 @@ def testTooSmall(self):
def testTooBig(self): if not self._resizable: - return + self.skipTest("Not checking resize for this test category.")
an_fs = self._fs_class() + if not an_fs.formattable: + self.skipTest("can not create filesystem %s" % an_fs.name) + an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) an_fs.updateSizeInfo() @@ -328,9 +340,12 @@ def testTooBig(self):
def testTooBig2(self): if not self._resizable: - return + self.skipTest("Not checking resize for this test category.")
an_fs = self._fs_class() + if not an_fs.formattable: + self.skipTest("can not create filesystem %s" % an_fs.name) + an_fs.device = self.loopDevices[0] self.assertIsNone(an_fs.create()) an_fs.updateSizeInfo()
From: mulhern amulhern@redhat.com
Related: #12
Check if the filesystem has resizing abilities instead.
Signed-off-by: mulhern amulhern@redhat.com --- tests/formats_test/fs_test.py | 14 ------------ tests/formats_test/fstesting.py | 49 +++++++++++++++++++++-------------------- 2 files changed, 25 insertions(+), 38 deletions(-)
diff --git a/tests/formats_test/fs_test.py b/tests/formats_test/fs_test.py index 2b22de3..71fa524 100755 --- a/tests/formats_test/fs_test.py +++ b/tests/formats_test/fs_test.py @@ -12,7 +12,6 @@
class Ext2FSTestCase(fstesting.FSAsRoot): _fs_class = fs.Ext2FS - _resizable = True
class Ext3FSTestCase(Ext2FSTestCase): _fs_class = fs.Ext3FS @@ -22,42 +21,34 @@ class Ext4FSTestCase(Ext3FSTestCase):
class FATFSTestCase(fstesting.FSAsRoot): _fs_class = fs.FATFS - _resizable = False
class EFIFSTestCase(FATFSTestCase): _fs_class = fs.EFIFS
class BTRFSTestCase(fstesting.FSAsRoot): _fs_class = fs.BTRFS - _resizable = False
@unittest.skip("Unable to create GFS2 filesystem.") class GFS2TestCase(fstesting.FSAsRoot): _fs_class = fs.GFS2 - _resizable = False
class JFSTestCase(fstesting.FSAsRoot): _fs_class = fs.JFS - _resizable = False
class ReiserFSTestCase(fstesting.FSAsRoot): _fs_class = fs.ReiserFS - _resizable = False
class XFSTestCase(fstesting.FSAsRoot): _fs_class = fs.XFS - _resizable = False
class HFSTestCase(fstesting.FSAsRoot): _fs_class = fs.HFS - _resizable = False
class AppleBootstrapFSTestCase(HFSTestCase): _fs_class = fs.AppleBootstrapFS
class HFSPlusTestCase(fstesting.FSAsRoot): _fs_class = fs.HFSPlus - _resizable = False
class MacEFIFSTestCase(HFSPlusTestCase): _fs_class = fs.MacEFIFS @@ -65,24 +56,20 @@ class MacEFIFSTestCase(HFSPlusTestCase): @unittest.skip("Unable to create because NTFS._formattable is False.") class NTFSTestCase(fstesting.FSAsRoot): _fs_class = fs.NTFS - _resizable = True
@unittest.skip("Unable to create because device fails deviceCheck().") class NFSTestCase(fstesting.FSAsRoot): _fs_class = fs.NFS - _resizable = False
class NFSv4TestCase(NFSTestCase): _fs_class = fs.NFSv4
class Iso9660FS(fstesting.FSAsRoot): _fs_class = fs.Iso9660FS - _resizable = False
@unittest.skip("Too strange to test using this framework.") class NoDevFSTestCase(fstesting.FSAsRoot): _fs_class = fs.NoDevFS - _resizable = False
class DevPtsFSTestCase(NoDevFSTestCase): _fs_class = fs.DevPtsFS @@ -104,7 +91,6 @@ class USBFSTestCase(NoDevFSTestCase):
class BindFSTestCase(fstesting.FSAsRoot): _fs_class = fs.BindFS - _resizable = False
class SimpleTmpFSTestCase(loopbackedtestcase.LoopBackedTestCase):
diff --git a/tests/formats_test/fstesting.py b/tests/formats_test/fstesting.py index 25646da..eb527f5 100644 --- a/tests/formats_test/fstesting.py +++ b/tests/formats_test/fstesting.py @@ -11,15 +11,21 @@ from blivet.size import Size, ROUND_DOWN from blivet.formats import fs
+def can_resize(an_fs): + """ Returns True if this filesystem has all necessary resizing tools + available. + + :param an_fs: a filesystem object + """ + resize_tasks = (an_fs._resize, an_fs._sizeinfo, an_fs._minsize) + return all(not t.availabilityErrors for t in resize_tasks) + @add_metaclass(abc.ABCMeta) class FSAsRoot(loopbackedtestcase.LoopBackedTestCase):
_fs_class = abc.abstractproperty( doc="The class of the filesystem being tested on.")
- _resizable = abc.abstractproperty( - doc="Should we expect to be able to resize this filesystem.") - _DEVICE_SIZE = Size("100 MiB")
def __init__(self, methodName='runTest'): @@ -83,7 +89,7 @@ def testInstantiation(self): self.assertEqual(an_fs.resizable, False)
# sizes - expected_min_size = Size(0) if self._resizable else an_fs._minSize + expected_min_size = Size(0) if can_resize(an_fs) else an_fs._minSize self.assertEqual(an_fs.minSize, expected_min_size)
self.assertEqual(an_fs.maxSize, an_fs._maxSize) @@ -97,7 +103,7 @@ def testInstantiation(self): an_fs = self._fs_class(size=NEW_SIZE)
# sizes - expected_min_size = Size(0) if self._resizable else an_fs._minSize + expected_min_size = Size(0) if can_resize(an_fs) else an_fs._minSize self.assertEqual(an_fs.minSize, expected_min_size)
self.assertEqual(an_fs.maxSize, an_fs._maxSize) @@ -116,7 +122,7 @@ def testCreation(self): self.assertTrue(an_fs.exists) self.assertIsNone(an_fs.doCheck())
- expected_min_size = Size(0) if self._resizable else an_fs._minSize + expected_min_size = Size(0) if can_resize(an_fs) else an_fs._minSize self.assertEqual(an_fs.minSize, expected_min_size)
self.assertEqual(an_fs.maxSize, an_fs._maxSize) @@ -192,7 +198,7 @@ def testResize(self): # CHECKME: target size is still 0 after updatedSizeInfo is called. self.assertEqual(an_fs.size, Size(0) if an_fs.resizable else an_fs._size)
- if not self._resizable: + if not can_resize(an_fs): self.assertFalse(an_fs.resizable) # Not resizable, so can not do resizing actions. with self.assertRaises(FSError): @@ -221,10 +227,10 @@ def testNoExplicitTargetSize(self): # _size if it is not set when size is calculated. Note that _targetSize # gets value of _size in constructor, so if _size is set to not-zero # in constructor call behavior would be different. - if not self._resizable: - self.skipTest("Not checking resize for this test category.")
an_fs = self._fs_class() + if not can_resize(an_fs): + self.skipTest("Not checking resize for this test category.") if not an_fs.formattable: self.skipTest("can not create filesystem %s" % an_fs.name)
@@ -244,11 +250,10 @@ def testNoExplicitTargetSize2(self): """ Because _targetSize has been set to size in constructor the resize action resizes filesystem to that size. """ - if not self._resizable: - self.skipTest("Not checking resize for this test category.") - SIZE = Size("64 MiB") an_fs = self._fs_class(size=SIZE) + if not can_resize(an_fs): + self.skipTest("Not checking resize for this test category.") if not an_fs.formattable: self.skipTest("can not create filesystem %s" % an_fs.name)
@@ -266,10 +271,9 @@ def testNoExplicitTargetSize2(self): self._test_sizes(an_fs)
def testShrink(self): - if not self._resizable: - self.skipTest("Not checking resize for this test category.") - an_fs = self._fs_class() + if not can_resize(an_fs): + self.skipTest("Not checking resize for this test category.") if not an_fs.formattable: self.skipTest("can not create filesystem %s" % an_fs.name)
@@ -299,10 +303,9 @@ def testShrink(self): self._test_sizes(an_fs)
def testTooSmall(self): - if not self._resizable: - self.skipTest("Not checking resize for this test category.") - an_fs = self._fs_class() + if not can_resize(an_fs): + self.skipTest("Not checking resize for this test category.") if not an_fs.formattable: self.skipTest("can not create or resize filesystem %s" % an_fs.name)
@@ -319,10 +322,9 @@ def testTooSmall(self): self._test_sizes(an_fs)
def testTooBig(self): - if not self._resizable: - self.skipTest("Not checking resize for this test category.") - an_fs = self._fs_class() + if not can_resize(an_fs): + self.skipTest("Not checking resize for this test category.") if not an_fs.formattable: self.skipTest("can not create filesystem %s" % an_fs.name)
@@ -339,10 +341,9 @@ def testTooBig(self): self._test_sizes(an_fs)
def testTooBig2(self): - if not self._resizable: - self.skipTest("Not checking resize for this test category.") - an_fs = self._fs_class() + if not can_resize(an_fs): + self.skipTest("Not checking resize for this test category.") if not an_fs.formattable: self.skipTest("can not create filesystem %s" % an_fs.name)
From: mulhern amulhern@redhat.com
Related: #12
These showed up because fewer tests are skipped with the finer granularity.
Signed-off-by: mulhern amulhern@redhat.com --- tests/formats_test/fstesting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tests/formats_test/fstesting.py b/tests/formats_test/fstesting.py index eb527f5..33fb488 100644 --- a/tests/formats_test/fstesting.py +++ b/tests/formats_test/fstesting.py @@ -50,7 +50,7 @@ def _test_sizes(self, an_fs): else: expected_size = _size # If the size can be obtained it will not be 0 - if an_fs._info.implemented: + if not an_fs._sizeinfo.availabilityErrors: self.assertNotEqual(expected_size, Size(0)) self.assertTrue(expected_size <= self._DEVICE_SIZE) # Otherwise it will be 0, assuming the device was not initialized @@ -60,7 +60,7 @@ def _test_sizes(self, an_fs): self.assertEqual(an_fs.size, expected_size)
# Only the resizable filesystems can figure out their current min size - if an_fs._resize: + if not an_fs._sizeinfo.availabilityErrors: expected_min_size = min_size else: expected_min_size = an_fs._minSize @@ -74,7 +74,7 @@ def _test_sizes(self, an_fs): self.assertEqual(an_fs.currentSize, _size)
# Free is the actual size - the minimum size - self.assertEqual(an_fs.free, _size - expected_min_size) + self.assertEqual(an_fs.free, max(Size(0), _size - expected_min_size))
# target size is set by side-effect self.assertEqual(an_fs.targetSize, an_fs._targetSize)
From: mulhern amulhern@redhat.com
Related: #12
Signed-off-by: mulhern amulhern@redhat.com --- tests/formats_test/selinux_test.py | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/tests/formats_test/selinux_test.py b/tests/formats_test/selinux_test.py index 516e07f..b5c62eb 100755 --- a/tests/formats_test/selinux_test.py +++ b/tests/formats_test/selinux_test.py @@ -28,6 +28,10 @@ def testMountingExt2FS(self): """ LOST_AND_FOUND_CONTEXT = 'system_u:object_r:lost_found_t:s0' an_fs = fs.Ext2FS(device=self.loopDevices[0], label="test") + + if not an_fs.formattable or not an_fs.mountable: + self.skipTest("can not create or mount filesystem %s" % an_fs.name) + self.assertIsNone(an_fs.create())
blivet.flags.installer_mode = False @@ -61,6 +65,10 @@ def testMountingExt2FS(self): def testMountingXFS(self): """ XFS does not have a lost+found directory. """ an_fs = fs.XFS(device=self.loopDevices[0], label="test") + + if not an_fs.formattable or not an_fs.mountable: + self.skipTest("can not create or mount filesystem %s" % an_fs.name) + self.assertIsNone(an_fs.create())
blivet.flags.installer_mode = False
Other that the (rather minor) comments above this looks good to me. With the exception of the dependecies being removed out out python-blivet.spec's scope where they belong (as I tried to explain in [the related Anaconda's PR] (https://github.com/rhinstaller/anaconda/pull/83))
Closed.
anaconda-patches@lists.fedorahosted.org