summaryrefslogtreecommitdiffstats
path: root/src/software/openlmi/software/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/software/openlmi/software/util')
-rw-r--r--src/software/openlmi/software/util/__init__.py134
-rw-r--r--src/software/openlmi/software/util/cmpi_logging.py203
-rw-r--r--src/software/openlmi/software/util/common.py825
3 files changed, 337 insertions, 825 deletions
diff --git a/src/software/openlmi/software/util/__init__.py b/src/software/openlmi/software/util/__init__.py
index 2ebe827..e48ea29 100644
--- a/src/software/openlmi/software/util/__init__.py
+++ b/src/software/openlmi/software/util/__init__.py
@@ -1,3 +1,4 @@
+# -*- encoding: utf-8 -*-
# Software Management Providers
#
# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
@@ -18,3 +19,136 @@
#
# Authors: Michal Minar <miminar@redhat.com>
#
+
+"""Common utilities for LMI_Software* providers
+"""
+
+import platform
+import re
+
+RE_EVRA = re.compile(
+ r'^(?P<epoch>\d+):(?P<ver>[^-]+)-(?P<rel>.+)\.(?P<arch>[^.]+)$')
+RE_NEVRA = re.compile(
+ r'^(?P<name>.+)-(?P<evra>(?P<epoch>\d+):(?P<ver>[^-]+)'
+ r'-(?P<rel>.+)\.(?P<arch>[^.]+))$')
+RE_NEVRA_OPT_EPOCH = re.compile(
+ r'^(?P<name>.+)-(?P<evra>((?P<epoch>\d+):)?(?P<ver>[^-]+)'
+ r'-(?P<rel>.+)\.(?P<arch>[^.]+))$')
+RE_ENVRA = re.compile(
+ r'^(?P<epoch>\d+):(?P<name>.+)-(?P<evra>(?P<ver>[^-]+)'
+ r'-(?P<rel>.+)\.(?P<arch>[^.]+))$')
+
+def _get_distname():
+ """
+ @return name of linux distribution
+ """
+ if hasattr(platform, 'linux_distribution'):
+ return platform.linux_distribution(
+ full_distribution_name=False)[0].lower()
+ else:
+ return platform.dist()[0].lower()
+
+
+def get_target_operating_system():
+ """
+ @return (val, text).
+ Where val is a number from ValueMap of TargetOperatingSystem property
+ of CIM_SoftwareElement class and text is its testual representation.
+ """
+
+ system = platform.system()
+ if system.lower() == 'linux':
+ try:
+ val, dist = \
+ { 'redhat' : (79, 'RedHat Enterprise Linux')
+ , 'suse' : (81, 'SUSE')
+ , 'mandriva' : (88, 'Mandriva')
+ , 'ubuntu' : (93, 'Ubuntu')
+ , 'debian' : (95, 'Debian')
+ }[_get_distname()]
+ except KeyError:
+ linrel = platform.uname()[2]
+ if linrel.startswith('2.4'):
+ val, dist = (97, 'Linux 2.4.x')
+ elif linrel.startswith('2.6'):
+ val, dist = (99, 'Linux 2.6.x')
+ else:
+ return (36, 'LINUX') # no check for x86_64
+ if platform.machine() == 'x86_64':
+ val += 1
+ dist += ' 64-Bit'
+ return (val, dist)
+ elif system.lower() in ('macosx', 'darwin'):
+ return (2, 'MACOS')
+ # elif system.lower() == 'windows': # no general value
+ else:
+ return (0, 'Unknown')
+
+def check_target_operating_system(system):
+ """
+ @return if param system matches current target operating system
+ """
+ if isinstance(system, basestring):
+ system = int(system)
+ if not isinstance(system, (int, long)):
+ raise TypeError("system must be either string or integer, not %s" %
+ system.__class__.__name__)
+ tos = get_target_operating_system()
+ if system == tos:
+ return True
+ if system == 36: # linux
+ if platform.system().lower() == "linux":
+ return True
+ if ( system >= 97 and system <= 100 # linux 2.x.x
+ and platform.uname()[2].startswith('2.4' if system < 99 else '2.6')
+ # check machine
+ and ( bool(platform.machine().endswith('64'))
+ == bool(not (system % 2)))):
+ return True
+ return False
+
+def nevra2filter(nevra):
+ """
+ Takes either regexp match object resulting from RE_NEVRA match or
+ a nevra string.
+ @return dictionary with package filter key-value pairs made from nevra
+ """
+ if isinstance(nevra, basestring):
+ match = RE_NEVRA_OPT_EPOCH.match(nevra)
+ elif nevra.__class__.__name__.lower() == "sre_match":
+ match = nevra
+ else:
+ raise TypeError("nevra must be either string or regexp match object")
+ epoch = match.group("epoch")
+ if not epoch or match.group("epoch") == "(none)":
+ epoch = "0"
+ return { "name" : match.group("name")
+ , "epoch" : epoch
+ , "version" : match.group("ver")
+ , "release" : match.group("rel")
+ , "arch" : match.group("arch")
+ }
+
+def make_nevra(name, epoch, ver, rel, arch, with_epoch='NOT_ZERO'):
+ """
+ @param with_epoch may be one of:
+ "NOT_ZERO" - include epoch only if it's not zero
+ "ALWAYS" - include epoch always
+ "NEVER" - do not include epoch at all
+ """
+ estr = ''
+ if with_epoch.lower() == "always":
+ estr = epoch
+ elif with_epoch.lower() == "not_zero":
+ if epoch != "0":
+ estr = epoch
+ if len(estr):
+ estr += ":"
+ return "%s-%s%s-%s.%s" % (name, estr, ver, rel, arch)
+
+def pkg2nevra(pkg, with_epoch='NOT_ZERO'):
+ """
+ @return nevra string made of pkg
+ """
+ return make_nevra(pkg.name, pkg.epoch, pkg.version,
+ pkg.release, pkg.arch, with_epoch)
diff --git a/src/software/openlmi/software/util/cmpi_logging.py b/src/software/openlmi/software/util/cmpi_logging.py
new file mode 100644
index 0000000..2db14b6
--- /dev/null
+++ b/src/software/openlmi/software/util/cmpi_logging.py
@@ -0,0 +1,203 @@
+# -*- Coding:utf-8 -*-
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Authors: Jan Safranek <jsafrane@redhat.com>
+
+
+import logging
+import inspect
+import traceback
+
+TRACE_WARNING = logging.INFO - 1
+TRACE_INFO = logging.INFO - 2
+TRACE_VERBOSE = logging.DEBUG
+
+class CMPILogHandler(logging.Handler):
+ """
+ A handler class, which sends log messages to CMPI log.
+ """
+
+ def __init__(self, cmpi_logger, *args, **kwargs):
+ self.cmpi_logger = cmpi_logger
+ super(CMPILogHandler, self).__init__(*args, **kwargs)
+
+ def emit(self, record):
+ msg = self.format(record)
+ if record.levelno >= logging.ERROR:
+ self.cmpi_logger.log_error(msg)
+ elif record.levelno >= logging.WARNING:
+ self.cmpi_logger.log_warn(msg)
+ elif record.levelno >= logging.INFO:
+ self.cmpi_logger.log_info(msg)
+ elif record.levelno >= TRACE_WARNING:
+ self.cmpi_logger.trace_warn(record.filename, msg)
+ elif record.levelno >= TRACE_INFO:
+ self.cmpi_logger.trace_info(record.filename, msg)
+ elif record.levelno >= logging.DEBUG:
+ self.cmpi_logger.trace_verbose(record.filename, msg)
+
+class CMPILogger(logging.getLoggerClass()):
+ """
+ A logger class, which adds trace_method level log methods.
+ """
+ def trace_warn(self, msg, *args, **kwargs):
+ """ Log message with TRACE_WARNING severity. """
+ self.log(TRACE_WARNING, msg, *args, **kwargs)
+
+ def trace_info(self, msg, *args, **kwargs):
+ """ Log message with TRACE_INFO severity. """
+ self.log(TRACE_INFO, msg, *args, **kwargs)
+
+ def trace_verbose(self, msg, *args, **kwargs):
+ """ Log message with TRACE_VERBOSE severity. """
+ self.log(TRACE_VERBOSE, msg, *args, **kwargs)
+
+logging.setLoggerClass(CMPILogger)
+
+def trace_method(func):
+ """ Decorator, trace entry and exit for a class method. """
+ classname = inspect.getouterframes(inspect.currentframe())[1][3]
+ def helper_func(*args, **kwargs):
+ """
+ Helper function, wrapping real function by trace_method decorator.
+ """
+ logger.log(TRACE_VERBOSE, "Entering %s.%s", classname, func.__name__)
+ try:
+ ret = func(*args, **kwargs)
+ except Exception as exc:
+ if getattr(exc, "tb_printed", False) is False:
+ logger.exception("full traceback")
+ logger.log(TRACE_VERBOSE, "traceback: %s",
+ traceback.format_exc())
+ exc.tb_printed = True
+ logger.log(TRACE_WARNING, "%s.%s threw exception %s",
+ classname, func.__name__, str(exc))
+ raise
+ logger.log(TRACE_VERBOSE, "Exiting %s.%s", classname, func.__name__)
+ return ret
+ helper_func.__name__ = func.__name__
+ helper_func.__doc__ = func.__doc__
+ helper_func.__module__ = func.__module__
+ return helper_func
+
+def trace_function(func):
+ """ Decorator, trace entry and exit for a function outside any class. """
+ def helper_func(*args, **kwargs):
+ """
+ Helper function, wrapping real function by trace_method decorator.
+ """
+ logger.log(TRACE_VERBOSE, "Entering %s.%s",
+ func.__module__, func.__name__)
+ try:
+ ret = func(*args, **kwargs)
+ except Exception as exc:
+ if getattr(exc, "tb_printed", False) is False:
+ logger.exception("full traceback")
+ logger.log(TRACE_VERBOSE, "traceback: %s",
+ traceback.format_exc())
+ exc.tb_printed = True
+ logger.log(TRACE_WARNING, "%s.%s threw exception %s",
+ func.__module__, func.__name__, str(exc))
+ raise
+ logger.log(TRACE_VERBOSE, "Exiting %s", func.__name__)
+ return ret
+ helper_func.__name__ = func.__name__
+ helper_func.__doc__ = func.__doc__
+ helper_func.__module__ = func.__module__
+ return helper_func
+
+class LogManager(object):
+ """
+ Class, which takes care of CMPI logging.
+ There should be only one instance of this class and it should be
+ instantiated as soon as possible, even before reading a config.
+ The config file can be provided later by set_config call.
+ """
+ FORMAT_STDERR = '%(levelname)s: %(message)s'
+ FORMAT_CMPI = '%(levelname)s: %(message)s'
+
+ LOGGER_NAME = "openlmi.storage"
+
+ def __init__(self, env):
+ """
+ Initialize logging.
+ """
+ formatter = logging.Formatter(self.FORMAT_CMPI)
+
+ self.cmpi_handler = CMPILogHandler(env.get_logger())
+ self.cmpi_handler.setLevel(logging.DEBUG)
+ self.cmpi_handler.setFormatter(formatter)
+
+ self.logger = logging.getLogger(self.LOGGER_NAME)
+ self.logger.addHandler(self.cmpi_handler)
+ self.logger.setLevel(logging.DEBUG)
+
+ self.stderr_handler = None
+ self.config = None
+
+ global logger # IGNORE:W0603
+ logger = self.logger
+ logger.info("CMPI log started")
+
+ @trace_method
+ def set_config(self, config):
+ """
+ Set a configuration of logging. It applies its setting immediately
+ and also subscribes for configuration changes.
+ """
+ self.config = config
+ config.add_listener(self._config_changed)
+ # apply the config
+ self._config_changed(config)
+
+ @trace_method
+ def _config_changed(self, config):
+ """
+ Apply changed configuration, i.e. start/stop sending to stderr
+ and set appropriate log level.
+ """
+ if config.tracing:
+ self.logger.setLevel(logging.DEBUG)
+ else:
+ self.logger.setLevel(logging.INFO)
+ if config.stderr:
+ # start sending to stderr
+ if not self.stderr_handler:
+ # create stderr handler
+ formatter = logging.Formatter(self.FORMAT_STDERR)
+ self.stderr_handler = logging.StreamHandler()
+ self.stderr_handler.setLevel(logging.DEBUG)
+ self.stderr_handler.setFormatter(formatter)
+ self.logger.addHandler(self.stderr_handler)
+ self.logger.info("Started logging to stderr.")
+ else:
+ # stop sending to stderr
+ if self.stderr_handler:
+ self.logger.info("Stopped logging to stderr.")
+ self.logger.removeHandler(self.stderr_handler)
+ self.stderr_handler = None
+
+ def destroy(self):
+ if self.stderr_handler:
+ self.logger.removeHandler(self.stderr_handler)
+ self.stderr_handler = None
+ self.logger.removeHandler(self.cmpi_handler)
+ self.cmpi_handler = None
+ self.config.remove_listener(self._config_changed)
+
+logger = None
diff --git a/src/software/openlmi/software/util/common.py b/src/software/openlmi/software/util/common.py
deleted file mode 100644
index 685171a..0000000
--- a/src/software/openlmi/software/util/common.py
+++ /dev/null
@@ -1,825 +0,0 @@
-# -*- encoding: utf-8 -*-
-# Software Management Providers
-#
-# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-
-"""Common utilities for LMI_Software* providers
-"""
-
-import collections
-from datetime import datetime
-import grp
-import hashlib
-import itertools
-import os
-import platform
-import pwd
-import re
-import rpm
-import socket
-import stat
-import pywbem
-import yum
-import cmpi_pywbem_bindings as pycimmb
-from openlmi.software.util import singletonmixin
-
-RE_EVRA = re.compile(r'^(?P<epoch>\d+):(?P<ver>[^-]+)'
- r'-(?P<rel>.+)\.(?P<arch>[^.]+)$')
-RE_NEVRA = re.compile(r'^(?P<name>.+)-(?P<evra>((?P<epoch>\d+):)?(?P<ver>[^-]+)'
- r'-(?P<rel>.+)\.(?P<arch>[^.]+))$')
-
-RPMDB_PATH = '/var/lib/rpm/Packages'
-
-class YumDB(singletonmixin.Singleton):
- """
- Context manager for accessing yum/rpm database.
- """
-
- # this is to inform Singleton, that __init__ should be called only once
- ignoreSubsequent = True
-
- def __init__(self, env, *args, **kwargs): #pylint: disable=W0231
- if not isinstance(env, pycimmb.ProviderEnvironment):
- raise TypeError("env must be instance of"
- " pycimmb.ProviderEnvironment")
- self._yum_args = (args, kwargs)
- self._yum = None
- self._db_mtime = 0
- self._lock_cnt = 0
- self.env = env
- env.get_logger().log_info('init called')
-
- def is_dirty(self):
- """
- @return True if rpm database has been modified since last update
- """
- return self._db_mtime < os.stat(RPMDB_PATH).st_mtime
-
- def is_locked(self):
- """
- @return True if rpm database is locked
- """
- return self._yum._lockfile is not None
-
- def update_db(self):
- """
- Call to update database metadata.
- """
- self.env.get_logger().log_info('updating rpmdb')
- self._db_mtime = os.stat(RPMDB_PATH).st_mtime
- self._yum.doConfigSetup()
- self._yum.doTsSetup()
- self._yum.doRpmDBSetup()
-
- def __enter__(self):
- self._lock_cnt += 1
- if self._lock_cnt < 2:
- self._yum = yum.YumBase(*self._yum_args[0], **self._yum_args[1])
- if not self.is_locked() and self.is_dirty():
- self.update_db()
- self._yum.doLock()
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- if self._lock_cnt == 1:
- del self._yum
- self._lock_cnt -= 1
-
- def __getattr__(self, name):
- if not self.is_locked() and self.is_dirty():
- self.update_db()
- return getattr(self._yum, name)
-
-
-def _get_distname():
- """
- @return name of linux distribution
- """
- if hasattr(platform, 'linux_distribution'):
- return platform.linux_distribution(
- full_distribution_name=False)[0].lower()
- else:
- return platform.dist()[0].lower()
-
-
-def get_target_operating_system():
- """
- @return (val, text).
- Where val is a number from ValueMap of TargetOperatingSystem property
- of CIM_SoftwareElement class and text is its testual representation.
- """
-
- system = platform.system()
- if system.lower() == 'linux':
- try:
- val, dist = \
- { 'redhat' : (79, 'RedHat Enterprise Linux')
- , 'suse' : (81, 'SUSE')
- , 'mandriva' : (88, 'Mandriva')
- , 'ubuntu' : (93, 'Ubuntu')
- , 'debian' : (95, 'Debian')
- }[_get_distname()]
- except KeyError:
- linrel = platform.uname()[2]
- if linrel.startswith('2.4'):
- val, dist = (97, 'Linux 2.4.x')
- elif linrel.startswith('2.6'):
- val, dist = (99, 'Linux 2.6.x')
- else:
- return (36, 'LINUX') # no check for x86_64
- if platform.machine() == 'x86_64':
- val += 1
- dist += ' 64-Bit'
- return (val, dist)
- elif system.lower() in ('macosx', 'darwin'):
- return (2, 'MACOS')
- # elif system.lower() == 'windows': # no general value
- else:
- return (0, 'Unknown')
-
-def get_computer_system_op(prefix='Linux'):
- """
- @return object path of CIM_ComputerSystem for this system
- """
- return pywbem.CIMInstanceName(
- classname='%s_ComputerSystem' % prefix,
- keybindings={
- "CreationClassName": "%s_ComputerSystem" % prefix
- , "Name" : socket.gethostname() },
- namespace="root/cimv2")
-
-def check_target_operating_system(system):
- """
- @return if param system matches current target operating system
- """
- if isinstance(system, basestring):
- system = int(system)
- if not isinstance(system, (int, long)):
- raise TypeError("system must be either string or integer, not {}"
- .format(system.__class__.__name__))
- tos = get_target_operating_system()
- if system == tos:
- return True
- if system == 36: # linux
- if platform.system().lower() == "linux":
- return True
- if ( system >= 97 and system <= 100 # linux 2.x.x
- and platform.uname()[2].startswith('2.4' if system < 99 else '2.6')
- # check machine
- and ( bool(platform.machine().endswith('64'))
- == bool(not (system % 2)))):
- return True
- return False
-
-def check_computer_system_op(env, system):
- """
- @param system is object path referring to CIM_ComputerSystem instance
- passed as argument to some cim function
- @return True if this instance matches our system; otherwise a CIMError
- will be raised
- """
- if not isinstance(system, pywbem.CIMInstanceName):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "\"System\" must be a CIMInstanceName")
- our_system = get_computer_system_op('CIM')
- chandle = env.get_cimom_handle()
- if not chandle.is_subclass(system.namespace,
- sub=system.classname,
- super=our_system.classname):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "Class of \"System\" must be a sublass of %s" %
- our_system.classname)
- if not 'CreationClassName' in system or not 'Name' in system:
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "\"System\" is missing one of keys")
- if not chandle.is_subclass(system.namespace,
- sub=system['CreationClassName'],
- super=our_system.classname):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "CreationClassName of \"System\" must be a sublass of %s" %
- our_system.classname)
- if system['Name'] != our_system['Name']:
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "Name of \"System\" does not match \"%s\"" %
- our_system['Name'])
- return True
-
-
-def match_pkg(pkg, **kwargs):
- """
- all not Null and not empty arguments will be matched against pkg
- attributes; if all of them match, return True
-
- possible arguments:
- name, epoch, version, release, arch
- evra, nevra
- """
- for attr in ( 'evra', 'nevra', 'name', 'epoch'
- , 'version', 'release', 'arch'):
- value = kwargs.get(attr, None)
- if value and getattr(pkg, attr) != value:
- return False
- return True
-
-class SoftwarePackage:
- """
- Just a namespace for common function related to SoftwarePackage provider.
- TODO: make it a submodule
- """
-
- @staticmethod
- def object_path2pkg(env, objpath, package_list='installed'):
- """
- @param objpath must contain precise information of package,
- otherwise a CIM_ERR_NOT_FOUND error is raised
- @param package_list one of {'installed', 'all', 'available'}
- says, where to look for given package
- """
- if not isinstance(objpath, pywbem.CIMInstanceName):
- raise TypeError("objpath must be an instance of CIMInstanceName")
- if not isinstance(package_list, basestring):
- raise TypeError("package_list must be a string")
- if not package_list in ('installed', 'all', 'available'):
- raise ValueError('unsupported package list "%s"'%package_list)
-
- if ( not objpath['Name'] or not objpath['SoftwareElementID']
- or not objpath['SoftwareElementID'].startswith(objpath['Name'])
- or objpath['SoftwareElementID'].find(objpath['Version']) == -1):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Wrong keys.")
- if not check_target_operating_system(objpath['TargetOperatingSystem']):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "Wrong target operating system.")
- if not objpath['Name'] or not objpath['Version']:
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- 'Both "Name" and "Version" must be given')
- match = RE_NEVRA.match(objpath['SoftwareElementID'])
- if not match:
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "Wrong SotwareElementID. Expected valid nevra"
- " (name-[epoch:]version-release.arch).")
- if objpath['Version'] != match.group('ver'):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "Version does not match version part in SoftwareElementID.")
- evra = "{}:{}-{}.{}".format(*(
- (match.group(k) if k != "epoch" or match.group(k) else "0")
- for k in ("epoch", 'ver', 'rel', 'arch')))
- with YumDB.getInstance(env) as ydb:
- pkglist = ydb.doPackageLists(package_list,
- showdups=package_list != 'installed')
- if package_list != 'all':
- pkglist = getattr(pkglist, package_list)
- else:
- # NOTE: available ∩ installed = ∅
- pkglist = itertools.chain(pkglist.available, pkglist.installed)
- exact, _, _ = yum.packages.parsePackages(pkglist, [objpath['Name']])
- for pkg in yum.misc.unique(exact):
- if pkg.evra == evra:
- return pkg
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "No matching package found.")
-
- @staticmethod
- def object_path2pkg_search(env, objpath):
- """
- similar to object_path2pkg, but tries to find best suitable
- package matching keys
-
- If any matching package is already installed, it is returned.
- Otherwise available package with highest version is returned.
-
- @param objpath may be object of CIMInstance or CIMInstanceName and
- must contain at least \"Name\" or \"SoftwareElementID\"
- @return instance of yum.rpmsack.RPMInstalledPackage in case of
- installed package, otherwise yum.packages.YumAvailablePackage
- """
- logger = env.get_logger()
- if isinstance(objpath, pywbem.CIMInstance):
- def _get_key(k):
- """@return value of instance's key"""
- value = objpath.properties.get(k, None)
- if isinstance(value, pywbem.CIMProperty):
- return value.value
- if value is not None:
- return value
- logger.log_error('missing key "{}" in inst.props'.format(k))
- return objpath.path[k] if k in objpath.path else None
- elif isinstance(objpath, pywbem.CIMInstanceName):
- _get_key = lambda k: objpath[k] if k in objpath else None
- else:
- raise TypeError("objpath must be either CIMInstance"
- "or CIMInstanceName")
-
- # parse and check arguments
- match_props = {} # args for match_pkg
- if _get_key('SoftwareElementID'):
- match = RE_NEVRA.match(_get_key('SoftwareElementID'))
- if not match:
- raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
- "SoftwareElementID could not be parsed.")
- for k in ('name', 'version', 'release', 'arch'):
- mkey = k if k not in ('version', 'release') else k[:3]
- match_props[k] = match.group(mkey)
- if not match.group("epoch"):
- match_props["epoch"] = "0"
- else:
- for k in ('name', 'epoch', 'version', 'release', 'arch'):
- ikey = k if k != 'arch' else "architecture"
- if _get_key(ikey):
- match_props[k] = _get_key(ikey)
-
- if not match_props:
- raise pywbem.CIMError(pywbem.CIM_ERR_FAILED,
- "Too few key values given (give at least a Name).")
- if not 'name' in match_props:
- raise pywbem.CIMError(pywbem.CIM_ERR_FAILED,
- "Missing either Name or SoftwareElementID property.")
-
- # get available packages
- pkglist = YumDB.getInstance(env).doPackageLists('all', showdups=True)
- # NOTE: available ∩ installed = ∅
- exact, _, _ = yum.packages.parsePackages(
- itertools.chain(pkglist.available, pkglist.installed),
- [match_props['name']])
- exact = yum.misc.unique(exact)
- exact_orig = exact
- exact = sorted([ p for p in exact if match_pkg(p, **match_props) ])
- if len(exact) == 0:
- logger.log_error('could not find any package for query: {}'
- ' in list: {}'
- .format(match_props, [p.nevra for p in exact_orig]))
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "No matching package found.")
- for pkg in exact: # check, whether package is already installed
- if isinstance(pkg, yum.rpmsack.RPMInstalledPackage):
- return pkg
- logger.log_info(('found multiple matching packages'
- ' for query: {}' if len(exact) > 1 else
- 'exact match found for query: {}')
- .format(match_props))
- return exact[-1] # select highest version
-
- @staticmethod
- def pkg2model(env, pkg, keys_only=True, model=None):
- """
- @param model if None, will be filled with data, otherwise
- a new instance of CIMInstance or CIMObjectPath is created
- """
- #if not isinstance(pkg, yum.rpmsack.RPMInstalledPackage):
- #if not isinstance(pkg, yum.packages.YumHeaderPackage):
- if not isinstance(pkg, yum.packages.YumAvailablePackage):
- raise TypeError(
- "pkg must be an instance of YumAvailablePackage")
- if model is None:
- model = pywbem.CIMInstanceName('LMI_SoftwarePackage',
- namespace='root/cimv2')
- if not keys_only:
- model = pywbem.CIMInstance('LMI_SoftwarePackage', path=model)
- if isinstance(model, pywbem.CIMInstance):
- def _set_key(k, value):
- """Sets the value of key property of cim instance"""
- model[k] = value
- model.path[k] = value #pylint: disable=E1103
- else:
- _set_key = model.__setitem__
- with YumDB.getInstance(env):
- _set_key('Name', pkg.name)
- _set_key('SoftwareElementID', pkg.nevra)
- _set_key('SoftwareElementState', pywbem.Uint16(2
- if isinstance(pkg, yum.rpmsack.RPMInstalledPackage)
- else 1))
- _set_key('TargetOperatingSystem',
- pywbem.Uint16(get_target_operating_system()[0]))
- _set_key('Version', pkg.version)
- if not keys_only:
- model['Caption'] = pkg.summary
- model['Description'] = pkg.description
- if isinstance(pkg, yum.rpmsack.RPMInstalledPackage):
- model['InstallDate'] = pywbem.CIMDateTime(
- datetime.fromtimestamp(pkg.installtime))
- if pkg.vendor:
- model['Manufacturer'] = pkg.vendor
- model['Release'] = pkg.release
- model['Epoch'] = pywbem.Uint16(pkg.epoch)
- model["Architecture"] = pkg.arch
- model['License'] = pkg.license
- model['Group'] = pkg.group
- model['Size'] = pywbem.Uint64(pkg.size)
- return model
-
-class SoftwareFileCheck:
- """
- Just a namespace for functions related to FileCheck provider.
- TODO: make it a submodule
- """
-
- passed_flags_descriptions = (
- "Existence",
- "File Type",
- "File Size",
- "File Mode",
- "File Checksum",
- "Device major/minor number",
- "Symlink Target",
- "User Ownership", "Group Ownership",
- "Modify Time")
-
- checksumtype_str2num = dict((val, k) for (k, val) in
- yum.constants.RPM_CHECKSUM_TYPES.items())
-
- @staticmethod
- def pkg_checksum_type(pkg):
- """
- @return integer representation of checksum type
- """
- if not isinstance(pkg, yum.packages.YumAvailablePackage):
- raise TypeError("pkg must be an instance of YumAvailablePackage")
- if isinstance(pkg, yum.rpmsack.RPMInstalledPackage):
- return pkg.hdr[rpm.RPMTAG_FILEDIGESTALGO]
- with YumDB.getInstance(): # ensure, that _yum is inited
- return SoftwareFileCheck.checksumtype_str2pywbem(
- pkg.yumdb_info.checksum_type)
-
- @staticmethod
- def checksumtype_num2hash(csumt):
- """
- @param csumt checksum type as a number obtained from package
- @return hash function object corresponding to csumt
- """
- return getattr(hashlib, yum.constants.RPM_CHECKSUM_TYPES[csumt])
-
- @staticmethod
- def checksumtype_str2pywbem(alg):
- """
- @param alg is a name of algorithm used for checksum
- @return pywbem number corresponding to given alg
- """
- try:
- res = SoftwareFileCheck.checksumtype_str2num[alg.lower()]
- except KeyError:
- res = 0
- return pywbem.Uint16(res)
-
- @staticmethod
- def filetype_str2pywbem(file_type):
- """
- @param file_type is a name of file type obtained from pkg headers
- @return pywbem number corresponding to thus file type
- """
- try:
- return pywbem.Uint16(
- { 'file' : 1
- , 'directory' : 2
- , 'symlink' : 3
- , 'fifo' : 4
- , 'character device' : 5
- , 'block device' : 6
- }[file_type])
- except KeyError:
- return pywbem.Uint16(0)
-
- @staticmethod
- def filetype_mode2pywbem(mode):
- """
- @param mode is a raw file mode as integer
- @return pywbem numeric value of file's type
- """
- for i, name in enumerate(
- ('REG', 'DIR', 'LNK', 'FIFO', 'CHR', 'BLK'), 1):
- if getattr(stat, 'S_IS' + name)(mode):
- return pywbem.Uint16(i)
- return pywbem.Uint16(0)
-
- @staticmethod
- def mode2pywbem_flags(mode):
- """
- @param mode if None, file does not exist
- @return list of integer flags describing file's access permissions
- """
- if mode is None:
- return None
- flags = []
- for i, flag in enumerate((
- stat.S_IXOTH,
- stat.S_IWOTH,
- stat.S_IROTH,
- stat.S_IXGRP,
- stat.S_IWGRP,
- stat.S_IRGRP,
- stat.S_IXUSR,
- stat.S_IWUSR,
- stat.S_IRUSR,
- stat.S_ISVTX,
- stat.S_ISGID,
- stat.S_ISUID)):
- if flag & mode:
- flags.append(pywbem.Uint8(i))
- return flags
-
- @staticmethod
- def hashfile(afile, hashers, blocksize=65536):
- """
- @param hashers is a list of hash objects
- @return list of digest strings (in hex format) for each hash object
- given in the same order
- """
- if not isinstance(hashers, (tuple, list, set, frozenset)):
- hashers = (hashers, )
- buf = afile.read(blocksize)
- while len(buf) > 0:
- for hashfunc in hashers:
- hashfunc.update(buf)
- buf = afile.read(blocksize)
- return [ hashfunc.hexdigest() for hashfunc in hashers ]
-
- @staticmethod
- def compute_checksums(env, checksum_type, file_type, file_path):
- """
- @param file_type is not a file, then zeroes are returned
- @param checksum_type selected hash algorithm to compute second
- checksum
- @return (md5sum, checksum)
- both checksums are computed from file_path's content
- first one is always md5, the second one depends on checksum_type
- if file does not exists, (None, None) is returned
- """
- hashers = [hashlib.md5()] #pylint: disable=E1101
- if checksum_type != SoftwareFileCheck.checksumtype_str2num["md5"]:
- hashers.append(SoftwareFileCheck.checksumtype_num2hash(
- checksum_type)())
- if file_type != SoftwareFileCheck.filetype_str2pywbem('file'):
- rslts = ['0'*len(h.hexdigest()) for h in hashers]
- else:
- try:
- with open(file_path, 'rb') as fobj:
- rslts = SoftwareFileCheck.hashfile(fobj, hashers)
- except (OSError, IOError) as exc:
- env.get_logger().log_error("could not open file \"%s\""
- " for reading: %s" % (file_path, exc))
- return None, None
- return (rslts[0], rslts[1] if len(rslts) > 1 else rslts[0]*2)
-
- @staticmethod
- def object_path2yumcheck(env, objpath):
- """
- @return instance of yum.packages._RPMVerifyPackage
- this object holds RPMInstalledPackage under its po attribute
- """
- if not isinstance(objpath, pywbem.CIMInstanceName):
- raise TypeError("objpath must be instance of CIMInstanceName, "
- "not \"%s\"" % objpath.__class__.__name__)
-
- if ( not objpath['Name'] or not objpath['SoftwareElementID']
- or not objpath['CheckID']
- or not objpath['CheckID'].endswith('#'+objpath['Name'])
- or objpath['SoftwareElementID'].find(objpath['Version']) == -1):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Wrong keys.")
- if objpath['SoftwareElementState'] not in ("2", 2):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "Only \"Executable\" software element state supported")
- if not check_target_operating_system(objpath['TargetOperatingSystem']):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "Wrong target operating system.")
- if not objpath['Name'] or not objpath['Version']:
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- 'Both "Name" and "Version" must be given')
- match = RE_NEVRA.match(objpath['SoftwareElementID'])
- if not match:
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "Wrong SotwareElementID. Expected valid nevra"
- " (name-epoch:version-release.arch).")
- if objpath['Version'] != match.group('ver'):
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "Version does not match version part in SoftwareElementID.")
-
- evra = "{}:{}-{}.{}".format(*(
- (match.group(k) if k != "epoch" or match.group(k) else "0")
- for k in ("epoch", 'ver', 'rel', 'arch')))
- with YumDB.getInstance(env) as ydb:
- pkglist = ydb.doPackageLists('installed')
- exact, _, _ = yum.packages.parsePackages(
- pkglist.installed, [match.group('name')])
- for pkg in yum.misc.unique(exact):
- if pkg.evra != evra:
- continue
- vpkg = yum.packages._RPMVerifyPackage(
- pkg, pkg.hdr.fiFromHeader(),
- SoftwareFileCheck.pkg_checksum_type(pkg), [], True)
- if not objpath['Name'] in vpkg:
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "File not found in RPM package.")
- return vpkg
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "No matching package installed.")
-
- # Named tuple to store results of rpm file check as pywbem values,
- # all results are in form:
- # (expected, reality)
- # where
- # expected is value from rpm package
- # reality is value obtained from installed file
- # None means, that value could not be obtained
- # except for "exists" and "md5_checksum" attributes, where "exists"
- # is boolean and md5_checksum is a string
- # for example:
- # file_check.file_type == (4, 3)
- FileCheck = collections.namedtuple('FileCheck',
- 'exists, md5_checksum, file_type, file_size, file_mode, '
- 'file_checksum, device, link_target, user_id, group_id, '
- 'last_modification_time')
-
- @staticmethod
- def test_file(env, checksum_type, vpf):
- """
- @param checksum type is a pywbem value for ChecksumType property
- @return instance of FileCheck
- """
- if not isinstance(vpf, yum.packages._RPMVerifyPackageFile):
- raise TypeError("vpf must be an instance of _RPMVerifyPackage,"
- " not \"%s\"" % vpf.__class__.__name__)
- exists = os.path.lexists(vpf.filename)
- md5_checksum = None
- expected = {
- "file_type" : SoftwareFileCheck.filetype_str2pywbem(vpf.ftype),
- "user_id" : pywbem.Uint32(pwd.getpwnam(vpf.user).pw_uid),
- "group_id" : pywbem.Uint32(grp.getgrnam(vpf.group).gr_gid),
- "file_mode" : pywbem.Uint32(vpf.mode),
- "file_size" : pywbem.Uint64(vpf.size),
- "link_target" : vpf.readlink if vpf.readlink else None,
- "file_checksum" : vpf.digest[1],
- "device" : (pywbem.Uint64(vpf.dev)
- if vpf.ftype.endswith('device') else None),
- "last_modification_time" : pywbem.Uint64(vpf.mtime)
- }
- if not exists:
- reality = collections.defaultdict(lambda: None)
- else:
- fstat = os.lstat(vpf.filename)
- reality = {
- "file_type" : SoftwareFileCheck.filetype_mode2pywbem(
- fstat.st_mode),
- "user_id" : pywbem.Uint32(fstat.st_uid),
- "group_id" : pywbem.Uint32(fstat.st_gid),
- "file_mode" : pywbem.Uint32(fstat.st_mode),
- "file_size" : pywbem.Uint64(fstat.st_size),
- "last_modification_time" : pywbem.Uint64(fstat.st_mtime)
- }
- reality["device"] = (pywbem.Uint64(fstat.st_dev)
- if reality['file_type'] ==
- SoftwareFileCheck.filetype_str2pywbem("device") else None)
- reality["link_target"] = (os.readlink(vpf.filename)
- if os.path.islink(vpf.filename) else None)
- md5_checksum, checksum = SoftwareFileCheck.compute_checksums(
- env, checksum_type, reality["file_type"], vpf.filename)
- reality["file_checksum"] = checksum
- kwargs = dict(exists=exists, md5_checksum=md5_checksum,
- **dict((k, (expected[k], reality[k])) for k in expected))
- return SoftwareFileCheck.FileCheck(**kwargs)
-
- @staticmethod
- def filecheck_passed(file_check):
- """
- @return True if installed file passed all checks.
- """
- if not isinstance(file_check, SoftwareFileCheck.FileCheck):
- raise TypeError("file_check must be an instance of FileCheck")
- passed = file_check.exists
- for k, val in file_check._asdict().items():
- if not passed:
- break
- if not isinstance(val, tuple):
- continue
- if ( k == "last_modification_time"
- and file_check.file_type[0] != \
- SoftwareFileCheck.filetype_str2pywbem("file")):
- continue
- if ( k == "file_mode"
- and file_check.file_type[0] == \
- SoftwareFileCheck.filetype_str2pywbem("symlink")):
- continue
- passed = val[0] == val[1]
- return passed
-
- @staticmethod
- def _filecheck2model_flags(file_check):
- """
- @param file_check is an instance of FileCheck
- @return pywbem value for PassedFlags property
- """
- flags = []
- for k, value in file_check._asdict().items(): #pylint: disable=W0212
- if isinstance(value, tuple):
- if ( k == "last_modification_time"
- and file_check.file_type[0] != \
- SoftwareFileCheck.filetype_str2pywbem('file')):
- # last_modification_time check is valid only for
- # regular files
- flag = file_check.exists
- elif ( k == "file_mode"
- and file_check.file_type[0] == \
- SoftwareFileCheck.filetype_str2pywbem('symlink')):
- # do not check mode of symlinks
- flag = ( file_check.exists
- and ( file_check.file_type[0]
- == file_check.file_type[1]))
- else:
- flag = file_check.exists and value[0] == value[1]
- flags.append(flag)
- elif isinstance(value, bool):
- flags.append(value)
- return flags
-
- @staticmethod
- def _fill_non_key_values(env, model, pkg, vpf, file_check):
- """
- Fills a non key values into instance of SoftwareFileCheck.
- """
- model['FileName'] = os.path.basename(vpf.filename)
- model['FileChecksumType'] = csumt = \
- pywbem.Uint16(SoftwareFileCheck.pkg_checksum_type(pkg))
- if file_check is None:
- file_check = SoftwareFileCheck.test_file(env, csumt, vpf)
- for mattr, fattr in (
- ('FileType', 'file_type'),
- ('FileUserID', 'user_id'),
- ('FileGroupID', 'group_id'),
- ('FileMode', 'file_mode'),
- ('LastModificationTime', 'last_modification_time'),
- ('FileSize', 'file_size'),
- ('LinkTarget', 'link_target'),
- ('FileChecksum', 'file_checksum')):
- exp, rea = getattr(file_check, fattr)
- if exp is not None:
- model['Expected' + mattr] = exp
- if rea is not None:
- model[mattr] = rea
- model['ExpectedFileModeFlags'] = \
- SoftwareFileCheck.mode2pywbem_flags(file_check.file_mode[0])
- if file_check.exists:
- model['FileModeFlags'] = \
- SoftwareFileCheck.mode2pywbem_flags(file_check.file_mode[1])
- model['FileExists'] = file_check.exists
- if file_check.md5_checksum is not None:
- model['MD5Checksum'] = file_check.md5_checksum
- model['PassedFlags'] = SoftwareFileCheck._filecheck2model_flags(
- file_check)
- model['PassedFlagsDescriptions'] = list(
- SoftwareFileCheck.passed_flags_descriptions)
-
- @staticmethod
- def filecheck2model(vpkg, file_name, env, keys_only=True,
- model=None, file_check=None):
- """
- @param vpkg is an instance of yum.packages_RPMVerifyPackage
- @param file_name a absolute file path contained in package
- @param keys_only if True, then only key values will be filed
- @param model if given, then this instance will be modified and
- returned
- @param file_check if not given, it will be computed
- @return instance of LMI_SoftwareFileCheck class with all desired
- values filed
- """
- if not isinstance(vpkg, yum.packages._RPMVerifyPackage):
- raise TypeError(
- "vpkg must be an instance of _RPMVerifyPackage")
- if not file_name in vpkg:
- raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
- "File \"%s\" not found among package files" % file_name)
- if model is None:
- model = pywbem.CIMInstanceName("LMI_SoftwareFileCheck",
- namespace="root/cimv2")
- if not keys_only:
- model = pywbem.CIMInstance("LMI_SoftwareFileCheck", path=model)
- if file_check is not None:
- if not isinstance(file_check, SoftwareFileCheck.FileCheck):
- raise TypeError("file_check must be an instance of FileCheck")
- pkg = vpkg.po
- vpf = vpkg._files[file_name]
- model['Name'] = vpf.filename
- model['SoftwareElementID'] = pkg.nevra
- model['SoftwareElementState'] = pywbem.Uint16(2)
- model['TargetOperatingSystem'] = pywbem.Uint16(
- get_target_operating_system()[0])
- model['Version'] = pkg.version
- model['CheckID'] = '%s#%s' % (pkg.name, vpf.filename)
- if not keys_only:
- SoftwareFileCheck._fill_non_key_values(
- env, model, pkg, vpf, file_check)
- return model
-