Mini Shell
# 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
import shutil
from six import add_metaclass
from .. import safe_dbus
from ..devicelibs.stratis import STRATIS_SERVICE, STRATIS_PATH
import gi
gi.require_version("BlockDev", "2.0")
gi.require_version("GLib", "2.0")
gi.require_version("Gio", "2.0")
from gi.repository import BlockDev as blockdev
from gi.repository import GLib, Gio
import logging
log = logging.getLogger("blivet")
CACHE_AVAILABILITY = True
class ExternalResource(object):
""" An external resource. """
def __init__(self, method, name):
""" Initializes an instance of an external resource.
:param method: A method object
:type method: :class:`Method`
:param str name: the name of the external resource
"""
self._method = method
self.name = name
self._availability_errors = None
def __str__(self):
return self.name
@property
def availability_errors(self):
""" Whether the resource has any availability errors.
:returns: [] if the resource is available
:rtype: list of str
"""
_errors = list()
# Prepare error cache and return value based on current caching setting.
if CACHE_AVAILABILITY:
_errors = self._availability_errors
else:
self._availability_errors = None
# Check for errors if necessary.
if self._availability_errors is None:
_errors = self._method.availability_errors(self)
# Update error cache if necessary.
if CACHE_AVAILABILITY and self._availability_errors is None:
self._availability_errors = _errors[:]
return _errors
@property
def available(self):
""" Whether the resource is available.
:returns: True if the resource is available
:rtype: bool
"""
return self.availability_errors == []
@add_metaclass(abc.ABCMeta)
class Method(object):
""" Method for determining if external resource is available."""
@abc.abstractmethod
def availability_errors(self, resource):
""" Returns [] if the resource is available.
:param resource: any external resource
:type resource: :class:`ExternalResource`
:returns: [] if the external resource is available
:rtype: list of str
"""
raise NotImplementedError()
class Path(Method):
""" Methods for when application is found in PATH. """
def availability_errors(self, resource):
""" Returns [] if the name of the application is in the path.
:param resource: any application
:type resource: :class:`ExternalResource`
:returns: [] if the name of the application is in the path
:rtype: list of str
"""
if not shutil.which(resource.name):
return ["application %s is not in $PATH" % resource.name]
else:
return []
Path = Path()
class AppVersionInfo(object):
def __init__(self, app_name, required_version, version_opt, version_regex):
""" Initializer.
:param str app_name: the name of the application
:param required_version: the required version for this application
:param version_opt: command line option to print version of this application
:param version_regex: regular expression to extract version from
output of @version_opt
:type required_version: :class:`distutils.LooseVersion` or NoneType
"""
self.app_name = app_name
self.required_version = required_version
self.version_opt = version_opt
self.version_regex = version_regex
def __str__(self):
return "%s-%s" % (self.app_name, self.required_version)
class VersionMethod(Method):
""" Methods for checking the version of the external resource. """
def __init__(self, version_info=None):
""" Initializer.
:param :class:`AppVersionInfo` version_info:
"""
self.version_info = version_info
self._availability_errors = None
def availability_errors(self, resource):
if self._availability_errors is not None and CACHE_AVAILABILITY:
return self._availability_errors[:]
self._availability_errors = Path.availability_errors(resource)
if self.version_info.required_version is None:
return self._availability_errors[:]
try:
ret = blockdev.utils.check_util_version(self.version_info.app_name,
self.version_info.required_version,
self.version_info.version_opt,
self.version_info.version_regex)
if not ret:
err = "installed version of %s is less than " \
"required version %s" % (self.version_info.app_name,
self.version_info.required_version)
self._availability_errors.append(err)
except blockdev.UtilsError as e:
err = "failed to get installed version of %s: %s" % (self.version_info.app_name, e)
self._availability_errors.append(err)
return self._availability_errors[:]
class BlockDevTechInfo(object):
def __init__(self, plugin_name, check_fn, technologies):
""" Initializer.
:param str plugin_name: the name of the libblockdev plugin
:param check_fn: function used to check for support availability
:param technologies: list of required technologies
"""
self.plugin_name = plugin_name
self.check_fn = check_fn
self.technologies = technologies
def __str__(self):
return "blockdev-%s" % self.plugin_name
class BlockDevMethod(Method):
""" Methods for when application is actually a libblockdev plugin. """
def __init__(self, tech_info):
""" Initializer.
:param :class:`AppVersionInfo` version_info:
"""
self._tech_info = tech_info
self._availability_errors = None
def _check_technologies(self):
errors = []
for tech, mode in self._tech_info.technologies.items():
try:
self._tech_info.check_fn(tech, mode)
except GLib.GError as e:
errors.append("%s: %s" % (tech.value_name, e.message))
return errors
def availability_errors(self, resource):
""" Returns [] if the plugin is loaded.
:param resource: a libblockdev plugin
:type resource: :class:`ExternalResource`
:returns: [] if the name of the plugin is loaded
:rtype: list of str
"""
if self._tech_info.plugin_name not in blockdev.get_available_plugin_names(): # pylint: disable=no-value-for-parameter
return ["libblockdev plugin %s not loaded" % self._tech_info.plugin_name]
else:
tech_missing = self._check_technologies()
if tech_missing:
return ["libblockdev plugin %s is loaded but some required "
"technologies are not available (%s)" % (self._tech_info.plugin_name, "; ".join(tech_missing))]
else:
return []
class DBusMethod(Method):
""" Methods for when application is actually a DBus service. """
def __init__(self, dbus_name, dbus_path):
""" Initializer.
:param :class:`AppVersionInfo` version_info:
"""
self.dbus_name = dbus_name
self.dbus_path = dbus_path
self._availability_errors = None
def availability_errors(self, resource):
""" Returns [] if the service is available.
:param resource: a DBus service
:type resource: :class:`ExternalResource`
:returns: [] if the name of the plugin is loaded
:rtype: list of str
"""
try:
avail = blockdev.utils.dbus_service_available(None, Gio.BusType.SYSTEM, self.dbus_name, self.dbus_path)
avail = safe_dbus.check_object_available(self.dbus_name, self.dbus_path)
except safe_dbus.DBusCallError:
return ["DBus service %s not available" % resource.name]
else:
if avail:
return []
else:
return ["DBus service %s not available" % resource.name]
class _UnavailableMethod(Method):
""" Method that indicates a resource is unavailable. """
def __init__(self, error_msg=None):
self.error_msg = error_msg or "always unavailable"
def availability_errors(self, resource):
return [self.error_msg]
UnavailableMethod = _UnavailableMethod()
class _AvailableMethod(Method):
""" Method that indicates a resource is available. """
def availability_errors(self, resource):
return []
AvailableMethod = _AvailableMethod()
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_version(name, version_method):
""" Construct an external resource that is an application.
This application will be available if its name can be found in $PATH
AND its version is at least the required version.
:param :class:`VersionMethod` version_method: the version method
"""
return ExternalResource(version_method, name)
def blockdev_plugin(name, blockdev_method):
""" Construct an external resource that is a libblockdev plugin. """
return ExternalResource(blockdev_method, name)
def dbus_service(name, dbus_method):
""" Construct an external resource that is a DBus service. """
return ExternalResource(dbus_method, name)
def unavailable_resource(name):
""" Construct an external resource that is always unavailable. """
return ExternalResource(UnavailableMethod, name)
def available_resource(name):
""" Construct an external resource that is always available. """
return ExternalResource(AvailableMethod, name)
# libblockdev btrfs plugin required technologies and modes
BLOCKDEV_BTRFS_ALL_MODES = (blockdev.BtrfsTechMode.CREATE |
blockdev.BtrfsTechMode.DELETE |
blockdev.BtrfsTechMode.MODIFY |
blockdev.BtrfsTechMode.QUERY)
BLOCKDEV_BTRFS = BlockDevTechInfo(plugin_name="btrfs",
check_fn=blockdev.btrfs_is_tech_avail,
technologies={blockdev.BtrfsTech.MULTI_DEV: BLOCKDEV_BTRFS_ALL_MODES,
blockdev.BtrfsTech.SUBVOL: BLOCKDEV_BTRFS_ALL_MODES,
blockdev.BtrfsTech.SNAPSHOT: BLOCKDEV_BTRFS_ALL_MODES})
BLOCKDEV_BTRFS_TECH = BlockDevMethod(BLOCKDEV_BTRFS)
# libblockdev crypto plugin required technologies and modes
BLOCKDEV_CRYPTO_ALL_MODES = (blockdev.CryptoTechMode.CREATE |
blockdev.CryptoTechMode.OPEN_CLOSE |
blockdev.CryptoTechMode.QUERY |
blockdev.CryptoTechMode.ADD_KEY |
blockdev.CryptoTechMode.REMOVE_KEY |
blockdev.CryptoTechMode.RESIZE)
BLOCKDEV_CRYPTO = BlockDevTechInfo(plugin_name="crypto",
check_fn=blockdev.crypto_is_tech_avail,
technologies={blockdev.CryptoTech.LUKS: BLOCKDEV_CRYPTO_ALL_MODES,
blockdev.CryptoTech.LUKS2: BLOCKDEV_CRYPTO_ALL_MODES,
blockdev.CryptoTech.ESCROW: blockdev.CryptoTechMode.CREATE})
BLOCKDEV_CRYPTO_TECH = BlockDevMethod(BLOCKDEV_CRYPTO)
BLOCKDEV_CRYPTO_INTEGRITY = BlockDevTechInfo(plugin_name="crypto",
check_fn=blockdev.crypto_is_tech_avail,
technologies={blockdev.CryptoTech.INTEGRITY: (blockdev.CryptoTechMode.CREATE |
blockdev.CryptoTechMode.OPEN_CLOSE |
blockdev.CryptoTechMode.QUERY)})
BLOCKDEV_CRYPTO_TECH_INTEGRITY = BlockDevMethod(BLOCKDEV_CRYPTO_INTEGRITY)
# libblockdev dm plugin required technologies and modes
BLOCKDEV_DM_ALL_MODES = (blockdev.DMTechMode.CREATE_ACTIVATE |
blockdev.DMTechMode.REMOVE_DEACTIVATE |
blockdev.DMTechMode.QUERY)
BLOCKDEV_DM = BlockDevTechInfo(plugin_name="dm",
check_fn=blockdev.dm_is_tech_avail,
technologies={blockdev.DMTech.MAP: BLOCKDEV_DM_ALL_MODES})
BLOCKDEV_DM_TECH = BlockDevMethod(BLOCKDEV_DM)
BLOCKDEV_DM_RAID = BlockDevTechInfo(plugin_name="dm",
check_fn=blockdev.dm_is_tech_avail,
technologies={blockdev.DMTech.RAID: BLOCKDEV_DM_ALL_MODES})
BLOCKDEV_DM_TECH_RAID = BlockDevMethod(BLOCKDEV_DM_RAID)
# libblockdev loop plugin required technologies and modes
BLOCKDEV_LOOP_ALL_MODES = (blockdev.LoopTechMode.CREATE |
blockdev.LoopTechMode.CREATE |
blockdev.LoopTechMode.DESTROY |
blockdev.LoopTechMode.MODIFY |
blockdev.LoopTechMode.QUERY)
BLOCKDEV_LOOP = BlockDevTechInfo(plugin_name="loop",
check_fn=blockdev.loop_is_tech_avail,
technologies={blockdev.LoopTech.LOOP_TECH_LOOP: BLOCKDEV_LOOP_ALL_MODES})
BLOCKDEV_LOOP_TECH = BlockDevMethod(BLOCKDEV_LOOP)
# libblockdev lvm plugin required technologies and modes
BLOCKDEV_LVM_ALL_MODES = (blockdev.LVMTechMode.CREATE |
blockdev.LVMTechMode.REMOVE |
blockdev.LVMTechMode.MODIFY |
blockdev.LVMTechMode.QUERY)
BLOCKDEV_LVM = BlockDevTechInfo(plugin_name="lvm",
check_fn=blockdev.lvm_is_tech_avail,
technologies={blockdev.LVMTech.BASIC: BLOCKDEV_LVM_ALL_MODES,
blockdev.LVMTech.BASIC_SNAP: BLOCKDEV_LVM_ALL_MODES,
blockdev.LVMTech.THIN: BLOCKDEV_LVM_ALL_MODES,
blockdev.LVMTech.CACHE: BLOCKDEV_LVM_ALL_MODES,
blockdev.LVMTech.CALCS: blockdev.LVMTechMode.QUERY,
blockdev.LVMTech.THIN_CALCS: blockdev.LVMTechMode.QUERY,
blockdev.LVMTech.CACHE_CALCS: blockdev.LVMTechMode.QUERY,
blockdev.LVMTech.GLOB_CONF: (blockdev.LVMTechMode.QUERY |
blockdev.LVMTechMode.MODIFY)})
BLOCKDEV_LVM_TECH = BlockDevMethod(BLOCKDEV_LVM)
if hasattr(blockdev.LVMTech, "VDO"):
BLOCKDEV_LVM_VDO = BlockDevTechInfo(plugin_name="lvm",
check_fn=blockdev.lvm_is_tech_avail,
technologies={blockdev.LVMTech.VDO: (blockdev.LVMTechMode.CREATE |
blockdev.LVMTechMode.REMOVE |
blockdev.LVMTechMode.QUERY)})
BLOCKDEV_LVM_TECH_VDO = BlockDevMethod(BLOCKDEV_LVM_VDO)
else:
BLOCKDEV_LVM_TECH_VDO = _UnavailableMethod(error_msg="Installed version of libblockdev doesn't support LVM VDO technology")
if hasattr(blockdev.LVMTech, "SHARED"):
BLOCKDEV_LVM_SHARED = BlockDevTechInfo(plugin_name="lvm",
check_fn=blockdev.lvm_is_tech_avail,
technologies={blockdev.LVMTech.SHARED: blockdev.LVMTechMode.MODIFY}) # pylint: disable=no-member
BLOCKDEV_LVM_TECH_SHARED = BlockDevMethod(BLOCKDEV_LVM_SHARED)
else:
BLOCKDEV_LVM_TECH_SHARED = _UnavailableMethod(error_msg="Installed version of libblockdev doesn't support shared LVM technology")
# libblockdev mdraid plugin required technologies and modes
BLOCKDEV_MD_ALL_MODES = (blockdev.MDTechMode.CREATE |
blockdev.MDTechMode.DELETE |
blockdev.MDTechMode.MODIFY |
blockdev.MDTechMode.QUERY)
BLOCKDEV_MD = BlockDevTechInfo(plugin_name="mdraid",
check_fn=blockdev.md_is_tech_avail,
technologies={blockdev.MDTech.MD_TECH_MDRAID: BLOCKDEV_MD_ALL_MODES})
BLOCKDEV_MD_TECH = BlockDevMethod(BLOCKDEV_MD)
# libblockdev mpath plugin required technologies and modes
BLOCKDEV_MPATH_ALL_MODES = (blockdev.MpathTechMode.MODIFY |
blockdev.MpathTechMode.QUERY)
BLOCKDEV_MPATH = BlockDevTechInfo(plugin_name="mpath",
check_fn=blockdev.mpath_is_tech_avail,
technologies={blockdev.MpathTech.BASE: BLOCKDEV_MPATH_ALL_MODES})
BLOCKDEV_MPATH_TECH = BlockDevMethod(BLOCKDEV_MPATH)
# libblockdev swap plugin required technologies and modes
BLOCKDEV_SWAP_ALL_MODES = (blockdev.SwapTechMode.CREATE |
blockdev.SwapTechMode.ACTIVATE_DEACTIVATE |
blockdev.SwapTechMode.QUERY |
blockdev.SwapTechMode.SET_LABEL)
BLOCKDEV_SWAP = BlockDevTechInfo(plugin_name="swap",
check_fn=blockdev.swap_is_tech_avail,
technologies={blockdev.SwapTech.SWAP_TECH_SWAP: BLOCKDEV_SWAP_ALL_MODES})
BLOCKDEV_SWAP_TECH = BlockDevMethod(BLOCKDEV_SWAP)
# libblockdev plugins
# we can't just check if the plugin is loaded, we also need to make sure
# that all technologies required by us our supported (some may be missing
# due to missing dependencies)
BLOCKDEV_BTRFS_PLUGIN = blockdev_plugin("libblockdev btrfs plugin", BLOCKDEV_BTRFS_TECH)
BLOCKDEV_CRYPTO_PLUGIN = blockdev_plugin("libblockdev crypto plugin", BLOCKDEV_CRYPTO_TECH)
BLOCKDEV_CRYPTO_PLUGIN_INTEGRITY = blockdev_plugin("libblockdev crypto plugin (integrity technology)",
BLOCKDEV_CRYPTO_TECH_INTEGRITY)
BLOCKDEV_DM_PLUGIN = blockdev_plugin("libblockdev dm plugin", BLOCKDEV_DM_TECH)
BLOCKDEV_DM_PLUGIN_RAID = blockdev_plugin("libblockdev dm plugin (raid technology)", BLOCKDEV_DM_TECH_RAID)
BLOCKDEV_LOOP_PLUGIN = blockdev_plugin("libblockdev loop plugin", BLOCKDEV_LOOP_TECH)
BLOCKDEV_LVM_PLUGIN = blockdev_plugin("libblockdev lvm plugin", BLOCKDEV_LVM_TECH)
BLOCKDEV_LVM_PLUGIN_VDO = blockdev_plugin("libblockdev lvm plugin (vdo technology)", BLOCKDEV_LVM_TECH_VDO)
BLOCKDEV_LVM_PLUGIN_SHARED = blockdev_plugin("libblockdev lvm plugin (shared LVM technology)", BLOCKDEV_LVM_TECH_SHARED)
BLOCKDEV_MDRAID_PLUGIN = blockdev_plugin("libblockdev mdraid plugin", BLOCKDEV_MD_TECH)
BLOCKDEV_MPATH_PLUGIN = blockdev_plugin("libblockdev mpath plugin", BLOCKDEV_MPATH_TECH)
BLOCKDEV_SWAP_PLUGIN = blockdev_plugin("libblockdev swap plugin", BLOCKDEV_SWAP_TECH)
# applications with versions
# we need e2fsprogs newer than 1.41 and we are checking the version by running
# the "e2fsck" tool and parsing its ouput for version number
E2FSPROGS_INFO = AppVersionInfo(app_name="e2fsck",
required_version="1.41.0",
version_opt="-V",
version_regex=r"e2fsck ([0-9+\.]+) .*")
E2FSPROGS_VERSION = VersionMethod(E2FSPROGS_INFO)
# new version of dosftools changed behaviour of many tools
DOSFSTOOLS_INFO = AppVersionInfo(app_name="mkdosfs",
required_version="4.2",
version_opt="--help",
version_regex=r"mkfs\.fat ([0-9+\.]+) .*")
DOSFSTOOLS_VERSION = VersionMethod(DOSFSTOOLS_INFO)
# applications
DEBUGREISERFS_APP = application("debugreiserfs")
DF_APP = application("df")
DOSFSCK_APP = application("dosfsck")
DOSFSLABEL_APP = application("dosfslabel")
DUMPE2FS_APP = application_by_version("dumpe2fs", E2FSPROGS_VERSION)
E2FSCK_APP = application_by_version("e2fsck", E2FSPROGS_VERSION)
E2LABEL_APP = application_by_version("e2label", E2FSPROGS_VERSION)
FSCK_HFSPLUS_APP = application("fsck.hfsplus")
HFORMAT_APP = application("hformat")
JFSTUNE_APP = application("jfs_tune")
KPARTX_APP = application("kpartx")
LVMDEVICES = application("lvmdevices")
MKDOSFS_APP = application("mkdosfs")
MKDOSFS_NEW_APP = application_by_version("mkdosfs", DOSFSTOOLS_VERSION)
MKE2FS_APP = application_by_version("mke2fs", E2FSPROGS_VERSION)
MKFS_BTRFS_APP = application("mkfs.btrfs")
MKFS_GFS2_APP = application("mkfs.gfs2")
MKFS_HFSPLUS_APP = application("mkfs.hfsplus")
MKFS_JFS_APP = application("mkfs.jfs")
MKFS_XFS_APP = application("mkfs.xfs")
MKNTFS_APP = application("mkntfs")
MKREISERFS_APP = application("mkreiserfs")
MLABEL_APP = application("mlabel")
MULTIPATH_APP = application("multipath")
NTFSINFO_APP = application("ntfsinfo")
NTFSLABEL_APP = application("ntfslabel")
NTFSRESIZE_APP = application("ntfsresize")
REISERFSTUNE_APP = application("reiserfstune")
RESIZE2FS_APP = application_by_version("resize2fs", E2FSPROGS_VERSION)
TUNE2FS_APP = application_by_version("tune2fs", E2FSPROGS_VERSION)
XFSADMIN_APP = application("xfs_admin")
XFSDB_APP = application("xfs_db")
XFSFREEZE_APP = application("xfs_freeze")
XFSRESIZE_APP = application("xfs_growfs")
XFSREPAIR_APP = application("xfs_repair")
FSCK_F2FS_APP = application("fsck.f2fs")
MKFS_F2FS_APP = application("mkfs.f2fs")
MOUNT_APP = application("mount")
STRATISPREDICTUSAGE_APP = application("stratis-predict-usage")
STRATIS_SERVICE_METHOD = DBusMethod(STRATIS_SERVICE, STRATIS_PATH)
STRATIS_DBUS = dbus_service("stratis", STRATIS_SERVICE_METHOD)