diff options
author | Michal Minar <miminar@redhat.com> | 2013-02-04 12:37:58 +0100 |
---|---|---|
committer | Michal Minar <miminar@redhat.com> | 2013-02-04 14:51:03 +0100 |
commit | 775290a2c13b801c84fefdc4538fe110c4df6c24 (patch) | |
tree | 04427c4859b874ed4346a042f3d097ba51fa50d7 | |
parent | 861a85d3b8e7ed6eaa68edc6d03f1192d46272e1 (diff) | |
download | openlmi-providers-775290a2c13b801c84fefdc4538fe110c4df6c24.tar.gz openlmi-providers-775290a2c13b801c84fefdc4538fe110c4df6c24.tar.xz openlmi-providers-775290a2c13b801c84fefdc4538fe110c4df6c24.zip |
rewritten for safe execution of transactions
Made separate process openlmi.software.yumdb.process.YumWorker
for calls to yum API. Its client openlmi.software.yumdb.YumDB
communicates with it via synchronnous queues - uplink and downlink.
Resolves: #63 in openlmi trac -- yum API not useable, while changing
thread_id)
Resolves: #33 in openlmi trac -- Install/remove package
Common functionality of providers moved under openlmi.software.core
subpackage to make them easily accessible from other providers without
cyclic dependencies.
Improved logging with cmpi_logging module.
openlmi.software.cimom_entry module now is the only module loadable by
cmpi-bindings. It sets up providers and maps them by their name.
New subpackages:
openlmi.software.core
openlmi.software.yumdb
28 files changed, 3406 insertions, 1606 deletions
diff --git a/mof/LMI_Software.reg b/mof/LMI_Software.reg index 4e02120..1c6f323 100644 --- a/mof/LMI_Software.reg +++ b/mof/LMI_Software.reg @@ -1,23 +1,23 @@ [LMI_SoftwarePackage] - provider: /usr/lib/python2.7/site-packages/openlmi/software/LMI_SoftwarePackage.py + provider: /usr/lib/python2.7/site-packages/openlmi/software/cimom_entry.py location: pyCmpiProvider type: instance method namespace: root/cimv2 [LMI_SoftwareInstalledPackage] - provider: /usr/lib/python2.7/site-packages/openlmi/software/LMI_SoftwareInstalledPackage.py + provider: /usr/lib/python2.7/site-packages/openlmi/software/cimom_entry.py location: pyCmpiProvider type: instance association method namespace: root/cimv2 [LMI_SoftwareFileCheck] - provider: /usr/lib/python2.7/site-packages/openlmi/software/LMI_SoftwareFileCheck.py + provider: /usr/lib/python2.7/site-packages/openlmi/software/cimom_entry.py location: pyCmpiProvider type: instance method namespace: root/cimv2 [LMI_SoftwarePackageChecks] - provider: /usr/lib/python2.7/site-packages/openlmi/software/LMI_SoftwarePackageChecks.py + provider: /usr/lib/python2.7/site-packages/openlmi/software/cimom_entry.py location: pyCmpiProvider type: instance association namespace: root/cimv2 diff --git a/src/software/openlmi/software/LMI_SoftwareFileCheck.py b/src/software/openlmi/software/LMI_SoftwareFileCheck.py index 63070fc..7d4aea6 100644 --- a/src/software/openlmi/software/LMI_SoftwareFileCheck.py +++ b/src/software/openlmi/software/LMI_SoftwareFileCheck.py @@ -27,8 +27,10 @@ Instruments the CIM class LMI_SoftwareFileCheck import pywbem from pywbem.cim_provider2 import CIMProvider2 -from openlmi.software.util import common -from openlmi.software.util.common import SoftwareFileCheck + +from openlmi.software.core import SoftwareFileCheck +from openlmi.software.util import cmpi_logging +from openlmi.software.yumdb import YumDB class LMI_SoftwareFileCheck(CIMProvider2): # pylint: disable=R0904 """Instrument the CIM class LMI_SoftwareFileCheck @@ -39,12 +41,11 @@ class LMI_SoftwareFileCheck(CIMProvider2): # pylint: disable=R0904 """ - def __init__(self, env): - logger = env.get_logger() - logger.log_debug('Initializing provider %s from %s' \ + def __init__(self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ % (self.__class__.__name__, __file__)) - + @cmpi_logging.trace_method def get_instance(self, env, model): """Return an instance. @@ -67,16 +68,13 @@ class LMI_SoftwareFileCheck(CIMProvider2): # pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.get_instance()' \ - % self.__class__.__name__) - - with common.YumDB.getInstance(env): - vpkg = SoftwareFileCheck.object_path2yumcheck(env, model.path) + with YumDB.getInstance(): + pkg_info, pkg_check, pkg_file = \ + SoftwareFileCheck.object_path2pkg_file(model.path) return SoftwareFileCheck.filecheck2model( - vpkg, model['Name'], env, False) + pkg_info, pkg_check, pkg_file.path, keys_only=False) + @cmpi_logging.trace_method def enum_instances(self, env, model, keys_only): """Enumerate instances. @@ -99,13 +97,10 @@ class LMI_SoftwareFileCheck(CIMProvider2): # pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.enum_instances()' \ - % self.__class__.__name__) # this won't be supported because of enormous amount of data raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + @cmpi_logging.trace_method def set_instance(self, env, instance, modify_existing): """Return a newly created or modified instance. @@ -132,12 +127,9 @@ class LMI_SoftwareFileCheck(CIMProvider2): # pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.set_instance()' \ - % self.__class__.__name__) raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + @cmpi_logging.trace_method def delete_instance(self, env, instance_name): """Delete an instance. @@ -159,13 +151,9 @@ class LMI_SoftwareFileCheck(CIMProvider2): # pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.delete_instance()' \ - % self.__class__.__name__) - raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + @cmpi_logging.trace_method def cim_method_invoke(self, env, object_name): """Implements LMI_SoftwareFileCheck.Invoke() @@ -203,23 +191,18 @@ class LMI_SoftwareFileCheck(CIMProvider2): # pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.cim_method_invoke()' \ - % self.__class__.__name__) - - with common.YumDB.getInstance(env): - vpkg = SoftwareFileCheck.object_path2yumcheck(env, object_name) - sfc = SoftwareFileCheck.test_file(env, - SoftwareFileCheck.pkg_checksum_type(vpkg.po), - vpkg._files[object_name["Name"]]) + with YumDB.getInstance(): + _, pkg_check, pkg_file = \ + SoftwareFileCheck.object_path2pkg_file(object_name) + sfc = SoftwareFileCheck.test_file( + pkg_check.file_checksum_type, pkg_file) out_params = [] ret = 0 if SoftwareFileCheck.filecheck_passed(sfc) else 2 return (pywbem.Uint32(ret), out_params) - def cim_method_invokeonsystem(self, env, - object_name, #pylint: disable=W0613 - param_targetsystem=None): #pylint: disable=W0613 + @cmpi_logging.trace_method + def cim_method_invokeonsystem(self, env, object_name, + param_targetsystem=None): """Implements LMI_SoftwareFileCheck.InvokeOnSystem() The InvokeOnSystem method evaluates this Check. The details of the @@ -259,146 +242,6 @@ class LMI_SoftwareFileCheck(CIMProvider2): # pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.cim_method_invokeonsystem()' \ - % self.__class__.__name__) - # TODO do something raise pywbem.CIMError(pywbem.CIM_ERR_METHOD_NOT_AVAILABLE) - class Values(object): - class TargetOperatingSystem(object): - Unknown = pywbem.Uint16(0) - Other = pywbem.Uint16(1) - MACOS = pywbem.Uint16(2) - ATTUNIX = pywbem.Uint16(3) - DGUX = pywbem.Uint16(4) - DECNT = pywbem.Uint16(5) - Tru64_UNIX = pywbem.Uint16(6) - OpenVMS = pywbem.Uint16(7) - HPUX = pywbem.Uint16(8) - AIX = pywbem.Uint16(9) - MVS = pywbem.Uint16(10) - OS400 = pywbem.Uint16(11) - OS_2 = pywbem.Uint16(12) - JavaVM = pywbem.Uint16(13) - MSDOS = pywbem.Uint16(14) - WIN3x = pywbem.Uint16(15) - WIN95 = pywbem.Uint16(16) - WIN98 = pywbem.Uint16(17) - WINNT = pywbem.Uint16(18) - WINCE = pywbem.Uint16(19) - NCR3000 = pywbem.Uint16(20) - NetWare = pywbem.Uint16(21) - OSF = pywbem.Uint16(22) - DC_OS = pywbem.Uint16(23) - Reliant_UNIX = pywbem.Uint16(24) - SCO_UnixWare = pywbem.Uint16(25) - SCO_OpenServer = pywbem.Uint16(26) - Sequent = pywbem.Uint16(27) - IRIX = pywbem.Uint16(28) - Solaris = pywbem.Uint16(29) - SunOS = pywbem.Uint16(30) - U6000 = pywbem.Uint16(31) - ASERIES = pywbem.Uint16(32) - HP_NonStop_OS = pywbem.Uint16(33) - HP_NonStop_OSS = pywbem.Uint16(34) - BS2000 = pywbem.Uint16(35) - LINUX = pywbem.Uint16(36) - Lynx = pywbem.Uint16(37) - XENIX = pywbem.Uint16(38) - VM = pywbem.Uint16(39) - Interactive_UNIX = pywbem.Uint16(40) - BSDUNIX = pywbem.Uint16(41) - FreeBSD = pywbem.Uint16(42) - NetBSD = pywbem.Uint16(43) - GNU_Hurd = pywbem.Uint16(44) - OS9 = pywbem.Uint16(45) - MACH_Kernel = pywbem.Uint16(46) - Inferno = pywbem.Uint16(47) - QNX = pywbem.Uint16(48) - EPOC = pywbem.Uint16(49) - IxWorks = pywbem.Uint16(50) - VxWorks = pywbem.Uint16(51) - MiNT = pywbem.Uint16(52) - BeOS = pywbem.Uint16(53) - HP_MPE = pywbem.Uint16(54) - NextStep = pywbem.Uint16(55) - PalmPilot = pywbem.Uint16(56) - Rhapsody = pywbem.Uint16(57) - Windows_2000 = pywbem.Uint16(58) - Dedicated = pywbem.Uint16(59) - OS_390 = pywbem.Uint16(60) - VSE = pywbem.Uint16(61) - TPF = pywbem.Uint16(62) - Windows__R__Me = pywbem.Uint16(63) - Caldera_Open_UNIX = pywbem.Uint16(64) - OpenBSD = pywbem.Uint16(65) - Not_Applicable = pywbem.Uint16(66) - Windows_XP = pywbem.Uint16(67) - z_OS = pywbem.Uint16(68) - Microsoft_Windows_Server_2003 = pywbem.Uint16(69) - Microsoft_Windows_Server_2003_64_Bit = pywbem.Uint16(70) - Windows_XP_64_Bit = pywbem.Uint16(71) - Windows_XP_Embedded = pywbem.Uint16(72) - Windows_Vista = pywbem.Uint16(73) - Windows_Vista_64_Bit = pywbem.Uint16(74) - Windows_Embedded_for_Point_of_Service = pywbem.Uint16(75) - Microsoft_Windows_Server_2008 = pywbem.Uint16(76) - Microsoft_Windows_Server_2008_64_Bit = pywbem.Uint16(77) - FreeBSD_64_Bit = pywbem.Uint16(78) - RedHat_Enterprise_Linux = pywbem.Uint16(79) - RedHat_Enterprise_Linux_64_Bit = pywbem.Uint16(80) - Solaris_64_Bit = pywbem.Uint16(81) - SUSE = pywbem.Uint16(82) - SUSE_64_Bit = pywbem.Uint16(83) - SLES = pywbem.Uint16(84) - SLES_64_Bit = pywbem.Uint16(85) - Novell_OES = pywbem.Uint16(86) - Novell_Linux_Desktop = pywbem.Uint16(87) - Sun_Java_Desktop_System = pywbem.Uint16(88) - Mandriva = pywbem.Uint16(89) - Mandriva_64_Bit = pywbem.Uint16(90) - TurboLinux = pywbem.Uint16(91) - TurboLinux_64_Bit = pywbem.Uint16(92) - Ubuntu = pywbem.Uint16(93) - Ubuntu_64_Bit = pywbem.Uint16(94) - Debian = pywbem.Uint16(95) - Debian_64_Bit = pywbem.Uint16(96) - Linux_2_4_x = pywbem.Uint16(97) - Linux_2_4_x_64_Bit = pywbem.Uint16(98) - Linux_2_6_x = pywbem.Uint16(99) - Linux_2_6_x_64_Bit = pywbem.Uint16(100) - Linux_64_Bit = pywbem.Uint16(101) - Other_64_Bit = pywbem.Uint16(102) - Microsoft_Windows_Server_2008_R2 = pywbem.Uint16(103) - VMware_ESXi = pywbem.Uint16(104) - Microsoft_Windows_7 = pywbem.Uint16(105) - CentOS_32_bit = pywbem.Uint16(106) - CentOS_64_bit = pywbem.Uint16(107) - Oracle_Enterprise_Linux_32_bit = pywbem.Uint16(108) - Oracle_Enterprise_Linux_64_bit = pywbem.Uint16(109) - eComStation_32_bitx = pywbem.Uint16(110) - - class SoftwareElementState(object): - Deployable = pywbem.Uint16(0) - Installable = pywbem.Uint16(1) - Executable = pywbem.Uint16(2) - Running = pywbem.Uint16(3) - - class FileType(object): - Unknown = pywbem.Uint16(0) - File = pywbem.Uint16(1) - Directory = pywbem.Uint16(2) - Symlink = pywbem.Uint16(3) - FIFO = pywbem.Uint16(4) - Character_Device = pywbem.Uint16(5) - Block_Device = pywbem.Uint16(6) - -## end of class LMI_SoftwareFileCheckProvider - -def get_providers(env): - """for associating CIM Class Name to python provider class name""" - lmi_softwarefilecheck_prov = LMI_SoftwareFileCheck(env) - return {'LMI_SoftwareFileCheck': lmi_softwarefilecheck_prov} diff --git a/src/software/openlmi/software/LMI_SoftwareInstalledPackage.py b/src/software/openlmi/software/LMI_SoftwareInstalledPackage.py index 8914c47..d82a5a2 100644 --- a/src/software/openlmi/software/LMI_SoftwareInstalledPackage.py +++ b/src/software/openlmi/software/LMI_SoftwareInstalledPackage.py @@ -26,14 +26,14 @@ Instruments the CIM class LMI_SoftwareInstalledPackage """ -import itertools import pywbem -import yum from pywbem.cim_provider2 import CIMProvider2 -from openlmi.software.LMI_SoftwarePackage import LMI_SoftwarePackage -from openlmi.software.util import common -from openlmi.software.util.common import ( - YumDB, SoftwarePackage, SoftwareFileCheck) + +from openlmi.software.core import ( + ComputerSystem, SoftwareFileCheck, + SoftwareInstalledPackage, SoftwarePackage) +from openlmi.software.util import cmpi_logging +from openlmi.software.yumdb import YumDB class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 """Instrument the CIM class LMI_SoftwareInstalledPackage @@ -43,11 +43,11 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 """ - def __init__ (self, env): - logger = env.get_logger() - logger.log_debug('Initializing provider %s from %s' \ + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ % (self.__class__.__name__, __file__)) + @cmpi_logging.trace_method def get_instance(self, env, model): """Return an instance. @@ -70,24 +70,22 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.get_instance()' \ - % self.__class__.__name__) - if not "Software" in model: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing Software property.") if not "System" in model: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing System property.") - common.check_computer_system_op(env, model['System']) - model['System'] = common.get_computer_system_op() - with YumDB.getInstance(env): - pkg = SoftwarePackage.object_path2pkg(env, model['Software']) - model['Software'] = SoftwarePackage.pkg2model(env, pkg, True) + ComputerSystem.check_path_property(env, model, 'System') + model['System'] = ComputerSystem.get_path() + with YumDB.getInstance(): + pkg_info = SoftwarePackage.object_path2pkg(model['Software'], + kind="installed") + model['Software'] = SoftwarePackage.pkg2model( + pkg_info, keys_only=True) return model + @cmpi_logging.trace_method def enum_instances(self, env, model, keys_only): """Enumerate instances. @@ -110,11 +108,6 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.enum_instances()' \ - % self.__class__.__name__) - # Prime model.path with knowledge of the keys, so key values on # the CIMInstanceName (model.path) will automatically be set when # we set property values on the model. @@ -123,22 +116,15 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 yum_package_path = pywbem.CIMInstanceName("LMI_SoftwarePackage", namespace=model.path.namespace, host=model.path.host) - model['System'] = common.get_computer_system_op() - with YumDB.getInstance(env) as ydb: - for pkg in ydb.rpmdb: - iname = SoftwarePackage.pkg2model(env, pkg, - True, yum_package_path) + model['System'] = ComputerSystem.get_path() + with YumDB.getInstance() as ydb: + pkglist = ydb.get_package_list('installed') + for pkg in pkglist: + iname = SoftwarePackage.pkg2model(pkg, model=yum_package_path) model['Software'] = iname - if keys_only: - yield model - else: - try: - yield self.get_instance(env, model) - except pywbem.CIMError as error: - if error.args[0] not in (pywbem.CIM_ERR_NOT_FOUND, - pywbem.CIM_ERR_ACCESS_DENIED): - raise + yield model + @cmpi_logging.trace_method def set_instance(self, env, instance, modify_existing): """Return a newly created or modified instance. @@ -165,11 +151,6 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.set_instance()' \ - % self.__class__.__name__) - # parse and check arguments if modify_existing is True: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED, @@ -181,29 +162,24 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 if not "System" in instance: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing System property.") - common.check_computer_system_op(env, instance['System']) + ComputerSystem.check_path_property(env, instance, 'System') + + with YumDB.getInstance() as ydb: + pkg_info = SoftwarePackage.object_path2pkg_search( + instance['Software']) - with YumDB.getInstance(env) as ydb: - pkg = SoftwarePackage.object_path2pkg_search( - env, instance['Software']) - if isinstance(pkg, yum.rpmsack.RPMInstalledPackage): + if pkg_info.installed: raise pywbem.CIMError(pywbem.CIM_ERR_ALREADY_EXISTS, - "Package is already installed.") - - logger.log_info('installing package {}'.format(pkg.nevra)) - # install - ydb.install(pkg) - ydb.buildTransaction() - ydb.processTransaction() - logger.log_info('package installed'.format(pkg.nevra)) - - # return instance - pkg_iname = SoftwarePackage.pkg2model(env, pkg, True) - pkg_iname["SoftwareElementState"] = \ - LMI_SoftwarePackage.Values.SoftwareElementState.Executable - instance["Software"] = pkg_iname - return self.get_instance(env, instance) + "Package %s is already installed." % pkg_info) + + cmpi_logging.logger.info('installing package %s' % pkg_info) + installed_pkg = ydb.install_package(pkg_info) + cmpi_logging.logger.info('package %s installed' % pkg_info) + instance["Software"] = SoftwarePackage.pkg2model(installed_pkg) + return instance + + @cmpi_logging.trace_method def delete_instance(self, env, instance_name): """Delete an instance. @@ -225,27 +201,22 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.delete_instance()' \ - % self.__class__.__name__) - if not "Software" in instance_name: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing Software property.") if not "System" in instance_name: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing System property.") - common.check_computer_system_op(env, instance_name['System']) - with YumDB.getInstance(env) as ydb: - pkg = SoftwarePackage.object_path2pkg( - env, instance_name["Software"]) - logger.log_info('removing package "%s"' % pkg.nevra) - ydb.remove(pkg) - ydb.buildTransaction() - ydb.processTransaction() - logger.log_info('package "%s" removed' % pkg.nevra) + ComputerSystem.check_path_property(env, instance_name, 'System') + + with YumDB.getInstance() as ydb: + pkg_info = SoftwarePackage.object_path2pkg( + instance_name["Software"], kind="installed") + cmpi_logging.logger.info('removing package "%s"' % pkg_info) + ydb.remove_package(pkg_info) + cmpi_logging.logger.info('package "%s" removed' % pkg_info) + @cmpi_logging.trace_method def references(self, env, object_name, model, result_class_name, role, result_role, keys_only): """Instrument Associations. @@ -304,10 +275,6 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.references()' \ - % self.__class__.__name__) cimhandle = env.get_cimom_handle() # If you want to get references for free, implemented in terms @@ -321,6 +288,7 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 return self.simple_refs(env, object_name, model, result_class_name, role, result_role, keys_only) + @cmpi_logging.trace_method def cim_method_checkintegrity(self, env, object_name): """Implements LMI_SoftwarePackage.CheckIntegrity() @@ -337,7 +305,7 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 should be invoked. Returns a two-tuple containing the return value ( - type pywbem.Uint32 self.Values.CheckIntegrity) + type pywbem.Uint32 SoftwareInstalledPackage.Values.CheckIntegrity) and a list of CIMParameter objects representing the output parameters Output parameters: @@ -357,36 +325,33 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.cim_method_checkintegrity()' \ - % self.__class__.__name__) - - failed = [] if not "Software" in object_name: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing Software property.") if not "System" in object_name: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing System property.") - with YumDB.getInstance(env): - pkg = SoftwarePackage.object_path2pkg(env, object_name['Software']) - csum = SoftwareFileCheck.pkg_checksum_type(pkg) - vpkg = yum.packages._RPMVerifyPackage( - pkg, pkg.hdr.fiFromHeader(), csum, [], True) - for vpf in vpkg: - file_check = SoftwareFileCheck.test_file(env, csum, vpf) + + failed = [] + with YumDB.getInstance() as ydb: + pkg_info = SoftwarePackage.object_path2pkg(object_name['Software'], + kind="installed") + pkg_check = ydb.check_package(pkg_info) + for pkg_file in pkg_check.files.values(): + file_check = SoftwareFileCheck.test_file( + pkg_check.file_checksum_type, pkg_file) if SoftwareFileCheck.filecheck_passed(file_check): continue failed.append(SoftwareFileCheck.filecheck2model( - vpkg, vpf.filename, env, keys_only=True, - file_check=file_check)) + pkg_info, pkg_check, pkg_file.path, + keys_only=True, file_check=file_check)) out_params = [ pywbem.CIMParameter('Failed', type='reference', value=failed) ] - return ( getattr(self.Values.CheckIntegrity, + return ( getattr(SoftwareInstalledPackage.Values.CheckIntegrity, 'Pass' if len(failed) == 0 else 'Not_passed') , out_params ) + @cmpi_logging.trace_method def cim_method_update(self, env, object_name, param_epoch=None, param_release=None, @@ -410,7 +375,7 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 newest, when empty Returns a two-tuple containing the return value ( - type pywbem.Uint16 self.Values.Update) + type pywbem.Uint16 SoftwareInstalledPackage.Values.Update) and a list of CIMParameter objects representing the output parameters Output parameters: @@ -430,86 +395,57 @@ class LMI_SoftwareInstalledPackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.cim_method_update()' \ - % self.__class__.__name__) - if not "Software" in object_name: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing Software property.") if not "System" in object_name: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing System property.") - common.check_computer_system_op(env, object_name['System']) - - with YumDB.getInstance(env) as ydb: - orig = SoftwarePackage.object_path2pkg(env, object_name['Software']) - - evr_str = [] - for name, param in ( - ('epoch', param_epoch), - ('version', param_version), - ('release', param_release)): - evr_str.append("%s(%s)"%(name, param)) - if len(evr_str): - evr_str = "specific "+'-'.join(evr_str) - else: - evr_str = "the newest version-release" - - logger.log_info('trying to update to %s of package \"%s\"' % - (evr_str, object_name["Software"]["Name"])) - - pkglist = ydb.doPackageLists('all', showdups=True) - # NOTE: available ∩ installed = ∅ - exact, _, _ = yum.packages.parsePackages( - itertools.chain(pkglist.available, pkglist.installed), - [orig.name]) - try: - pkg = [ p for p in sorted(exact) - if (not param_epoch or param_epoch == p.epoch) - and (not param_version or param_version == p.ver) - and (not param_release or param_release == p.rel) ] [-1] - except IndexError: - logger.log_error( - 'could not find any matching available package') - raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND) - out_params = [pywbem.CIMParameter('Installed', type='reference', - value=SoftwarePackage.pkg2model(env, pkg, True))] - if orig.evra == pkg.evra: - logger.log_info('already up to date') - return (self.Values.Update.Already_newest, out_params) - - ydb.update(update_to=True, - name=pkg.name, - epoch=pkg.epoch, - version=pkg.version, - release=pkg.release) - ydb.buildTransaction() - ydb.processTransaction() - logger.log_info('package {} updated to: {}'.format( - orig.name, pkg.evra)) - - out_params[0].value["SoftwareElementState"] = \ - LMI_SoftwarePackage.Values.SoftwareElementState.Executable - - return (self.Values.Update.Successful_installation, out_params) - - class Values(object): - class Update(object): - Already_newest = pywbem.Uint16(0) - Successful_installation = pywbem.Uint16(1) - Failed = pywbem.Uint16(2) - - class CheckIntegrity(object): - Pass = pywbem.Uint32(0) - Not_passed = pywbem.Uint32(1) - Error = pywbem.Uint32(2) - -## end of class LMI_SoftwareInstalledPackage - - -def get_providers(env): - """Associates CIM Class Name to python provider class name""" - lmi_sip_prov = LMI_SoftwareInstalledPackage(env) - return {'LMI_SoftwareInstalledPackage': lmi_sip_prov} + ComputerSystem.check_path_property(env, object_name, 'System') + + with YumDB.getInstance() as ydb: + orig = SoftwarePackage.object_path2pkg(object_name['Software'], + kind='installed') + + # NOTE: that we need to obtain all available and installed packages + # because they are disjunctive and we want to select package + # with highest version. Let the yum do the sorting of packages. + pkglist = ydb.filter_packages('all', + allow_duplicates=True, sort=True, + name=orig.name, + epoch=param_epoch, + version=param_version, + release=param_release, + arch=orig.arch) + + if len(pkglist) < 1: + cmpi_logging.logger.error( + "desired package matching evr not found") + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "package matching desired evr not found among" + " available packages") + if len(pkglist) > 1: + cmpi_logging.logger.info("multiple packages matching" + " evr - selecting newest") + cmpi_logging.logger.debug("matching packages (from oldest):" + " [%s]" % ", ".join([str(p) for p in pkglist])) + desired_pkg = pkglist[-1] + + out_params = [pywbem.CIMParameter('Installed', type='reference')] + if desired_pkg.installed: + out_params[0].value = SoftwarePackage.pkg2model(desired_pkg) + cmpi_logging.logger.info('already up to date') + return ( SoftwareInstalledPackage.Values.Update.Already_newest + , out_params) + + cmpi_logging.logger.info( + 'trying to update package \"%s\" to "%s"' % ( + orig, desired_pkg)) + installed_pkg = ydb.update_to_package(desired_pkg) + cmpi_logging.logger.info('update successful') + + out_params[0].value = SoftwarePackage.pkg2model(installed_pkg) + + return ( SoftwareInstalledPackage.Values.Update.Successful_installation + , out_params) + diff --git a/src/software/openlmi/software/LMI_SoftwarePackage.py b/src/software/openlmi/software/LMI_SoftwarePackage.py index b595600..e031998 100644 --- a/src/software/openlmi/software/LMI_SoftwarePackage.py +++ b/src/software/openlmi/software/LMI_SoftwarePackage.py @@ -26,13 +26,14 @@ Instruments the CIM class LMI_SoftwarePackage """ -import itertools import pywbem -import yum from pywbem.cim_provider2 import CIMProvider2 -from openlmi.software.util.common import (YumDB, SoftwarePackage) -class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 +from openlmi.software.core import SoftwarePackage +from openlmi.software.util import cmpi_logging +from openlmi.software.yumdb import YumDB + +class LMI_SoftwarePackage(CIMProvider2): """Instrument the CIM class LMI_SoftwarePackage RPM package installed on particular computer system with YUM (The @@ -40,11 +41,11 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 """ - def __init__(self, env): - logger = env.get_logger() - logger.log_debug('Initializing provider %s from %s' \ + def __init__(self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ % (self.__class__.__name__, __file__)) + @cmpi_logging.trace_method def get_instance(self, env, model): """Return an instance. @@ -67,16 +68,12 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ + with YumDB.getInstance(): + pkg_info = SoftwarePackage.object_path2pkg(model.path, 'all') + return SoftwarePackage.pkg2model( + pkg_info, keys_only=False, model=model) - logger = env.get_logger() - logger.log_debug('Entering %s.get_instance()' \ - % self.__class__.__name__) - - with YumDB.getInstance(env): - pkg = SoftwarePackage.object_path2pkg(env, model.path, 'all') - return SoftwarePackage.pkg2model(env, pkg, - keys_only=False, model=model) - + @cmpi_logging.trace_method def enum_instances(self, env, model, keys_only): """Enumerate instances. @@ -99,11 +96,6 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.enum_instances()' \ - % self.__class__.__name__) - # Prime model.path with knowledge of the keys, so key values on # the CIMInstanceName (model.path) will automatically be set when # we set property values on the model. @@ -111,14 +103,15 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 'SoftwareElementState': None, 'Name': None, 'SoftwareElementID': None}) - with YumDB.getInstance(env) as ydb: + with YumDB.getInstance() as ydb: # get all packages - pkglist = ydb.doPackageLists('all', showdups=True) - pkglist = itertools.chain(pkglist.installed, pkglist.available) - # NOTE: available ∩ installed = ∅ - for pkg in sorted(pkglist): - yield SoftwarePackage.pkg2model(env, pkg, keys_only, model) + pkglist = ydb.get_package_list('all', + allow_duplicates=True, sort=True) + for pkg in pkglist: + yield SoftwarePackage.pkg2model(pkg, + keys_only=keys_only, model=model) + @cmpi_logging.trace_method def set_instance(self, env, instance, modify_existing): """Return a newly created or modified instance. @@ -145,12 +138,9 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.set_instance()' \ - % self.__class__.__name__) raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + @cmpi_logging.trace_method def delete_instance(self, env, instance_name): """Delete an instance. @@ -172,12 +162,9 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.delete_instance()' \ - % self.__class__.__name__) raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + @cmpi_logging.trace_method def cim_method_install(self, env, object_name): """Implements LMI_SoftwarePackage.Install() @@ -189,7 +176,8 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 specifying the object on which the method Update() should be invoked. - Returns a two-tuple containing the return value (type pywbem.Uint32 self.Values.Install) + Returns a two-tuple containing the return value ( + type pywbem.Uint32 SoftwarePackage.Values.Install) and a list of CIMParameter objects representing the output parameters Output parameters: @@ -209,31 +197,26 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - logger = env.get_logger() - logger.log_debug('Entering %s.cim_method_install()' \ - % self.__class__.__name__) - - with YumDB.getInstance(env) as ydb: + with YumDB.getInstance() as ydb: # get available packages - pkg = SoftwarePackage.object_path2pkg_search(env, object_name) + pkg_info = SoftwarePackage.object_path2pkg_search(object_name) out_params = [ pywbem.CIMParameter('Installed', type='reference') ] - if isinstance(pkg, yum.rpmsack.RPMInstalledPackage): - out_params[0].value = SoftwarePackage.pkg2model(env, pkg, True) - return (self.Values.Install.Already_installed, out_params) + if pkg_info.installed: + out_params[0].value = SoftwarePackage.pkg2model( + pkg_info, keys_only=True) + return ( SoftwarePackage.Values.Install.Already_installed + , out_params) - logger.log_info('installing package {}'.format(pkg.nevra)) - # install - ydb.install(pkg) - ydb.buildTransaction() - ydb.processTransaction() - logger.log_info('package installed'.format(pkg.nevra)) + cmpi_logging.logger.info('installing package %s' % pkg_info) + installed_pkg = ydb.install_package(pkg_info) + cmpi_logging.logger.info('package %s installed' % pkg_info) out_params[0].value = SoftwarePackage.pkg2model( - env, pkg, True, object_name) - out_params[0].value['SoftwareElementState'] = \ - self.Values.SoftwareElementState.Executable - return (self.Values.Install.Successful_installation, out_params) + installed_pkg, keys_only=True) + return ( SoftwarePackage.Values.Install.Successful_installation + , out_params) + @cmpi_logging.trace_method def cim_method_remove(self, env, object_name): """Implements LMI_SoftwarePackage.Remove() @@ -245,7 +228,8 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 specifying the object on which the method Remove() should be invoked. - Returns a two-tuple containing the return value (type pywbem.Uint32 self.Values.Remove) + Returns a two-tuple containing the return value ( + type pywbem.Uint32 SoftwarePackage.Values.Remove) and a list of CIMParameter objects representing the output parameters Output parameters: none @@ -261,255 +245,14 @@ class LMI_SoftwarePackage(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.cim_method_remove()' \ - % self.__class__.__name__) - - with YumDB.getInstance(env) as ydb: - pkg = SoftwarePackage.object_path2pkg(env, object_name, 'all') - if isinstance(pkg, yum.rpmsack.RPMInstalledPackage): - logger.log_info('removing package "%s"' % pkg.nevra) - ydb.remove(pkg) - ydb.buildTransaction() - ydb.processTransaction() - logger.log_info('package "%s" removed' % pkg.nevra) - rval = self.Values.Remove.Successful_removal + with YumDB.getInstance() as ydb: + pkg_info = SoftwarePackage.object_path2pkg(object_name, 'all') + if pkg_info.installed: + cmpi_logging.logger.info('removing package %s' % pkg_info) + ydb.remove_package(pkg_info) + cmpi_logging.logger.info('package %s removed' % pkg_info) + rval = SoftwarePackage.Values.Remove.Successful_removal else: - rval = self.Values.Remove.Not_installed + rval = SoftwarePackage.Values.Remove.Not_installed return (rval, []) - class Values(object): - class DetailedStatus(object): - Not_Available = pywbem.Uint16(0) - No_Additional_Information = pywbem.Uint16(1) - Stressed = pywbem.Uint16(2) - Predictive_Failure = pywbem.Uint16(3) - Non_Recoverable_Error = pywbem.Uint16(4) - Supporting_Entity_in_Error = pywbem.Uint16(5) - # DMTF_Reserved = .. - # Vendor_Reserved = 0x8000.. - - class Status(object): - OK = 'OK' - Error = 'Error' - Degraded = 'Degraded' - Unknown = 'Unknown' - Pred_Fail = 'Pred Fail' - Starting = 'Starting' - Stopping = 'Stopping' - Service = 'Service' - Stressed = 'Stressed' - NonRecover = 'NonRecover' - No_Contact = 'No Contact' - Lost_Comm = 'Lost Comm' - Stopped = 'Stopped' - - class HealthState(object): - Unknown = pywbem.Uint16(0) - OK = pywbem.Uint16(5) - Degraded_Warning = pywbem.Uint16(10) - Minor_failure = pywbem.Uint16(15) - Major_failure = pywbem.Uint16(20) - Critical_failure = pywbem.Uint16(25) - Non_recoverable_error = pywbem.Uint16(30) - # DMTF_Reserved = .. - # Vendor_Specific = 32768..65535 - - class TargetOperatingSystem(object): - Unknown = pywbem.Uint16(0) - Other = pywbem.Uint16(1) - MACOS = pywbem.Uint16(2) - ATTUNIX = pywbem.Uint16(3) - DGUX = pywbem.Uint16(4) - DECNT = pywbem.Uint16(5) - Tru64_UNIX = pywbem.Uint16(6) - OpenVMS = pywbem.Uint16(7) - HPUX = pywbem.Uint16(8) - AIX = pywbem.Uint16(9) - MVS = pywbem.Uint16(10) - OS400 = pywbem.Uint16(11) - OS_2 = pywbem.Uint16(12) - JavaVM = pywbem.Uint16(13) - MSDOS = pywbem.Uint16(14) - WIN3x = pywbem.Uint16(15) - WIN95 = pywbem.Uint16(16) - WIN98 = pywbem.Uint16(17) - WINNT = pywbem.Uint16(18) - WINCE = pywbem.Uint16(19) - NCR3000 = pywbem.Uint16(20) - NetWare = pywbem.Uint16(21) - OSF = pywbem.Uint16(22) - DC_OS = pywbem.Uint16(23) - Reliant_UNIX = pywbem.Uint16(24) - SCO_UnixWare = pywbem.Uint16(25) - SCO_OpenServer = pywbem.Uint16(26) - Sequent = pywbem.Uint16(27) - IRIX = pywbem.Uint16(28) - Solaris = pywbem.Uint16(29) - SunOS = pywbem.Uint16(30) - U6000 = pywbem.Uint16(31) - ASERIES = pywbem.Uint16(32) - HP_NonStop_OS = pywbem.Uint16(33) - HP_NonStop_OSS = pywbem.Uint16(34) - BS2000 = pywbem.Uint16(35) - LINUX = pywbem.Uint16(36) - Lynx = pywbem.Uint16(37) - XENIX = pywbem.Uint16(38) - VM = pywbem.Uint16(39) - Interactive_UNIX = pywbem.Uint16(40) - BSDUNIX = pywbem.Uint16(41) - FreeBSD = pywbem.Uint16(42) - NetBSD = pywbem.Uint16(43) - GNU_Hurd = pywbem.Uint16(44) - OS9 = pywbem.Uint16(45) - MACH_Kernel = pywbem.Uint16(46) - Inferno = pywbem.Uint16(47) - QNX = pywbem.Uint16(48) - EPOC = pywbem.Uint16(49) - IxWorks = pywbem.Uint16(50) - VxWorks = pywbem.Uint16(51) - MiNT = pywbem.Uint16(52) - BeOS = pywbem.Uint16(53) - HP_MPE = pywbem.Uint16(54) - NextStep = pywbem.Uint16(55) - PalmPilot = pywbem.Uint16(56) - Rhapsody = pywbem.Uint16(57) - Windows_2000 = pywbem.Uint16(58) - Dedicated = pywbem.Uint16(59) - OS_390 = pywbem.Uint16(60) - VSE = pywbem.Uint16(61) - TPF = pywbem.Uint16(62) - Windows__R__Me = pywbem.Uint16(63) - Caldera_Open_UNIX = pywbem.Uint16(64) - OpenBSD = pywbem.Uint16(65) - Not_Applicable = pywbem.Uint16(66) - Windows_XP = pywbem.Uint16(67) - z_OS = pywbem.Uint16(68) - Microsoft_Windows_Server_2003 = pywbem.Uint16(69) - Microsoft_Windows_Server_2003_64_Bit = pywbem.Uint16(70) - Windows_XP_64_Bit = pywbem.Uint16(71) - Windows_XP_Embedded = pywbem.Uint16(72) - Windows_Vista = pywbem.Uint16(73) - Windows_Vista_64_Bit = pywbem.Uint16(74) - Windows_Embedded_for_Point_of_Service = pywbem.Uint16(75) - Microsoft_Windows_Server_2008 = pywbem.Uint16(76) - Microsoft_Windows_Server_2008_64_Bit = pywbem.Uint16(77) - FreeBSD_64_Bit = pywbem.Uint16(78) - RedHat_Enterprise_Linux = pywbem.Uint16(79) - RedHat_Enterprise_Linux_64_Bit = pywbem.Uint16(80) - Solaris_64_Bit = pywbem.Uint16(81) - SUSE = pywbem.Uint16(82) - SUSE_64_Bit = pywbem.Uint16(83) - SLES = pywbem.Uint16(84) - SLES_64_Bit = pywbem.Uint16(85) - Novell_OES = pywbem.Uint16(86) - Novell_Linux_Desktop = pywbem.Uint16(87) - Sun_Java_Desktop_System = pywbem.Uint16(88) - Mandriva = pywbem.Uint16(89) - Mandriva_64_Bit = pywbem.Uint16(90) - TurboLinux = pywbem.Uint16(91) - TurboLinux_64_Bit = pywbem.Uint16(92) - Ubuntu = pywbem.Uint16(93) - Ubuntu_64_Bit = pywbem.Uint16(94) - Debian = pywbem.Uint16(95) - Debian_64_Bit = pywbem.Uint16(96) - Linux_2_4_x = pywbem.Uint16(97) - Linux_2_4_x_64_Bit = pywbem.Uint16(98) - Linux_2_6_x = pywbem.Uint16(99) - Linux_2_6_x_64_Bit = pywbem.Uint16(100) - Linux_64_Bit = pywbem.Uint16(101) - Other_64_Bit = pywbem.Uint16(102) - Microsoft_Windows_Server_2008_R2 = pywbem.Uint16(103) - VMware_ESXi = pywbem.Uint16(104) - Microsoft_Windows_7 = pywbem.Uint16(105) - CentOS_32_bit = pywbem.Uint16(106) - CentOS_64_bit = pywbem.Uint16(107) - Oracle_Enterprise_Linux_32_bit = pywbem.Uint16(108) - Oracle_Enterprise_Linux_64_bit = pywbem.Uint16(109) - eComStation_32_bitx = pywbem.Uint16(110) - - class Remove(object): - Not_installed = pywbem.Uint32(0) - Successful_removal = pywbem.Uint32(1) - Failed = pywbem.Uint32(2) - - class CommunicationStatus(object): - Unknown = pywbem.Uint16(0) - Not_Available = pywbem.Uint16(1) - Communication_OK = pywbem.Uint16(2) - Lost_Communication = pywbem.Uint16(3) - No_Contact = pywbem.Uint16(4) - # DMTF_Reserved = .. - # Vendor_Reserved = 0x8000.. - - class OperationalStatus(object): - Unknown = pywbem.Uint16(0) - Other = pywbem.Uint16(1) - OK = pywbem.Uint16(2) - Degraded = pywbem.Uint16(3) - Stressed = pywbem.Uint16(4) - Predictive_Failure = pywbem.Uint16(5) - Error = pywbem.Uint16(6) - Non_Recoverable_Error = pywbem.Uint16(7) - Starting = pywbem.Uint16(8) - Stopping = pywbem.Uint16(9) - Stopped = pywbem.Uint16(10) - In_Service = pywbem.Uint16(11) - No_Contact = pywbem.Uint16(12) - Lost_Communication = pywbem.Uint16(13) - Aborted = pywbem.Uint16(14) - Dormant = pywbem.Uint16(15) - Supporting_Entity_in_Error = pywbem.Uint16(16) - Completed = pywbem.Uint16(17) - Power_Mode = pywbem.Uint16(18) - Relocating = pywbem.Uint16(19) - # DMTF_Reserved = .. - # Vendor_Reserved = 0x8000.. - - class OperatingStatus(object): - Unknown = pywbem.Uint16(0) - Not_Available = pywbem.Uint16(1) - Servicing = pywbem.Uint16(2) - Starting = pywbem.Uint16(3) - Stopping = pywbem.Uint16(4) - Stopped = pywbem.Uint16(5) - Aborted = pywbem.Uint16(6) - Dormant = pywbem.Uint16(7) - Completed = pywbem.Uint16(8) - Migrating = pywbem.Uint16(9) - Emigrating = pywbem.Uint16(10) - Immigrating = pywbem.Uint16(11) - Snapshotting = pywbem.Uint16(12) - Shutting_Down = pywbem.Uint16(13) - In_Test = pywbem.Uint16(14) - Transitioning = pywbem.Uint16(15) - In_Service = pywbem.Uint16(16) - # DMTF_Reserved = .. - # Vendor_Reserved = 0x8000.. - - class SoftwareElementState(object): - Deployable = pywbem.Uint16(0) - Installable = pywbem.Uint16(1) - Executable = pywbem.Uint16(2) - Running = pywbem.Uint16(3) - - class PrimaryStatus(object): - Unknown = pywbem.Uint16(0) - OK = pywbem.Uint16(1) - Degraded = pywbem.Uint16(2) - Error = pywbem.Uint16(3) - # DMTF_Reserved = .. - # Vendor_Reserved = 0x8000.. - - class Install(object): - Already_installed = pywbem.Uint32(0) - Successful_installation = pywbem.Uint32(1) - Failed = pywbem.Uint32(2) - -## end of class LMI_SoftwarePackage - -def get_providers(env): - """Associates CIM Class Name to python provider class name""" - lmi_softwarepackage_prov = LMI_SoftwarePackage(env) - return {'LMI_SoftwarePackage': lmi_softwarepackage_prov} diff --git a/src/software/openlmi/software/LMI_SoftwarePackageChecks.py b/src/software/openlmi/software/LMI_SoftwarePackageChecks.py index facf90e..76befe0 100644 --- a/src/software/openlmi/software/LMI_SoftwarePackageChecks.py +++ b/src/software/openlmi/software/LMI_SoftwarePackageChecks.py @@ -26,10 +26,11 @@ Instruments the CIM class LMI_SoftwarePackageChecks """ import pywbem -import yum from pywbem.cim_provider2 import CIMProvider2 -from openlmi.software.util import common -from openlmi.software.util.common import (SoftwarePackage, SoftwareFileCheck) + +from openlmi.software.core import (SoftwarePackage, SoftwareFileCheck) +from openlmi.software.util import cmpi_logging +from openlmi.software.yumdb import YumDB class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 """Instrument the CIM class LMI_SoftwarePackageChecks @@ -42,11 +43,11 @@ class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 """ - def __init__ (self, env): - logger = env.get_logger() - logger.log_debug('Initializing provider %s from %s' \ + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ % (self.__class__.__name__, __file__)) + @cmpi_logging.trace_method def get_instance(self, env, model): """Return an instance. @@ -69,25 +70,21 @@ class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.get_instance()' \ - % self.__class__.__name__) - if not "Check" in model: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing Check property.") if not "Element" in model: raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Missing Element property.") - vpkg = SoftwareFileCheck.object_path2yumcheck(env, model['Check']) + + pkg_info, pkg_check, pkg_file = \ + SoftwareFileCheck.object_path2pkg_file(model['Check']) model['Check'] = SoftwareFileCheck.filecheck2model( - vpkg, model['Check']['Name'], - env, keys_only=True) - model['Element'] = SoftwarePackage.pkg2model( - env, vpkg.po, keys_only=True) + pkg_info, pkg_check, pkg_file.path) + model['Element'] = SoftwarePackage.pkg2model(pkg_info) return model + @cmpi_logging.trace_method def enum_instances(self, env, model, keys_only): """Enumerate instances. @@ -110,13 +107,10 @@ class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.enum_instances()' \ - % self.__class__.__name__) raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED, "Enumeration of instances is not supported.") + @cmpi_logging.trace_method def set_instance(self, env, instance, modify_existing): """Return a newly created or modified instance. @@ -143,12 +137,9 @@ class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.set_instance()' \ - % self.__class__.__name__) raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + @cmpi_logging.trace_method def delete_instance(self, env, instance_name): """Delete an instance. @@ -170,12 +161,9 @@ class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.delete_instance()' \ - % self.__class__.__name__) raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + @cmpi_logging.trace_method def references(self, env, object_name, model, result_class_name, role, result_role, keys_only): """Instrument Associations. @@ -234,10 +222,6 @@ class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 CIM_ERR_FAILED (some other unspecified error occurred) """ - - logger = env.get_logger() - logger.log_debug('Entering %s.references()' \ - % self.__class__.__name__) cimhandle = env.get_cimom_handle() # Prime model.path with knowledge of the keys, so key values on @@ -245,7 +229,7 @@ class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 # we set property values on the model. model.path.update({'Check': None, 'Element': None}) - with common.YumDB.getInstance(env): + with YumDB.getInstance() as ydb: if ( (not role or role.lower() == 'element') and cimhandle.is_subclass(object_name.namespace, sub=object_name.classname, @@ -254,15 +238,14 @@ class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 classname='LMI_SoftwareFileCheck', namespace="root/cimv2", host=model.path.host) - model['Element'] = object_name + pkg_info = SoftwarePackage.object_path2pkg(object_name, + kind="installed") + model['Element'] = SoftwarePackage.pkg2model(pkg_info) - pkg = SoftwarePackage.object_path2pkg(env, object_name) - vpkg = yum.packages._RPMVerifyPackage( - pkg, pkg.hdr.fiFromHeader(), - SoftwareFileCheck.pkg_checksum_type(pkg), [], True) - for file_check in vpkg: + pkg_check = ydb.check_package(pkg_info) + for file_name in pkg_check: model['Check'] = SoftwareFileCheck.filecheck2model( - vpkg, file_check.filename, env, keys_only=True, + pkg_info, pkg_check, file_name, model=filecheck_model) yield model @@ -270,21 +253,11 @@ class LMI_SoftwarePackageChecks(CIMProvider2): #pylint: disable=R0904 and cimhandle.is_subclass(object_name.namespace, sub=object_name.classname, super='LMI_SoftwareFileCheck')): - model['Check'] = object_name + pkg_info, pkg_check, pkg_file = \ + SoftwareFileCheck.object_path2pkg_file(object_name) + model['Check'] = SoftwareFileCheck.filecheck2model( + pkg_info, pkg_check, pkg_file.path) - vpkg = SoftwareFileCheck.object_path2yumcheck(env, object_name) - model['Element'] = SoftwarePackage.pkg2model( - env, vpkg.po, keys_only=True) + model['Element'] = SoftwarePackage.pkg2model(pkg_info) yield model - class Values(object): - class Phase(object): - In_State = pywbem.Uint16(0) - Next_State = pywbem.Uint16(1) - -## end of class LMI_SoftwarePackageChecksProvider - -def get_providers(env): - """Associates CIM Class Name to python provider class name""" - lmi_softwarepackagechecks_prov = LMI_SoftwarePackageChecks(env) - return {'LMI_SoftwarePackageChecks': lmi_softwarepackagechecks_prov} diff --git a/src/software/openlmi/software/cimom_entry.py b/src/software/openlmi/software/cimom_entry.py new file mode 100644 index 0000000..59c01ee --- /dev/null +++ b/src/software/openlmi/software/cimom_entry.py @@ -0,0 +1,52 @@ +# -*- 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> +# + +""" +Entry module for OpenLMI Software proviers. +""" + +from openlmi.software.LMI_SoftwarePackage import LMI_SoftwarePackage +from openlmi.software.LMI_SoftwareInstalledPackage import \ + LMI_SoftwareInstalledPackage +from openlmi.software.LMI_SoftwareFileCheck import LMI_SoftwareFileCheck +from openlmi.software.LMI_SoftwarePackageChecks import \ + LMI_SoftwarePackageChecks +from openlmi.software.util import cmpi_logging +from openlmi.software.yumdb import YumDB + +def get_providers(env): + cmpi_logging.LogManager(env) + + providers = { + "LMI_SoftwarePackage" : LMI_SoftwarePackage(env), + "LMI_SoftwareInstalledPackage" : LMI_SoftwareInstalledPackage(env), + "LMI_SoftwareFileCheck" : LMI_SoftwareFileCheck(env), + "LMI_SoftwarePackageChecks" : LMI_SoftwarePackageChecks(env) + } + + return providers + +def can_unload(_env): + return True + +def shutdown(_env): + YumDB.getInstance().clean_up() diff --git a/src/software/openlmi/software/core/ComputerSystem.py b/src/software/openlmi/software/core/ComputerSystem.py new file mode 100644 index 0000000..90699a0 --- /dev/null +++ b/src/software/openlmi/software/core/ComputerSystem.py @@ -0,0 +1,73 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to associated class Linux_ComputerSystem. +""" + +import pywbem +import socket + +def get_path(prefix='Linux'): + """ + @return object path of Linux_ComputerSystem + """ + op = pywbem.CIMInstanceName( + classname='%s_ComputerSystem' % prefix, + namespace="root/cimv2") + op["CreationClassName"] = "Linux_ComputerSystem" + op["Name"] = socket.gethostname() + return op + +def check_path_property(env, op, prop_name): + """ + Checks, whether object path contains correct instance name of + Linux_ComputerSystem corresponding to this system. + If not, an exception is raised. + """ + system = op[prop_name] + if not isinstance(system, pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "\"%s\" must be a CIMInstanceName" % prop_name) + our_system = get_path(prefix='CIM') + ch = env.get_cimom_handle() + if system.namespace != our_system.namespace: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'Namespace of "%s" does not match "%s"' % ( + prop_name, our_system.namespace)) + if not ch.is_subclass(our_system.namespace, + sub=system.classname, + super=our_system.classname): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Class of \"%s\" must be a sublass of %s" % ( + prop_name, our_system.classname)) + if not 'CreationClassName' in system or not 'Name' in system: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "\"%s\" is missing one of keys", prop_name) + if not ch.is_subclass(our_system.namespace, + sub=system['CreationClassName'], + super=our_system.classname): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "CreationClassName of \"%s\" must be a sublass of %s" % ( + prop_name, our_system.classname)) + if system['Name'] != our_system['Name']: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Name of \"%s\" does not match \"%s\"" % + prop_name, our_system['Name']) + return True + diff --git a/src/software/openlmi/software/core/SoftwareFileCheck.py b/src/software/openlmi/software/core/SoftwareFileCheck.py new file mode 100644 index 0000000..fe844c1 --- /dev/null +++ b/src/software/openlmi/software/core/SoftwareFileCheck.py @@ -0,0 +1,515 @@ +# -*- 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> +# + +""" +Just a common functionality related to SoftwareFileCheck provider. +""" + +import collections +import hashlib +import os +import pywbem +import stat +import yum + +from openlmi.software import util +from openlmi.software.util import cmpi_logging +from openlmi.software.yumdb import YumDB +from openlmi.software.yumdb import packageinfo +from openlmi.software.yumdb import packagecheck + +PASSED_FLAGS_DESCRIPTIONS = ( + "Existence", + "File Type", + "File Size", + "File Mode", + "File Checksum", + "Device major/minor number", + "Symlink Target", + "User Ownership", "Group Ownership", + "Modify Time") + +# Named tuple to store results of rpm file check as pywbem values, all results +# are in the 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', #pylint: disable=C0103 + 'exists, md5_checksum, file_type, file_size, file_mode, ' + 'file_checksum, device, link_target, user_id, group_id, ' + 'last_modification_time') + +@cmpi_logging.trace_function +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]) + +@cmpi_logging.trace_function +def checksumtype_str2pywbem(alg): + """ + @param alg is a name of algorithm used for checksum + @return pywbem number corresponding to given alg + """ + try: + res = packagecheck.CHECKSUMTYPE_STR2NUM[alg.lower()] + except KeyError: + res = 0 + return pywbem.Uint16(res) + +@cmpi_logging.trace_function +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' : Values.FileType.File + , 'directory' : Values.FileType.Directory + , 'symlink' : Values.FileType.Symlink + , 'fifo' : Values.FileType.FIFO + , 'character device' : Values.FileType.Character_Device + , 'block device' : Values.FileType.Block_Device + }[file_type]) + except KeyError: + return Values.FileType.Unknown + +@cmpi_logging.trace_function +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) + +@cmpi_logging.trace_function +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 + +@cmpi_logging.trace_function +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 ] + +@cmpi_logging.trace_function +def compute_checksums(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 != packagecheck.CHECKSUMTYPE_STR2NUM["md5"]: + hashers.append(checksumtype_num2hash(checksum_type)()) + if file_type != filetype_str2pywbem('file'): + rslts = ['0'*len(h.hexdigest()) for h in hashers] + else: + try: + with open(file_path, 'rb') as fobj: + rslts = hashfile(fobj, hashers) + except (OSError, IOError) as exc: + cmpi_logging.logger.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) + +@cmpi_logging.trace_function +def object_path2pkg_file(objpath): + """ + @return (package_info, package_check) + """ + 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 util.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 = util.RE_NEVRA_OPT_EPOCH.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.") + + with YumDB.getInstance() as ydb: + pkglist = ydb.filter_packages('installed', **util.nevra2filter(match)) + if len(pkglist) < 1: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No matching package installed.") + pkg = pkglist[0] + pkg_check = ydb.check_package(pkg) + return (pkg, pkg_check, pkg_check[objpath["Name"]]) + +@cmpi_logging.trace_function +def test_file(checksum_type, package_file): + """ + @param checksum type is a pywbem value for ChecksumType property + @return instance of FileCheck + """ + if not isinstance(package_file, packagecheck.PackageFile): + raise TypeError("package_file must be an instance of PackageFile" + " not \"%s\"" % package_file.__class__.__name__) + exists = os.path.lexists(package_file.path) + md5_checksum = None + expected = { + "file_type" : filetype_str2pywbem(package_file.file_type), + "user_id" : pywbem.Uint32(package_file.uid), + "group_id" : pywbem.Uint32(package_file.gid), + "file_mode" : pywbem.Uint32(package_file.mode), + "file_size" : pywbem.Uint64(package_file.size), + "link_target" : package_file.link_target, + "file_checksum" : package_file.checksum, + "device" : pywbem.Uint64(package_file.device) + if package_file.device is not None else None, + "last_modification_time" : pywbem.Uint64(package_file.mtime) + } + if not exists: + reality = collections.defaultdict(lambda: None) + else: + fstat = os.lstat(package_file.path) + reality = { + "file_type" : 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'] == filetype_str2pywbem("device") + else None) + reality["link_target"] = (os.readlink(package_file.path) + if os.path.islink(package_file.path) else None) + md5_checksum, checksum = compute_checksums( + checksum_type, reality["file_type"], package_file.path) + reality["file_checksum"] = checksum + kwargs = dict(exists=exists, md5_checksum=md5_checksum, + **dict((k, (expected[k], reality[k])) for k in expected)) + return FileCheck(**kwargs) + +@cmpi_logging.trace_function +def _filecheck2model_flags(file_check): + """ + @param file_check is an instance of FileCheck + @return pywbem value for PassedFlags property + """ + if not isinstance(file_check, FileCheck): + raise TypeError("file_check must be an instance of FileCheck") + flags = [] + for k, value in file_check._asdict().items(): #pylint: disable=W0212 + if isinstance(value, tuple): + if ( k in ("last_modification_time", "file_size") + and file_check.file_type[0] != 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] == 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 + +@cmpi_logging.trace_function +def filecheck_passed(file_check): + """ + @return True if installed file passed all checks. + """ + return all(_filecheck2model_flags(file_check)) + +@cmpi_logging.trace_function +def _fill_non_key_values(model, pkg_check, pkg_file, file_check=None): + """ + Fills a non key values into instance of SoftwareFileCheck. + """ + model['FileName'] = os.path.basename(pkg_file.path) + model['FileChecksumType'] = csumt = pywbem.Uint16( + pkg_check.file_checksum_type) + if file_check is None: + file_check = test_file(csumt, pkg_file) + 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'] = mode2pywbem_flags(file_check.file_mode[0]) + if file_check.exists: + model['FileModeFlags'] = 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'] = _filecheck2model_flags(file_check) + model['PassedFlagsDescriptions'] = list(PASSED_FLAGS_DESCRIPTIONS) + +@cmpi_logging.trace_function +def filecheck2model(package_info, package_check, file_name, keys_only=True, + model=None, file_check=None): + """ + @param package_file is an instance of yumdb.PackageFile + @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(package_info, packageinfo.PackageInfo): + raise TypeError( + "package_info must be an instance ofyumdb.PackageInfo") + if not isinstance(package_check, packagecheck.PackageCheck): + raise TypeError( + "package_check must be an instance of yumdb.PackageFile") + if not file_name in package_check: + 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) + package_file = package_check[file_name] + model['Name'] = package_file.path + model['SoftwareElementID'] = package_info.nevra + model['SoftwareElementState'] = Values.SoftwareElementState.Executable + model['TargetOperatingSystem'] = pywbem.Uint16( + util.get_target_operating_system()[0]) + model['Version'] = package_info.version + model['CheckID'] = '%s#%s' % (package_info.name, package_file.path) + if not keys_only: + if file_check is not None: + if not isinstance(file_check, FileCheck): + raise TypeError("file_check must be an instance of FileCheck") + _fill_non_key_values(model, package_check, package_file, file_check) + return model + +class Values(object): + """ + Enumerations of LMI_SoftwareFileCheck class properties. + """ + class TargetOperatingSystem(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + MACOS = pywbem.Uint16(2) + ATTUNIX = pywbem.Uint16(3) + DGUX = pywbem.Uint16(4) + DECNT = pywbem.Uint16(5) + Tru64_UNIX = pywbem.Uint16(6) + OpenVMS = pywbem.Uint16(7) + HPUX = pywbem.Uint16(8) + AIX = pywbem.Uint16(9) + MVS = pywbem.Uint16(10) + OS400 = pywbem.Uint16(11) + OS_2 = pywbem.Uint16(12) + JavaVM = pywbem.Uint16(13) + MSDOS = pywbem.Uint16(14) + WIN3x = pywbem.Uint16(15) + WIN95 = pywbem.Uint16(16) + WIN98 = pywbem.Uint16(17) + WINNT = pywbem.Uint16(18) + WINCE = pywbem.Uint16(19) + NCR3000 = pywbem.Uint16(20) + NetWare = pywbem.Uint16(21) + OSF = pywbem.Uint16(22) + DC_OS = pywbem.Uint16(23) + Reliant_UNIX = pywbem.Uint16(24) + SCO_UnixWare = pywbem.Uint16(25) + SCO_OpenServer = pywbem.Uint16(26) + Sequent = pywbem.Uint16(27) + IRIX = pywbem.Uint16(28) + Solaris = pywbem.Uint16(29) + SunOS = pywbem.Uint16(30) + U6000 = pywbem.Uint16(31) + ASERIES = pywbem.Uint16(32) + HP_NonStop_OS = pywbem.Uint16(33) + HP_NonStop_OSS = pywbem.Uint16(34) + BS2000 = pywbem.Uint16(35) + LINUX = pywbem.Uint16(36) + Lynx = pywbem.Uint16(37) + XENIX = pywbem.Uint16(38) + VM = pywbem.Uint16(39) + Interactive_UNIX = pywbem.Uint16(40) + BSDUNIX = pywbem.Uint16(41) + FreeBSD = pywbem.Uint16(42) + NetBSD = pywbem.Uint16(43) + GNU_Hurd = pywbem.Uint16(44) + OS9 = pywbem.Uint16(45) + MACH_Kernel = pywbem.Uint16(46) + Inferno = pywbem.Uint16(47) + QNX = pywbem.Uint16(48) + EPOC = pywbem.Uint16(49) + IxWorks = pywbem.Uint16(50) + VxWorks = pywbem.Uint16(51) + MiNT = pywbem.Uint16(52) + BeOS = pywbem.Uint16(53) + HP_MPE = pywbem.Uint16(54) + NextStep = pywbem.Uint16(55) + PalmPilot = pywbem.Uint16(56) + Rhapsody = pywbem.Uint16(57) + Windows_2000 = pywbem.Uint16(58) + Dedicated = pywbem.Uint16(59) + OS_390 = pywbem.Uint16(60) + VSE = pywbem.Uint16(61) + TPF = pywbem.Uint16(62) + Windows__R__Me = pywbem.Uint16(63) + Caldera_Open_UNIX = pywbem.Uint16(64) + OpenBSD = pywbem.Uint16(65) + Not_Applicable = pywbem.Uint16(66) + Windows_XP = pywbem.Uint16(67) + z_OS = pywbem.Uint16(68) + Microsoft_Windows_Server_2003 = pywbem.Uint16(69) + Microsoft_Windows_Server_2003_64_Bit = pywbem.Uint16(70) + Windows_XP_64_Bit = pywbem.Uint16(71) + Windows_XP_Embedded = pywbem.Uint16(72) + Windows_Vista = pywbem.Uint16(73) + Windows_Vista_64_Bit = pywbem.Uint16(74) + Windows_Embedded_for_Point_of_Service = pywbem.Uint16(75) + Microsoft_Windows_Server_2008 = pywbem.Uint16(76) + Microsoft_Windows_Server_2008_64_Bit = pywbem.Uint16(77) + FreeBSD_64_Bit = pywbem.Uint16(78) + RedHat_Enterprise_Linux = pywbem.Uint16(79) + RedHat_Enterprise_Linux_64_Bit = pywbem.Uint16(80) + Solaris_64_Bit = pywbem.Uint16(81) + SUSE = pywbem.Uint16(82) + SUSE_64_Bit = pywbem.Uint16(83) + SLES = pywbem.Uint16(84) + SLES_64_Bit = pywbem.Uint16(85) + Novell_OES = pywbem.Uint16(86) + Novell_Linux_Desktop = pywbem.Uint16(87) + Sun_Java_Desktop_System = pywbem.Uint16(88) + Mandriva = pywbem.Uint16(89) + Mandriva_64_Bit = pywbem.Uint16(90) + TurboLinux = pywbem.Uint16(91) + TurboLinux_64_Bit = pywbem.Uint16(92) + Ubuntu = pywbem.Uint16(93) + Ubuntu_64_Bit = pywbem.Uint16(94) + Debian = pywbem.Uint16(95) + Debian_64_Bit = pywbem.Uint16(96) + Linux_2_4_x = pywbem.Uint16(97) + Linux_2_4_x_64_Bit = pywbem.Uint16(98) + Linux_2_6_x = pywbem.Uint16(99) + Linux_2_6_x_64_Bit = pywbem.Uint16(100) + Linux_64_Bit = pywbem.Uint16(101) + Other_64_Bit = pywbem.Uint16(102) + Microsoft_Windows_Server_2008_R2 = pywbem.Uint16(103) + VMware_ESXi = pywbem.Uint16(104) + Microsoft_Windows_7 = pywbem.Uint16(105) + CentOS_32_bit = pywbem.Uint16(106) + CentOS_64_bit = pywbem.Uint16(107) + Oracle_Enterprise_Linux_32_bit = pywbem.Uint16(108) + Oracle_Enterprise_Linux_64_bit = pywbem.Uint16(109) + eComStation_32_bitx = pywbem.Uint16(110) + + class SoftwareElementState(object): + Deployable = pywbem.Uint16(0) + Installable = pywbem.Uint16(1) + Executable = pywbem.Uint16(2) + Running = pywbem.Uint16(3) + + class FileType(object): + Unknown = pywbem.Uint16(0) + File = pywbem.Uint16(1) + Directory = pywbem.Uint16(2) + Symlink = pywbem.Uint16(3) + FIFO = pywbem.Uint16(4) + Character_Device = pywbem.Uint16(5) + Block_Device = pywbem.Uint16(6) + diff --git a/src/software/openlmi/software/core/SoftwareInstalledPackage.py b/src/software/openlmi/software/core/SoftwareInstalledPackage.py new file mode 100644 index 0000000..f91fb56 --- /dev/null +++ b/src/software/openlmi/software/core/SoftwareInstalledPackage.py @@ -0,0 +1,39 @@ +# -*- 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> +# + +""" +Just a common functionality related to LMI_SoftwarePackage provider. +""" + +import pywbem + +class Values(object): + class Update(object): + Already_newest = pywbem.Uint16(0) + Successful_installation = pywbem.Uint16(1) + Failed = pywbem.Uint16(2) + + class CheckIntegrity(object): + Pass = pywbem.Uint32(0) + Not_passed = pywbem.Uint32(1) + Error = pywbem.Uint32(2) + diff --git a/src/software/openlmi/software/core/SoftwarePackage.py b/src/software/openlmi/software/core/SoftwarePackage.py new file mode 100644 index 0000000..3e7284f --- /dev/null +++ b/src/software/openlmi/software/core/SoftwarePackage.py @@ -0,0 +1,412 @@ +# -*- 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> +# + +""" +Just a common functionality related to LMI_SoftwarePackage provider. +""" + +import pywbem + +from openlmi.software import util +from openlmi.software.util import cmpi_logging +from openlmi.software.yumdb import PackageInfo, YumDB + +@cmpi_logging.trace_function +def object_path2pkg(objpath, kind='installed'): + """ + @param objpath must contain precise information of package, + otherwise a CIM_ERR_NOT_FOUND error is raised + @param kind 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(kind, basestring): + raise TypeError("kind must be a string") + if not kind in ('installed', 'all', 'available'): + raise ValueError('unsupported package list "%s"' % kind) + + 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 util.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 = util.RE_NEVRA_OPT_EPOCH.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.") + pkglist = YumDB.getInstance().filter_packages(kind, + allow_duplicates=kind != 'installed', + **util.nevra2filter(match)) + if len(pkglist) > 0: + return pkglist[0] + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No matching package found.") + +@cmpi_logging.trace_function +def object_path2pkg_search(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 PackageInfo + """ + 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 + cmpi_logging.logger.error( + 'missing key "%s" in inst.props', 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 + filters = {} + if _get_key('SoftwareElementID'): + match = util.RE_NEVRA_OPT_EPOCH.match(_get_key('SoftwareElementID')) + if not match: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "SoftwareElementID could not be parsed.") + filters = util.nevra2filter(match) + else: + for k in ('name', 'epoch', 'version', 'release', 'arch'): + ikey = k if k != 'arch' else "architecture" + if _get_key(ikey): + filters[k] = _get_key(ikey) + + if not filters: + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, + "Too few key values given (give at least a Name).") + if not 'name' in filters: + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, + "Missing either Name or SoftwareElementID property.") + + pkglist = YumDB.getInstance().filter_packages('all', + allow_duplicates=True, sort=True, **filters) + if len(pkglist) == 0: + cmpi_logging.logger.error( + 'could not find any matching package in list: %s', + [p.nevra for p in pkglist]) + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No matching package found.") + for pkg in pkglist: # check, whether package is already installed + if pkg.installed: + return pkg + cmpi_logging.logger.info( + ( 'found multiple matching packages' + if len(pkglist) > 1 else 'exact match found')) + return pkglist[-1] # select highest version + +@cmpi_logging.trace_function +def pkg2model(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, PackageInfo): + raise TypeError("pkg must be an instance of PackageInfo") + 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__ + _set_key('Name', pkg.name) + _set_key('SoftwareElementID', pkg.nevra) + _set_key('SoftwareElementState', + Values.SoftwareElementState.Executable + if pkg.installed + else Values.SoftwareElementState.Installable) + _set_key('TargetOperatingSystem', + pywbem.Uint16(util.get_target_operating_system()[0])) + _set_key('Version', pkg.version) + if not keys_only: + model['Caption'] = pkg.summary + model['Description'] = pkg.description + if pkg.installed: + model['InstallDate'] = pywbem.CIMDateTime(pkg.install_time) + 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 Values(object): + class DetailedStatus(object): + Not_Available = pywbem.Uint16(0) + No_Additional_Information = pywbem.Uint16(1) + Stressed = pywbem.Uint16(2) + Predictive_Failure = pywbem.Uint16(3) + Non_Recoverable_Error = pywbem.Uint16(4) + Supporting_Entity_in_Error = pywbem.Uint16(5) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + + class Status(object): + OK = 'OK' + Error = 'Error' + Degraded = 'Degraded' + Unknown = 'Unknown' + Pred_Fail = 'Pred Fail' + Starting = 'Starting' + Stopping = 'Stopping' + Service = 'Service' + Stressed = 'Stressed' + NonRecover = 'NonRecover' + No_Contact = 'No Contact' + Lost_Comm = 'Lost Comm' + Stopped = 'Stopped' + + class HealthState(object): + Unknown = pywbem.Uint16(0) + OK = pywbem.Uint16(5) + Degraded_Warning = pywbem.Uint16(10) + Minor_failure = pywbem.Uint16(15) + Major_failure = pywbem.Uint16(20) + Critical_failure = pywbem.Uint16(25) + Non_recoverable_error = pywbem.Uint16(30) + # DMTF_Reserved = .. + # Vendor_Specific = 32768..65535 + + class TargetOperatingSystem(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + MACOS = pywbem.Uint16(2) + ATTUNIX = pywbem.Uint16(3) + DGUX = pywbem.Uint16(4) + DECNT = pywbem.Uint16(5) + Tru64_UNIX = pywbem.Uint16(6) + OpenVMS = pywbem.Uint16(7) + HPUX = pywbem.Uint16(8) + AIX = pywbem.Uint16(9) + MVS = pywbem.Uint16(10) + OS400 = pywbem.Uint16(11) + OS_2 = pywbem.Uint16(12) + JavaVM = pywbem.Uint16(13) + MSDOS = pywbem.Uint16(14) + WIN3x = pywbem.Uint16(15) + WIN95 = pywbem.Uint16(16) + WIN98 = pywbem.Uint16(17) + WINNT = pywbem.Uint16(18) + WINCE = pywbem.Uint16(19) + NCR3000 = pywbem.Uint16(20) + NetWare = pywbem.Uint16(21) + OSF = pywbem.Uint16(22) + DC_OS = pywbem.Uint16(23) + Reliant_UNIX = pywbem.Uint16(24) + SCO_UnixWare = pywbem.Uint16(25) + SCO_OpenServer = pywbem.Uint16(26) + Sequent = pywbem.Uint16(27) + IRIX = pywbem.Uint16(28) + Solaris = pywbem.Uint16(29) + SunOS = pywbem.Uint16(30) + U6000 = pywbem.Uint16(31) + ASERIES = pywbem.Uint16(32) + HP_NonStop_OS = pywbem.Uint16(33) + HP_NonStop_OSS = pywbem.Uint16(34) + BS2000 = pywbem.Uint16(35) + LINUX = pywbem.Uint16(36) + Lynx = pywbem.Uint16(37) + XENIX = pywbem.Uint16(38) + VM = pywbem.Uint16(39) + Interactive_UNIX = pywbem.Uint16(40) + BSDUNIX = pywbem.Uint16(41) + FreeBSD = pywbem.Uint16(42) + NetBSD = pywbem.Uint16(43) + GNU_Hurd = pywbem.Uint16(44) + OS9 = pywbem.Uint16(45) + MACH_Kernel = pywbem.Uint16(46) + Inferno = pywbem.Uint16(47) + QNX = pywbem.Uint16(48) + EPOC = pywbem.Uint16(49) + IxWorks = pywbem.Uint16(50) + VxWorks = pywbem.Uint16(51) + MiNT = pywbem.Uint16(52) + BeOS = pywbem.Uint16(53) + HP_MPE = pywbem.Uint16(54) + NextStep = pywbem.Uint16(55) + PalmPilot = pywbem.Uint16(56) + Rhapsody = pywbem.Uint16(57) + Windows_2000 = pywbem.Uint16(58) + Dedicated = pywbem.Uint16(59) + OS_390 = pywbem.Uint16(60) + VSE = pywbem.Uint16(61) + TPF = pywbem.Uint16(62) + Windows__R__Me = pywbem.Uint16(63) + Caldera_Open_UNIX = pywbem.Uint16(64) + OpenBSD = pywbem.Uint16(65) + Not_Applicable = pywbem.Uint16(66) + Windows_XP = pywbem.Uint16(67) + z_OS = pywbem.Uint16(68) + Microsoft_Windows_Server_2003 = pywbem.Uint16(69) + Microsoft_Windows_Server_2003_64_Bit = pywbem.Uint16(70) + Windows_XP_64_Bit = pywbem.Uint16(71) + Windows_XP_Embedded = pywbem.Uint16(72) + Windows_Vista = pywbem.Uint16(73) + Windows_Vista_64_Bit = pywbem.Uint16(74) + Windows_Embedded_for_Point_of_Service = pywbem.Uint16(75) + Microsoft_Windows_Server_2008 = pywbem.Uint16(76) + Microsoft_Windows_Server_2008_64_Bit = pywbem.Uint16(77) + FreeBSD_64_Bit = pywbem.Uint16(78) + RedHat_Enterprise_Linux = pywbem.Uint16(79) + RedHat_Enterprise_Linux_64_Bit = pywbem.Uint16(80) + Solaris_64_Bit = pywbem.Uint16(81) + SUSE = pywbem.Uint16(82) + SUSE_64_Bit = pywbem.Uint16(83) + SLES = pywbem.Uint16(84) + SLES_64_Bit = pywbem.Uint16(85) + Novell_OES = pywbem.Uint16(86) + Novell_Linux_Desktop = pywbem.Uint16(87) + Sun_Java_Desktop_System = pywbem.Uint16(88) + Mandriva = pywbem.Uint16(89) + Mandriva_64_Bit = pywbem.Uint16(90) + TurboLinux = pywbem.Uint16(91) + TurboLinux_64_Bit = pywbem.Uint16(92) + Ubuntu = pywbem.Uint16(93) + Ubuntu_64_Bit = pywbem.Uint16(94) + Debian = pywbem.Uint16(95) + Debian_64_Bit = pywbem.Uint16(96) + Linux_2_4_x = pywbem.Uint16(97) + Linux_2_4_x_64_Bit = pywbem.Uint16(98) + Linux_2_6_x = pywbem.Uint16(99) + Linux_2_6_x_64_Bit = pywbem.Uint16(100) + Linux_64_Bit = pywbem.Uint16(101) + Other_64_Bit = pywbem.Uint16(102) + Microsoft_Windows_Server_2008_R2 = pywbem.Uint16(103) + VMware_ESXi = pywbem.Uint16(104) + Microsoft_Windows_7 = pywbem.Uint16(105) + CentOS_32_bit = pywbem.Uint16(106) + CentOS_64_bit = pywbem.Uint16(107) + Oracle_Enterprise_Linux_32_bit = pywbem.Uint16(108) + Oracle_Enterprise_Linux_64_bit = pywbem.Uint16(109) + eComStation_32_bitx = pywbem.Uint16(110) + + class Remove(object): + Not_installed = pywbem.Uint32(0) + Successful_removal = pywbem.Uint32(1) + Failed = pywbem.Uint32(2) + + class CommunicationStatus(object): + Unknown = pywbem.Uint16(0) + Not_Available = pywbem.Uint16(1) + Communication_OK = pywbem.Uint16(2) + Lost_Communication = pywbem.Uint16(3) + No_Contact = pywbem.Uint16(4) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + + class OperationalStatus(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + OK = pywbem.Uint16(2) + Degraded = pywbem.Uint16(3) + Stressed = pywbem.Uint16(4) + Predictive_Failure = pywbem.Uint16(5) + Error = pywbem.Uint16(6) + Non_Recoverable_Error = pywbem.Uint16(7) + Starting = pywbem.Uint16(8) + Stopping = pywbem.Uint16(9) + Stopped = pywbem.Uint16(10) + In_Service = pywbem.Uint16(11) + No_Contact = pywbem.Uint16(12) + Lost_Communication = pywbem.Uint16(13) + Aborted = pywbem.Uint16(14) + Dormant = pywbem.Uint16(15) + Supporting_Entity_in_Error = pywbem.Uint16(16) + Completed = pywbem.Uint16(17) + Power_Mode = pywbem.Uint16(18) + Relocating = pywbem.Uint16(19) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + + class OperatingStatus(object): + Unknown = pywbem.Uint16(0) + Not_Available = pywbem.Uint16(1) + Servicing = pywbem.Uint16(2) + Starting = pywbem.Uint16(3) + Stopping = pywbem.Uint16(4) + Stopped = pywbem.Uint16(5) + Aborted = pywbem.Uint16(6) + Dormant = pywbem.Uint16(7) + Completed = pywbem.Uint16(8) + Migrating = pywbem.Uint16(9) + Emigrating = pywbem.Uint16(10) + Immigrating = pywbem.Uint16(11) + Snapshotting = pywbem.Uint16(12) + Shutting_Down = pywbem.Uint16(13) + In_Test = pywbem.Uint16(14) + Transitioning = pywbem.Uint16(15) + In_Service = pywbem.Uint16(16) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + + class SoftwareElementState(object): + Deployable = pywbem.Uint16(0) + Installable = pywbem.Uint16(1) + Executable = pywbem.Uint16(2) + Running = pywbem.Uint16(3) + + class PrimaryStatus(object): + Unknown = pywbem.Uint16(0) + OK = pywbem.Uint16(1) + Degraded = pywbem.Uint16(2) + Error = pywbem.Uint16(3) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + + class Install(object): + Already_installed = pywbem.Uint32(0) + Successful_installation = pywbem.Uint32(1) + Failed = pywbem.Uint32(2) + diff --git a/src/software/openlmi/software/core/SoftwarePackageChecks.py b/src/software/openlmi/software/core/SoftwarePackageChecks.py new file mode 100644 index 0000000..6d39294 --- /dev/null +++ b/src/software/openlmi/software/core/SoftwarePackageChecks.py @@ -0,0 +1,32 @@ +# -*- 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> +# + +""" +Just a common functionality related to LMI_SoftwarePackageChecks provider. +""" + +import pywbem + +class Values(object): + class Phase(object): + In_State = pywbem.Uint16(0) + Next_State = pywbem.Uint16(1) diff --git a/src/software/openlmi/software/core/__init__.py b/src/software/openlmi/software/core/__init__.py new file mode 100644 index 0000000..2ebe827 --- /dev/null +++ b/src/software/openlmi/software/core/__init__.py @@ -0,0 +1,20 @@ +# 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> +# 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 - diff --git a/src/software/openlmi/software/yumdb/__init__.py b/src/software/openlmi/software/yumdb/__init__.py new file mode 100644 index 0000000..54eeae0 --- /dev/null +++ b/src/software/openlmi/software/yumdb/__init__.py @@ -0,0 +1,307 @@ +# 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> +# +""" +Since yum API functions should not be called with different thread_ids +repeatedly in the same program. It's neccessary, to make these calls +in single thread. But the provider needs to be able to clean up itself, +when its not needed. That's why the yum API needs to be accessed from +separated process, that is created and terminated when needed. + +This package contains all the bowels of this separate process together +with its management and communication facilities. + +YumDB is a context manager supposed to be used by any provider as the +only accessor to yum api. +""" + +import errno +import os +import re +import time +from multiprocessing import Process, JoinableQueue, Queue +import Queue as TQueue # T as threaded +import threading +import yum + +from openlmi.software.yumdb import jobs +from openlmi.software.yumdb import errors +from openlmi.software.yumdb.packageinfo import PackageInfo +from openlmi.software.yumdb.packagecheck import PackageFile +from openlmi.software.yumdb.packagecheck import PackageCheck +from openlmi.software.yumdb.process import YumWorker +from openlmi.software.util import cmpi_logging +from openlmi.software.util import singletonmixin + +# this may be used as an argument to YumWorker +YUM_WORKER_DEBUG_LOGGING_CONFIG = { + "version" : 1, + "formatters": { + "default": { + "format" : "%(asctime)s %(levelname)s:%(module)s:" + "%(funcName)s:%(lineno)d - %(message)s" + } + }, + "handlers": { + "file" : { + "class" : "logging.handlers.RotatingFileHandler", + "filename" : "/var/tmp/YumWorker.log", + "level" : "DEBUG", + "formatter": "default", + } + }, + "root": { + "level": "DEBUG", + "handlers" : ["file"] + } + } + +class YumDB(singletonmixin.Singleton): + """ + Context manager for accessing yum/rpm database. + All requests are bundled into jobs -- instances of jobs.YumJob and + sent to YumWorker for processing. + + YumWorker is a separate process handling all calls to yum api. + Communication is done via queues (uplink and downlink). + Uplink is used to send jobs to YumWorker and downlink for obtaining + results. + + This is implemented in thread safe manner. + + It should be used as a context manager in case, we want to process + multiple jobs in single transaction. The example of usage: + with YumDB.getInstance() as ydb: + pkgs = ydb.filter_packages(...) + for pkg in pkgs: + ydb.install_package(pkg) + ... + Yum database stays locked in whole block of code under with statement. + """ + + # this is to inform Singleton, that __init__ should be called only once + ignoreSubsequent = True + + def __init__(self, *args, **kwargs): #pylint: disable=W0231 + """ + All arguments are passed to yum.YumBase constructor. + """ + self._process = None + self._yum_args = (args, kwargs) + + # used to access _replies dictionary and _expected list + self._reply_lock = threading.Lock() + # used to wait for job to be processed and received + self._reply_cond = threading.Condition(self._reply_lock) + # { job_id : reply, ... } + self._replies = {} + # ids of all expected jobs -- those to be processed by YumWorker + self._expected = [] + cmpi_logging.logger.trace_info('YumDB: initialized') + + # ************************************************************************* + # Private methods + # ************************************************************************* + @cmpi_logging.trace_method + def _wait_for_reply(self, job): + """ + Blocks until job is processed by YumWorker and received. + + Only one thread can block on downlink channel to obtain reply. If + it's reply for him, he takes it and leaves, otherwise he adds it to + _replies dictionary and notifies other threads. This thread is the + one, whose job appears as first in _expected list. + + @return result of job + """ + with self._reply_lock: + # until our job is not at the head of + self._expected.append(job.jobid) + while job.jobid != self._expected[0]: + if job.jobid in self._replies: + self._expected.remove(job.jobid) + return self._replies.pop(job.jobid) + else: + self._reply_cond.wait() + while True: + jobid, reply = self._worker.downlink.get() + with self._reply_lock: + if jobid != job.jobid: + self._replies[jobid] = reply + self._reply_cond.notifyAll() + else: + self._expected.remove(job.jobid) + if len(self._expected): + self._reply_cond.notify() + break + return reply + + def _do_job(self, job): + """ + Sends the job to YumWorker process and waits for reply. + If reply is a tuple, there was an error, while job processing. + Incoming exception is in format: + (exception_type, exception_value, formated_traceback_as_string) + @return reply + """ + cmpi_logging.logger.trace_verbose("YumDB: doing %s(id=%s) job", + job.__class__.__name__, job.jobid) + self._worker.uplink.put(job) + reply = self._wait_for_reply(job) + if isinstance(reply, tuple): + cmpi_logging.logger.error( + "YumDB: job %s(id=%s) failed with error %s: %s", + job.__class__.__name__, job.jobid, + reply[0].__name__, str(reply[1])) + cmpi_logging.logger.trace_warn( + "YumDB: job %s(id=%s) exception traceback:\n%s%s: %s", + job.__class__.__name__, job.jobid, "".join(reply[2]), + reply[0].__name__, str(reply[1])) + reply[1].tb_printed = True + raise reply[1] + cmpi_logging.logger.trace_verbose("YumDB: job %s(id=%s) done", + job.__class__.__name__, job.jobid) + return reply + + @property + def _worker(self): + """ + YumWorker process accessor. It's created upon first need. + """ + if self._process is None: + cmpi_logging.logger.trace_info("YumDB: starting YumWorker") + uplink = JoinableQueue() + downlink = Queue() + self._process = YumWorker(uplink, downlink, + yum_args=self._yum_args[0], yum_kwargs=self._yum_args[1]) + #logging_config=YUM_WORKER_DEBUG_LOGGING_CONFIG) + self._process.start() + return self._process + + # ************************************************************************* + # Special methods + # ************************************************************************* + def __del__(self): + """ + Ensure, that YumWorker process is correctly shutted down. + """ + self.clean_up() + YumDB.__del__(self) + + @cmpi_logging.trace_method + def __enter__(self): + self._do_job(jobs.YumBeginSession()) + cmpi_logging.logger.trace_info('YumDB: new session started') + return self + + @cmpi_logging.trace_method + def __exit__(self, exc_type, exc_value, traceback): + self._do_job(jobs.YumEndSession()) + cmpi_logging.logger.trace_info('YumDB: session ended') + + # ************************************************************************* + # Public methods + # ************************************************************************* + @cmpi_logging.trace_method + def clean_up(self): + """ + Shut down the YumWorker process. + """ + cmpi_logging.logger.info('YumDB: cleanup called') + if self._process: + cmpi_logging.logger.info('YumDB: terminating YumWorker') + self._process.uplink.put(None) # terminating command + self._process.uplink.join() + self._process.join() + cmpi_logging.logger.info('YumDB: YumWorker terminated') + self._process = None + + @cmpi_logging.trace_method + def get_package_list(self, kind, + allow_duplicates=False, + sort=False): + """ + @param kind is one of: {"installed", "available", "all"} + @param allow_duplicates says, whether to list all found versions + of single package + @return [pkg1, pkg2, ...], pkgi is instance of yumdb.PackageInfo + """ + return self._do_job(jobs.YumGetPackageList( + kind, allow_duplicates=allow_duplicates, sort=sort)) + + @cmpi_logging.trace_method + def filter_packages(self, kind, + allow_duplicates=False, + sort=False, + **filters): + """ + Similar to get_package_list(), but applies filter on packages. + @see yumdb.jobs.YumFilterPackages job for supported filter keys + """ + return self._do_job(jobs.YumFilterPackages( + kind, allow_duplicates=allow_duplicates, sort=sort, + **filters)) + + @cmpi_logging.trace_method + def install_package(self, pkg): + """ + Install package. + @param pkg is an instance of PackageInfo obtained with + get_package_list() or filter_packages(), which must be not installed + """ + return self._do_job(jobs.YumInstallPackage(pkg)) + + @cmpi_logging.trace_method + def remove_package(self, pkg): + """ + @param pkg is an instance of PackageInfo obtained with + get_package_list() or filter_packages(), which must be installed + """ + return self._do_job(jobs.YumRemovePackage(pkg)) + + @cmpi_logging.trace_method + def update_to_package(self, desired_pkg): + """ + @param desired_pkg is an instance of PackageInfo, + which must be available + """ + return self._do_job(jobs.YumUpdateToPackage(desired_pkg)) + + @cmpi_logging.trace_method + def update_package(self, pkg, + to_epoch=None, + to_version=None, + to_release=None): + """ + @param pkg is an instance of PackageInfo, which must be installed + + The other parameters filter candidate available packages for update. + """ + return self._do_job(jobs.YumUpdatePackage( + pkg, to_epoch, to_version, to_release)) + + @cmpi_logging.trace_method + def check_package(self, pkg): + """ + @param pkg is an instance of PackageInfo representing installed package + @return instance of yumdb.PackageCheck + """ + return self._do_job(jobs.YumCheckPackage(pkg)) + diff --git a/src/software/openlmi/software/yumdb/errors.py b/src/software/openlmi/software/yumdb/errors.py new file mode 100644 index 0000000..646048e --- /dev/null +++ b/src/software/openlmi/software/yumdb/errors.py @@ -0,0 +1,43 @@ +# -*- 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> +# +""" +Exceptions raisable by YumWorker. +""" + +class DatabaseLockError(Exception): + """Raised, when the yum database can not be locked.""" + pass +class TransactionError(Exception): + """Base exception representing yum transaction processing error.""" + pass +class TransactionBuildFailed(TransactionError): + """Raised, when transaction building fails.""" + pass +class TransactionExecutionFailed(TransactionError): + """Raised, when YumBase.doTransaction() method fails.""" + pass +class UnknownJob(Exception): + """Raised, when no handler is available for given job on worker.""" + pass +class PackageNotFound(Exception): + """Raised, when requested package could not be found.""" + pass diff --git a/src/software/openlmi/software/yumdb/jobs.py b/src/software/openlmi/software/yumdb/jobs.py new file mode 100644 index 0000000..1fdfbe3 --- /dev/null +++ b/src/software/openlmi/software/yumdb/jobs.py @@ -0,0 +1,234 @@ +# -*- 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> +# +""" +Define job classes representing kinds of jobs of worker process. +""" + +import threading + +from openlmi.software import util +from openlmi.software.yumdb.packageinfo import PackageInfo + +class YumJob(object): #pylint: disable=R0903 + """ + Base class for any job, that is processable by YumWorker process. + It contains at least a jobid attribute, that must be unique for + each job, it's counted from zero a incremented after each creation. + """ + __slots__ = ('jobid', ) + + # jobs can be created concurrently from multiple threads, that's + # why we need to make its creation thread safe + _JOB_ID_LOCK = threading.Lock() + _JOB_ID = 0 + + @staticmethod + def _get_job_id(): + """ + Generates new job ids. It should be called only from constructor + of YumJob. Ensures, that each job has a unique number. + @return number of jobs created since program start -1 + """ + with YumJob._JOB_ID_LOCK: + val = YumJob._JOB_ID + YumJob._JOB_ID += 1 + return val + + def __init__(self): + self.jobid = self._get_job_id() + + @property + def job_kwargs(self): + """ + Jobs are in worker represented be methods with arguments. + Those can be obtained from job by calling this property. + @return dictionary of keyword arguments of job + """ + kwargs = {} + cls = self.__class__ + while not cls in (YumJob, object): + for slot in cls.__slots__: + if not slot in kwargs: + kwargs[slot] = getattr(self, slot) + cls = cls.__bases__[0] + kwargs.pop('jobid', None) + return kwargs + + def __getstate__(self): + ret = self.job_kwargs + ret.update(jobid=self.jobid) + return ret + + def __setstate__(self, state): + for k, value in state.items(): + setattr(self, k, value) + +class YumBeginSession(YumJob): #pylint: disable=R0903 + """ + Begin session on YumWorker which ensures that yum database is locked + during its lifetime. Sessions can be nested, but the number of + YumEndSession jobs must be processed to make the database unlocked. + """ + pass +class YumEndSession(YumJob): #pylint: disable=R0903 + """ + End the session started with YumBeginSession. If the last active session + is ended, database will be unlocked. + """ + pass + +class YumGetPackageList(YumJob): #pylint: disable=R0903 + """ + Job requesing a list of packages. + Arguments: + kind - supported values are in SUPPORTED_KINDS tuple + allow_duplicates - whether multiple packages can be present + in result for single (name, arch) of package differing + in their version + + Worker replies with [pkg1, pkg2, ...]. + """ + __slots__ = ('kind', 'allow_duplicates', 'sort') + + SUPPORTED_KINDS = ('installed', 'available', 'all') + + def __init__(self, kind, allow_duplicates, sort=False): + YumJob.__init__(self) + if not isinstance(kind, basestring): + raise TypeError("kind must be a string") + if not kind in self.SUPPORTED_KINDS: + raise ValueError("kind must be one of {%s}" % + ", ".join(self.SUPPORTED_KINDS)) + self.kind = kind + self.allow_duplicates = bool(allow_duplicates) + self.sort = bool(sort) + +class YumFilterPackages(YumGetPackageList): #pylint: disable=R0903 + """ + Job similar to YumGetPackageList, but allowing to specify + filter on packages. + Arguments (plus those in YumGetPackageList): + name, epoch, version, release, arch, nevra, envra, evra + + Some of those are redundant, but filtering is optimized for + speed, so supplying all of them won't affect performance. + + Worker replies with [pkg1, pkg2, ...]. + """ + __slots__ = ( + 'name', 'epoch', 'version', 'release', 'arch', + 'nevra', 'envra', 'evra') + + def __init__(self, kind, allow_duplicates, + sort=False, + name=None, epoch=None, version=None, + release=None, arch=None, + nevra=None, evra=None, + envra=None): + if nevra is not None and not util.RE_NEVRA.match(nevra): + raise ValueError("Invalid nevra: %s" % nevra) + if evra is not None and not util.RE_EVRA.match(evra): + raise ValueError("Invalid evra: %s" % evra) + if envra is not None and not util.RE_ENVRA.match(evra): + raise ValueError("Invalid envra: %s" % envra) + YumGetPackageList.__init__(self, kind, allow_duplicates, sort) + self.name = name + self.epoch = None if epoch is None else str(epoch) + self.version = version + self.release = release + self.arch = arch + self.nevra = nevra + self.evra = evra + self.envra = envra + +class YumSpecificPackageJob(YumJob): #pylint: disable=R0903 + """ + Abstract job taking instance of yumdb.PackageInfo as argument. + Arguments: + pkg - plays different role depending on job subclass + """ + __slots__ = ('pkg', ) + def __init__(self, pkg): + if not isinstance(pkg, PackageInfo): + raise TypeError("pkg must be instance of PackageInfo") + YumJob.__init__(self) + self.pkg = pkg + +class YumInstallPackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Job requesting installation of specific package. + pkg argument should be available. + + Worker replies with new instance of package. + """ + pass + +class YumRemovePackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Job requesting removal of specific package. + pkg argument should be installed. + """ + pass + +class YumUpdateToPackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Job requesting update to provided specific package. + Package is updated to epoch, version and release of this + provided available package. + + Worker replies with new instance of package. + """ + pass + +class YumUpdatePackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Job requesting update of package, optionally reducing possible + candidate packages to ones with specific evr. + Arguments: + to_epoch, to_version, to_release + + The arguments more given, the more complete filter of candidates. + + Worker replies with new instance of package. + """ + __slots__ = ('to_epoch', 'to_version', 'to_release') + + def __init__(self, pkg, + to_epoch=None, to_version=None, to_release=None): + if not isinstance(pkg, PackageInfo): + raise TypeError("pkg must be instance of yumdb.PackageInfo") + YumSpecificPackageJob.__init__(self, pkg) + self.to_epoch = to_epoch + self.to_version = to_version + self.to_release = to_release + +class YumCheckPackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Request verification information for instaled package and its files. + + Worker replies with new instance of yumdb.PackageCheck. + """ + def __init__(self, pkg): + YumSpecificPackageJob.__init__(self, pkg) + if not pkg.installed: + raise ValueError("package must be installed to check it") + diff --git a/src/software/openlmi/software/yumdb/packagecheck.py b/src/software/openlmi/software/yumdb/packagecheck.py new file mode 100644 index 0000000..fbb5b21 --- /dev/null +++ b/src/software/openlmi/software/yumdb/packagecheck.py @@ -0,0 +1,188 @@ +# -*- 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> +# +""" +Module with definition of RPM package check class. +""" + +from collections import OrderedDict +from datetime import datetime +import grp +import os +import pwd +import rpm +import yum + +CHECKSUMTYPE_STR2NUM = dict((val.lower(), k) for (k, val) in + yum.constants.RPM_CHECKSUM_TYPES.items()) + +class PackageFile(object): + """ + Metadata related to particular file on filesystem belonging to RPM package. + Data contained here are from RPM database. + """ + __slots__ = ("path", "file_type", "uid", "gid", "mode", "device", "mtime", + "size", "link_target", "checksum") + + def __init__(self, path, file_type, uid, gid, mode, device, mtime, size, + link_target, checksum): + for arg in ('uid', 'gid', 'mode', 'mtime', 'size'): + if not isinstance(locals()[arg], (int, long)): + raise TypeError("%s must be integer" % arg) + if not os.path.isabs(path): + raise ValueError("path must be an absolute path") + self.path = path + self.file_type = file_type.lower() + self.uid = uid + self.gid = gid + self.mode = mode + self.device = device if file_type.endswith('device') else None + self.mtime = mtime + self.size = size + self.link_target = (link_target + if file_type == "symlink" and link_target else None) + self.checksum = checksum + + @property + def last_modification_datetime(self): + """ + @return instance datetime for last modification time of file + """ + return datetime.fromtimestamp(self.mtime) + + def __getstate__(self): + """ + Used for serialization with pickle. + @return container content that will be serialized + """ + return dict((k, getattr(self, k)) for k in self.__slots__) + + def __setstate__(self, state): + """ + Used for deserialization with pickle. + Restores the object from serialized form. + @param state is an object created by __setstate__() method + """ + for k, value in state.items(): + setattr(self, k, value) + +class PackageCheck(object): + """ + Metadata for package concerning verification. + It contains metadata for each file installed in "files" attribute. + """ + __slots__ = ("pkgid", "file_checksum_type", "files") + + def __init__(self, pkgid, file_checksum_type, files=None): + """ + @param pkgid is an in of original yum package object, which is used + by server for subsequent operations on this package requested by client + """ + if files is not None and not isinstance( + files, (list, tuple, set, dict)): + raise TypeError("files must be an iterable container") + self.pkgid = pkgid + self.file_checksum_type = file_checksum_type + if not isinstance(files, dict): + self.files = OrderedDict() + if files is not None: + for file_check in sorted(files, key=lambda f: f.path): + self.files[file_check.path] = file_check + else: + for path in sorted(files): + self.files[path] = files[path] + + def __iter__(self): + return iter(self.files) + + def __len__(self): + return len(self.files) + + def __getitem__(self, filepath): + return self.files[filepath] + + def __setitem__(self, filepath, package_file): + if not isinstance(package_file, PackageFile): + raise TypeError("package_file must be a PackageFile instance") + self.files[filepath] = package_file + + def __contains__(self, fileobj): + if isinstance(fileobj, basestring): + return fileobj in self.files + elif isinstance(fileobj, PackageFile): + return fileobj.path in self.files + else: + raise TypeError("expected file path for argument") + + def __getstate__(self): + """ + Used for serialization with pickle. + @return container content that will be serialized + """ + return dict((k, getattr(self, k)) for k in self.__slots__) + + def __setstate__(self, state): + """ + Used for deserialization with pickle. + Restores the object from serialized form. + @param state is an object created by __setstate__() method + """ + for k, value in state.items(): + setattr(self, k, value) + +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] + return CHECKSUMTYPE_STR2NUM[pkg.yumdb_info.checksum_type.lower()] + +def make_package_check_from_db(vpkg): + """ + Create instance of PackageCheck from instance of + yum.packages._RPMVerifyPackage + @return instance of PackageCheck + """ + if not isinstance(vpkg, yum.packages._RPMVerifyPackage): + raise TypeError("vpkg must be instance of" + " yum.packages._RPMVerifyPackage") + pkg = vpkg.po + + res = PackageCheck(id(pkg), pkg_checksum_type(pkg)) + files = res.files + for vpf in vpkg: + files[vpf.filename] = PackageFile( + vpf.filename, + vpf.ftype, + pwd.getpwnam(vpf.user).pw_uid, + grp.getgrnam(vpf.group).gr_gid, + vpf.mode, + vpf.dev, + vpf.mtime, + vpf.size, + vpf.readlink, + vpf.digest[1] + ) + return res + diff --git a/src/software/openlmi/software/yumdb/packageinfo.py b/src/software/openlmi/software/yumdb/packageinfo.py new file mode 100644 index 0000000..b2cd2b8 --- /dev/null +++ b/src/software/openlmi/software/yumdb/packageinfo.py @@ -0,0 +1,171 @@ +# -*- 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> +# +""" +Module holding an abstraction for RPM package. +""" + +from datetime import datetime +import yum + +from openlmi.software import util + +class PackageInfo(object): + """ + Container for package metadata. It represents rpm package in yum + database. It's supposed to be passed from YumWorker to YumDB client + and vice-versa. Instances of YumAvailablePackage can not be exchanged + -- results in segfaults. + + To speed up looking up of original yum package object on server, an + atribute "pkgid" is provided. + """ + __slots__ = ( + "pkgid", + "name", "epoch", "version", "release", "architecture", + 'summary', 'description', 'license', 'group', 'vendor', + 'size', + 'installed', # boolean + 'install_time' # datetime instance + ) + + def __init__(self, pkgid, name, epoch, version, release, arch, **kwargs): + """ + @param pkgid is an in of original yum package object, which is used + by server for subsequent operations on this package requested by client + """ + self.pkgid = pkgid + self.name = name + self.epoch = epoch + self.version = version + self.release = release + self.architecture = arch + self.summary = kwargs.pop('summary', None) + self.description = kwargs.pop('description', None) + self.license = kwargs.pop('license', None) + self.group = kwargs.pop('group', None) + self.vendor = kwargs.pop('vendor', None) + self.size = kwargs.pop('size', None) + if self.size is not None and not isinstance(self.size, (int, long)): + raise TypeError('size must be an integer') + self.installed = kwargs.pop('installed', None) + if self.installed is not None: + self.installed = bool(self.installed) + self.install_time = kwargs.pop('install_time', None) + if ( self.install_time is not None + and not isinstance(self.install_time, datetime)): + raise TypeError('install_time must be a datetime') + + # ************************************************************************* + # Properties + # ************************************************************************* + @property + def ver(self): + """Shortcut for version property.""" + return self.version + + @property + def rel(self): + """Shortcut for release property.""" + return self.release + + @property + def arch(self): + """Shortcut for architecture property.""" + return self.architecture + + @property + def nevra(self): + """@return nevra of package with epoch always present.""" + return self.get_nevra(with_epoch="ALWAYS") + + @property + def evra(self): + """@return evra of package.""" + return "%s:%s-%s.%s" % ( + self.epoch if self.epoch and self.epoch != "(none)" else "0", + self.version, + self.release, + self.architecture) + + @property + def key_props(self): + """ + @return package properties as dictionary, + that uniquelly identify package in database + """ + return dict((k, getattr(self, k)) for k in ( + 'name', 'epoch', 'version', 'release', 'arch')) + + # ************************************************************************* + # Public methods + # ************************************************************************* + def get_nevra(self, 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 + @return nevra of package + """ + return util.make_nevra(self.name, self.epoch, self.version, + self.release, self.arch, with_epoch) + + # ************************************************************************* + # Special methods + # ************************************************************************* + def __str__(self): + return self.nevra + + def __getstate__(self): + """ + Used for serialization with pickle. + @return container content that will be serialized + """ + return dict((k, getattr(self, k)) for k in self.__slots__) + + def __setstate__(self, state): + """ + Used for deserialization with pickle. + Restores the object from serialized form. + @param state is an object created by __setstate__() method + """ + for k, value in state.items(): + setattr(self, k, value) + +def make_package_from_db(pkg): + """ + Create instance of PackageInfo from instance of + yum.packages.YumAvailablePackage. + @return instance of PackageInfo + """ + metadata = dict((k, getattr(pkg, k)) for k in ( + 'summary', 'description', 'license', 'group', 'vendor', + 'size')) + if isinstance(pkg, yum.rpmsack.RPMInstalledPackage): + metadata['installed'] = True + metadata['install_time'] = datetime.fromtimestamp(pkg.installtime) + else: + metadata['installed'] = False + res = PackageInfo(id(pkg), pkg.name, pkg.epoch, pkg.version, pkg.release, + pkg.arch, **metadata) + return res + diff --git a/src/software/openlmi/software/yumdb/process.py b/src/software/openlmi/software/yumdb/process.py new file mode 100644 index 0000000..c247de7 --- /dev/null +++ b/src/software/openlmi/software/yumdb/process.py @@ -0,0 +1,605 @@ +# -*- 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> +# +""" +Module holding the code of separate process accessing the YUM API. +""" + +import errno +import inspect +from itertools import chain +import logging +from multiprocessing import Process +import Queue as TQueue # T as threaded +import sys +import time +import traceback +import weakref +import yum + +from openlmi.software import util +from openlmi.software.yumdb import errors +from openlmi.software.yumdb import jobs +from openlmi.software.yumdb import packageinfo +from openlmi.software.yumdb import packagecheck + +# ***************************************************************************** +# Constants +# ***************************************************************************** +# interval in seconds +FREE_DATABASE_TIMEOUT = 60 +LOCK_WAIT_INTERVAL = 0.5 +RPMDB_PATH = '/var/lib/rpm/Packages' + +# ***************************************************************************** +# Utilities +# **************************************************************************** +def _logger(): + """ + Returns logger for this module, when first needed. + @return logger specific for this process + """ + if not hasattr(_logger, "logger"): + _logger.logger = logging.getLogger(__name__) + return _logger.logger + +def _get_package_filter_function(filters): + """ + @param filters is a dictionary, where keys are package property + names and values are their desired values. + @return a function used to filter list of packages + """ + if not isinstance(filters, dict): + raise TypeError("filters must be a dictionary") + + filters = dict((k, value) for k, value in filters.items() + if value is not None) + + if "nevra" in filters: + def _cmp_nevra(pkg): + """@return True if pkg matches nevra filter""" + value = '%s-%s:%s-%s.%s' % ( + pkg.name, + "0" if not pkg.epoch or pkg.epoch == "(none)" + else pkg.epoch, + pkg.version, pkg.release, pkg.arch) + return value == filters["nevra"] + return _cmp_nevra + + elif "envra" in filters: + def _cmp_envra(pkg): + """@return True if pkg matches envra filter""" + value = '%s:%s-%s-%s.%s' % ( + "0" if not pkg.epoch or pkg.epoch == "(none)" + else pkg.epoch, + pkg.name, + pkg.version, pkg.release, pkg.arch) + return value == filters["envra"] + return _cmp_envra + + else: + if "evra" in filters: + for prop_name in ("epoch", "version", "release", "epoch"): + filters.pop(prop_name, None) + filter_list = [] + # properties are sorted by their filtering ability + # (the most unprobable property, that can match, comes first) + for prop_name in ("evra", "name", "version", "epoch", + "release", "arch"): + if not prop_name in filters: + continue + filter_list.append((prop_name, filters.pop(prop_name))) + def _cmp_props(pkg): + """@return True if pkg matches properies filter""" + return all(getattr(pkg, p) == v for p, v in filter_list) + return _cmp_props + +# ***************************************************************************** +# Decorators +# ***************************************************************************** +def _trace_function(func): + """ + Decorator for logging entries and exits of function or method. + """ + if not inspect.ismethod(func) and not inspect.isfunction(func): + raise TypeError("func must be a function") + + def _print_value(val): + """ + Used here for printing function arguments. Shortens the output + string, if that would be too long. + """ + if isinstance(val, list): + if len(val) < 2: + return str(val) + else: + return "[%s, ...]" % _print_value(val[0]) + return str(val) + + def _wrapper(self, *args, **kwargs): + """ + Wrapper for function or method, that does the logging. + """ + ftype = "method" if inspect.ismethod(func) else "function" + parent = ( func.im_class.__name__ + "." + if inspect.ismethod(func) else "") + + _logger().debug("entering %s %s%s with args=(%s)", + ftype, parent, func.__name__, + ", ".join(chain( + (_print_value(a) for a in args), + ( "%s=%s"%(k, _print_value(v)) + for k, v in kwargs.items())))) + result = func(self, *args, **kwargs) + _logger().debug("exiting %s %s%s", ftype, parent, func.__name__) + return result + + return _wrapper + +def _needs_database(method): + """ + Decorator for YumWorker job handlers, that need to access the yum database. + It ensures, that database is initialized and locks it in case, that + no session is active. + """ + def _wrapper(self, *args, **kwargs): + """ + Wrapper for the job handler method. + """ + self._init_database() #pylint: disable=W0212 + if self._session_level == 0: #pylint: disable=W0212 + self._lock_database() #pylint: disable=W0212 + try: + _logger().debug("calling job handler %s with args=(%s)", + method.__name__, + ", ".join(chain( + (str(a) for a in args), + ("%s=%s"%(k, str(v)) for k, v in kwargs.items())))) + result = method(self, *args, **kwargs) + _logger().debug("job handler %s finished", method.__name__) + return result + except: + _logger().debug("job handler %s terminated with exception: %s", + method.__name__, traceback.format_exc()) + raise + finally: + if self._session_level == 0: #pylint: disable=W0212 + self._unlock_database() #pylint: disable=W0212 + return _wrapper + +# ***************************************************************************** +# Classes +# ***************************************************************************** +class YumWorker(Process): + """ + The main process, that works with YUM API. It has two queues, one + for input jobs and second for results. + + Jobs are dispatched by their class names to particular handler method. + """ + + def __init__(self, + queue_in, + queue_out, + yum_args=None, + yum_kwargs=None, + logging_config=None): + Process.__init__(self) + self._queue_in = queue_in + self._queue_out = queue_out + self._session_level = 0 + self._session_ended = False + + if yum_args is None: + yum_args = tuple() + if yum_kwargs is None: + yum_kwargs = {} + + self._yum_args = (yum_args, yum_kwargs) + self._yum_base = None + + self._pkg_cache = None + self._logging_config = logging_config + + # ************************************************************************* + # Private methods + # ************************************************************************* + @_trace_function + def _init_database(self): + """ + Initializes yum base object, when it does no exists. + And updates the cache (when out of date). + """ + if self._yum_base is None: + _logger().info("creating YumBase with args=(%s)", + ", ".join(chain( + (str(a) for a in self._yum_args[0]), + ( "%s=%s"%(k, str(v)) + for k, v in self._yum_args[1].items())))) + self._yum_base = yum.YumBase( + *self._yum_args[0], **self._yum_args[1]) + + @_trace_function + def _free_database(self): + """ + Release the yum base object to safe memory. + """ + _logger().info("freing database") + self._pkg_cache.clear() + self._yum_base = None + + @_trace_function + def _lock_database(self): + """ + Only one process is allowed to work with package database at given time. + That's why we lock it. + + Try to lock it in loop, until success. + """ + while True: + try: + _logger().info("trying to lock database - session level %d", + self._session_level) + self._yum_base.doLock() + _logger().info("successfully locked up") + break + except yum.Errors.LockError as exc: + _logger().warn("failed to lock") + if exc.errno in (errno.EPERM, errno.EACCES, errno.ENOSPC): + _logger().error("can't create lock file") + raise errors.DatabaseLockError("Can't create lock file.") + _logger().info("trying to lock again after %.1f seconds", + LOCK_WAIT_INTERVAL) + time.sleep(LOCK_WAIT_INTERVAL) + + @_trace_function + def _unlock_database(self): + """ + The opposite to _lock_database() method. + """ + if self._yum_base is not None: + _logger().info("unlocking database") + self._yum_base.closeRpmDB() + self._yum_base.doUnlock() + + @_trace_function + def _transform_packages(self, packages, + cache_packages=True, + flush_cache=True): + """ + Return instances of PackageInfo for each package in packages. + Cache all the packages. + @param packages list of YumAvailablePackage instances + @param cache_packages whether to update cache with packages + @param flush_cache whether to clear the cache before adding input + packages; makes sense only with cachee_packages=True + """ + if cache_packages is True and flush_cache is True: + _logger().debug("flushing package cache") + self._pkg_cache.clear() + res = [] + for orig in packages: + pkg = packageinfo.make_package_from_db(orig) + if cache_packages is True: + self._pkg_cache[pkg.pkgid] = orig + res.append(pkg) + return res + + @_trace_function + def _cache_packages(self, packages, flush_cache=True, transform=False): + """ + Store packages in cache and return them. + @param flush_cache whether to clear the cache before adding new + packages + @param transform whether to return packages as PackageInfos + @return either list of original packages or PackageInfo instances + """ + if transform is True: + return self._transform_packages(packages, flush_cache=flush_cache) + if flush_cache is True: + _logger().debug("flushing package cache") + self._pkg_cache.clear() + for pkg in packages: + self._pkg_cache[id(pkg)] = pkg + return packages + + @_trace_function + def _lookup_package(self, pkg): + """ + Lookup the original package in cache. + If it was garbage collected already, make new query to find it. + @return instance of YumAvailablePackage + """ + if not isinstance(pkg, packageinfo.PackageInfo): + raise TypeError("pkg must be instance of PackageInfo") + _logger().debug("looking up yum package %s with id=%d", + pkg, pkg.pkgid) + try: + result = self._pkg_cache[pkg.pkgid] + _logger().debug("lookup successful") + except KeyError: + _logger().warn("lookup of package %s with id=%d failed, trying" + " to query database", pkg, pkg.pkgid) + result = self._handle_filter_packages( + 'installed' if pkg.installed else 'available', + allow_duplicates=False, + sort=False, + transform=False, + **pkg.key_props) + if len(result) < 1: + _logger().warn("package %s not found", pkg) + raise errors.PackageNotFound( + "package %s could not be found" % pkg) + result = result[0] + return result + + @_trace_function + def _do_work(self, job): + """ + Dispatcher of incoming jobs. Job is passed to the right handler + depending on its class. + """ + if not isinstance(job, jobs.YumJob): + raise TypeError("job must be instance of YumJob") + try: + handler = { + jobs.YumGetPackageList : self._handle_get_package_list, + jobs.YumFilterPackages : self._handle_filter_packages, + jobs.YumInstallPackage : self._handle_install_package, + jobs.YumRemovePackage : self._handle_remove_package, + jobs.YumUpdateToPackage: self._handle_update_to_package, + jobs.YumUpdatePackage : self._handle_update_package, + jobs.YumBeginSession : self._handle_begin_session, + jobs.YumEndSession : self._handle_end_session, + jobs.YumCheckPackage : self._handle_check_package + }[job.__class__] + _logger().info("processing job %s(id=%d)", + job.__class__.__name__, job.jobid) + except KeyError: + _logger().info("No handler for job \"%s\"", job.__class__.__name__) + raise errors.UnknownJob("No handler for job \"%s\"." % + job.__class__.__name__) + return handler(**job.job_kwargs) + + @_trace_function + def _run_transaction(self, name): + """ + Builds and runs the yum transaction and checks for errors. + @param name of transaction used only in error description on failure + """ + _logger().info("building transaction %s", name) + (code, msgs) = self._yum_base.buildTransaction() + if code == 1: + _logger().error("building transaction %s failed: %s", + name, "\n".join(msgs)) + raise errors.TransactionBuildFailed( + "Failed to build \"%s\" transaction: %s" % ( + name, "\n".join(msgs))) + _logger().info("processing transaction %s", name) + self._yum_base.processTransaction() + self._yum_base.closeRpmDB() + + @_trace_function + def _handle_begin_session(self): + """ + Handler for session begin job. + """ + self._session_level += 1 + _logger().info("beginning session level %s", self._session_level) + if self._session_level == 1: + self._init_database() + self._lock_database() + + @_trace_function + def _handle_end_session(self): + """ + Handler for session end job. + """ + _logger().info("ending session level %d", self._session_level) + self._session_level -= 1 + if self._session_level == 0: + self._unlock_database() + self._session_ended = True + + @_needs_database + def _handle_get_package_list(self, kind, allow_duplicates, sort, + transform=True): + """ + Handler for listing packages job. + @param transform says, whether to return just a package abstractions + or original ones + @return [pkg1, pkg2, ...] + """ + pkglist = self._yum_base.doPackageLists(kind, showdups=allow_duplicates) + if kind == 'all': + result = pkglist.available + pkglist.installed + else: + result = getattr(pkglist, kind) + if sort is True: + result.sort() + _logger().debug("returning %s packages", len(result)) + return self._cache_packages(result, transform=transform) + + @_needs_database + def _handle_filter_packages(self, kind, allow_duplicates, sort, + transform=True, **filters): + """ + Handler for filtering packages job. + @return [pkg1, pkg2, ...] + """ + pkglist = self._handle_get_package_list(kind, allow_duplicates, False, + transform=False) + matches = _get_package_filter_function(filters) + result = [p for p in pkglist if matches(p)] + if sort is True: + result.sort() + _logger().debug("%d packages matching", len(result)) + if transform is True: + # caching has been already done by _handle_get_package_list() + result = self._transform_packages(result, cache_packages=False) + return result + + @_needs_database + def _handle_install_package(self, pkg): + """ + Handler for package installation job. + @return installed package instance + """ + pkg_desired = self._lookup_package(pkg) + self._yum_base.install(pkg_desired) + self._run_transaction("install") + installed = self._handle_filter_packages("installed", False, False, + nevra=util.pkg2nevra(pkg_desired, with_epoch="ALWAYS")) + if len(installed) < 1: + raise errors.TransactionExecutionFailed( + "Failed to install desired package %s." % pkg) + return installed[0] + + @_needs_database + def _handle_remove_package(self, pkg): + """ + Handler for package removal job. + """ + pkg = self._lookup_package(pkg) + self._yum_base.remove(pkg) + self._run_transaction("remove") + + @_needs_database + def _handle_update_to_package(self, pkg): + """ + Handler for specific package update job. + @return package corresponding to pkg after update + """ + pkg_desired = self._lookup_package(pkg) + self._yum_base.update(update_to=True, + name=pkg_desired.name, + epoch=pkg_desired.epoch, + version=pkg_desired.version, + release=pkg_desired.release, + arch=pkg_desired.arch) + self._run_transaction("update") + installed = self._handle_filter_packages("installed", False, False, + **pkg.key_props) + if len(installed) < 1: + raise errors.TransactionExecutionFailed( + "Failed to update to desired package %s." % pkg) + return installed[0] + + @_needs_database + def _handle_update_package(self, pkg, to_epoch, to_version, to_release): + """ + Handler for package update job. + @return updated package instance + """ + pkg = self._lookup_package(pkg) + kwargs = { "name" : pkg.name, "arch" : pkg.arch } + if any(v is not None for v in (to_epoch, to_version, to_release)): + kwargs["update_to"] = True + if to_epoch: + kwargs["to_epoch"] = to_epoch + if to_version: + kwargs["to_version"] = to_version + if to_release: + kwargs["to_release"] = to_release + self._yum_base.update(**kwargs) + self._run_transaction("update") + kwargs = dict( (k[3:] if k.startswith("to_") else k, v) + for k, v in kwargs.items()) + installed = self._handle_filter_packages( + "installed", False, False, **kwargs) + if len(installed) < 1: + raise errors.TransactionExecutionFailed( + "Failed to update package %s." % pkg) + return installed[0] + + @_needs_database + def _handle_check_package(self, pkg): + """ + @return PackageFile instance for requested package + """ + pkg = self._lookup_package(pkg) + vpkg = yum.packages._RPMVerifyPackage(pkg, pkg.hdr.fiFromHeader(), + packagecheck.pkg_checksum_type(pkg), [], True) + return packagecheck.make_package_check_from_db(vpkg) + + # ************************************************************************* + # Public properties + # ************************************************************************* + @property + def uplink(self): + """ + @return input queue for jobs + """ + return self._queue_in + + @property + def downlink(self): + """ + @return output queue for job results + """ + return self._queue_out + + # ************************************************************************* + # Public methods + # ************************************************************************* + def run(self): + """ + Main loop of process. It accepts a job from input queue, handles it, + sends the result to output queue and marks the job as done. + + It is terminated, when None is received from input queue. + """ + if self._logging_config is not None: + try: + logging.config.dictConfig(self._logging_config) + except Exception: #pylint: disable=W0703 + # logging is not set up but client expects us to work + pass + _logger().info("starting %s main loop", self.__class__.__name__) + self._pkg_cache = weakref.WeakValueDictionary() + while True: + if self._session_ended and self._session_level == 0: + try: + job = self._queue_in.get(True, FREE_DATABASE_TIMEOUT) + except TQueue.Empty: + self._free_database() + self._session_ended = False + continue + else: + job = self._queue_in.get() + if job is not None: # not a terminate command + try: + result = self._do_work(job) + except Exception: #pylint: disable=W0703 + # (type, value, traceback) + result = sys.exc_info() + # traceback is not pickable - replace it with formatted + # text + result = ( result[0], result[1] + , traceback.format_tb(result[2])) + _logger().error("job %s(id=%d) failed: %s", + job.__class__.__name__, job.jobid, + traceback.format_exc()) + self._queue_out.put((job.jobid, result)) + self._queue_in.task_done() + if job is None: + break + diff --git a/src/software/setup.py b/src/software/setup.py index 25eb001..a044ac0 100644 --- a/src/software/setup.py +++ b/src/software/setup.py @@ -5,9 +5,13 @@ setup( author='Michal Minar', author_email='miminar@redhat.com', url='https://fedorahosted.org/openlmi/', - version='0.5', + version='0.6', namespace_packages=['openlmi'], - packages=['openlmi.software', 'openlmi.software.util'], + packages=[ + 'openlmi.software', + 'openlmi.software.core', + 'openlmi.software.util', + 'openlmi.software.yumdb'], install_requires=['openlmi'], license="LGPLv2+", classifiers=[ diff --git a/src/software/test/common.py b/src/software/test/common.py index ef3d157..d91f236 100644 --- a/src/software/test/common.py +++ b/src/software/test/common.py @@ -43,11 +43,14 @@ def remove_pkg(pkg, *args): pkg = pkg.name call(["rpm", "--quiet"] + list(args) + ["-e", pkg]) -def install_pkg(pkg, newer=True): +def install_pkg(pkg, newer=True, repolist=[]): """ Install a specific package. @param pkg is either package name or instance of Package In latter case, a specific version is installed. + @param repolist is a list of repositories, that should be + used for downloading, if using yum + when empty, all enabled repositories are used """ if isinstance(pkg, rpmcache.Package): try: @@ -57,7 +60,7 @@ def install_pkg(pkg, newer=True): except rpmcache.MissingRPM: pass pkg = pkg.name - call(["yum", "-q", "-y", "install", pkg]) + rpmcache.run_yum('-q', '-y', 'install', pkg, repolist=repolist) def is_installed(pkg, newer=True): """ @@ -148,6 +151,11 @@ class SoftwareBaseTestCase(unittest.TestCase): #pylint: disable=R0904 self.objpath = pywbem.CIMInstanceName( namespace="root/cimv2", classname=self.CLASS_NAME) + def install_pkg(self, pkg, newer=True, repolist=None): + if repolist is None: + repolist = self.test_repos + return install_pkg(pkg, newer, repolist) + def assertIsSubclass(self, cls, base_cls): #pylint: disable=C0103 """ Checks, whether cls is subclass of base_cls from CIM perspective. @@ -161,6 +169,21 @@ class SoftwareBaseTestCase(unittest.TestCase): #pylint: disable=R0904 return self.assertTrue(pywbem.is_subclass(self.conn, "root/cimv2", base_cls, cls)) + def assertEqual(self, fst, snd, *args, **kwargs): + if ( isinstance(fst, pywbem.CIMInstanceName) + and isinstance(snd, pywbem.CIMInstanceName) + and fst.classname == "LMI_SoftwarePackage" + and fst.classname == snd.classname + and fst.namespace == snd.namespace + and fst.keys() == snd.keys() + and all(fst[k] == snd[k] for k in ("Name", "SoftwareElementID", + "SoftwareElementState", "Version")) + and isinstance(fst["TargetOperatingSystem"], (int, long)) + and isinstance(snd["TargetOperatingSystem"], (int, long))): + return True + return unittest.TestCase.assertEqual( + self, fst, snd, *args, **kwargs) + @classmethod def setUpClass(cls): cls.url = os.environ.get('LMI_CIMOM_URL', 'http://localhost:5988') @@ -170,6 +193,11 @@ class SoftwareBaseTestCase(unittest.TestCase): #pylint: disable=R0904 cls.conn = pywbem.WBEMConnection(cls.url, (cls.user, cls.password)) cls.run_dangerous = ( os.environ.get('LMI_RUN_DANGEROUS', '0') == '1') + cls.test_repos = os.environ.get('LMI_SOFTWARE_TEST_REPOS', '') + if not cls.test_repos: + cls.test_repos = [] + else: + cls.test_repos = cls.test_repos.split(',') use_cache = os.environ.get('LMI_SOFTWARE_USE_CACHE', '0') == '1' cls.cache_dir = None if use_cache: @@ -185,7 +213,7 @@ class SoftwareBaseTestCase(unittest.TestCase): #pylint: disable=R0904 cls.pkgdb = rpmcache.get_pkg_database(use_cache=use_cache) for pkg in cls.pkgdb: if not is_installed(pkg.name): - install_pkg(pkg) + install_pkg(pkg, repolist=cls.test_repos) cls.pkg_files = dict((pkg.name, get_pkg_files(pkg)) for pkg in cls.pkgdb) diff --git a/src/software/test/rpmcache.py b/src/software/test/rpmcache.py index f68f128..9a0e98a 100644 --- a/src/software/test/rpmcache.py +++ b/src/software/test/rpmcache.py @@ -61,7 +61,7 @@ RE_REPO = re.compile( r'(?:^\*?)(?P<name>[^\s/]+\b)(?!\s+id)', re.MULTILINE | re.IGNORECASE) # maximum number of packages, that will be selected for testing -MAX_PKG_DB_SIZE = 5 +MAX_PKG_DB_SIZE = 10 # step used to iterate over package names used to check for thery dependencies # it's a number of packages, that will be passed to yum command at once PKG_DEPS_ITER_STEP = 50 @@ -95,6 +95,22 @@ def make_nevra(name, epoch, ver, rel, arch, with_epoch='NOT_ZERO'): estr += ":" return "%s-%s%s-%s.%s" % (name, estr, ver, rel, arch) +def run_yum(*params, **kwargs): + """ + Runs yum with params and returns its output + It's here especially to allow pass a repolist argument, that + specifies list of repositories, to run the command on. + """ + cmd = ['yum'] + list(params) + repolist = kwargs.get('repolist', []) + if repolist: + cmd += ['--disablerepo=*'] + cmd += ['--enablerepo='+r for r in repolist] + try: + return check_output(cmd) + except Exception: + import pdb;pdb.set_trace() + class Package(object): #pylint: disable=R0902 """ Element of test package database. It's a container for package @@ -244,20 +260,21 @@ def _check_single_pkg_deps( def _check_pkg_dependencies( installed, dup_list, - number_of_packages=MAX_PKG_DB_SIZE): + number_of_packages=MAX_PKG_DB_SIZE, + repolist=[]): """ Finds packages from dup_list with satisfied (installed) dependencies. @param installed is a set of installed package names @return filtered dup_list with at least number_of_packages elements. """ - cmd = ['yum', 'deplist'] + yum_params = ['deplist'] dups_no_deps = [] for i in range(0, len(dup_list), PKG_DEPS_ITER_STEP): dups_part = dup_list[i:i+PKG_DEPS_ITER_STEP] - cmd = cmd[:2] + yum_params = yum_params[:1] for dups in dups_part: - cmd.extend([d.get_nevra(newer=False) for d in dups]) - deplist_str = check_output(cmd) + yum_params.extend([d.get_nevra(newer=False) for d in dups]) + deplist_str = run_yum(*yum_params, repolist=repolist) matches = RE_PKG_DEPS.finditer(deplist_str) prev_match = None for pkgs in dups_part: @@ -283,14 +300,14 @@ def _check_pkg_dependencies( break return dups_no_deps -def _sorted_db_by_size(pkgdb): +def _sorted_db_by_size(pkgdb, repolist=[]): """ @param pkgdb is a list of lists of packages with common name @return sorted instances of Package according to their size """ - cmd = ['yum', 'info', '--showduplicates'] - cmd.extend([ps[0].name for ps in pkgdb]) - info_str = check_output(cmd) + yum_params = ['info', '--showduplicates'] + yum_params.extend([ps[0].name for ps in pkgdb]) + info_str = run_yum(*yum_params, repolist=repolist) pkg_sizes = {} # to get correct ordering from "yum info" command # { pkg_name : [(epoch, version, release), ... ] } @@ -376,7 +393,8 @@ def is_installed(pkg, newer=True): return call(["rpm", "--quiet", "-q", pkg]) == 0 else: try: - cmd = [ "rpm", "-q", "--qf", "%{EPOCH}:%{NVRA}", pkg.name ] + cmd = [ "rpm", "-q", "--qf", "%{EPOCH}:%{NVRA}", pkg.get_nevra( + newer, with_epoch='NEVER') ] out = check_output(cmd) epoch, nvra = out.split(':') #pylint: disable=E1103 if not epoch or epoch == "(none)": @@ -430,7 +448,9 @@ def load_pkgdb(cache_dir=''): #print "Loaded package database from: %s" % date_time return pkgdb -def get_pkg_database(force_update=False, use_cache=True, cache_dir=''): +def get_pkg_database(force_update=False, use_cache=True, + cache_dir='', + repolist=[]): """ Checks yum database for available packages, that have at least two different versions in repositories. Only not installed ones with @@ -454,16 +474,18 @@ def get_pkg_database(force_update=False, use_cache=True, cache_dir=''): installed = set(check_output( #pylint: disable=E1103 ['rpm', '-qa', '--qf=%{NAME}\n']).splitlines()) #print "Getting all available packages" - avail_str = check_output(['yum', 'list', 'available', '--showduplicates']) + avail_str = run_yum('list', 'available', '--showduplicates', + repolist=repolist) # list of lists of packages with the same name, longer than 2 #print "Finding duplicates" dups_list = _filter_duplicates(installed, avail_str) #print "Selecting only those (from %d) with installed dependencies" % \ #len(dups_list) selected = _check_pkg_dependencies(installed, dups_list, - number_of_packages=MAX_PKG_DB_SIZE*5) + number_of_packages=MAX_PKG_DB_SIZE*5, + repolist=repolist) #print "Selecting the smallest ones" - pkgdb = _sorted_db_by_size(selected) + pkgdb = _sorted_db_by_size(selected, repolist=repolist) if use_cache: repolist = _get_repo_list() _download_pkgdb(repolist, pkgdb, cache_dir) diff --git a/src/software/test/run.py b/src/software/test/run.py index aba362f..950b0c9 100755 --- a/src/software/test/run.py +++ b/src/software/test/run.py @@ -95,8 +95,17 @@ def parse_cmd_line(): parser.add_argument('--cache-dir', default=os.environ.get('LMI_SOFTWARE_CACHE_DIR', ''), help="Use particular directory, instead of temporary one, to store" - " rpm packages for testing purposes. Overrides nevironment" + " rpm packages for testing purposes. Overrides environment" " variable LMI_SOFTWARE_CACHE_DIR.") + parser.add_argument('--test-repos', + default=os.environ.get('LMI_SOFTWARE_REPOSITORIES', ''), + help="Use this option to specify list of repositories, that" + " alone should be used for testing. Overrides environment" + " variable LMI_SOFTWARE_REPOSITORIES.") + parser.add_argument('--test-packages', + default=os.environ.get('LMI_SOFTWARE_PACKAGES', ''), + help="Specify packages for dangerous tests. If empty" + " and cache is enabled, some will be picked up by algorithm") parser.add_argument('--force-update', action="store_true", help="Force update of package database. Otherwise an old" " one will be used (if any exists).") @@ -154,6 +163,8 @@ def prepare_environment(args): os.environ['LMI_SOFTWARE_USE_CACHE'] = '1' if args.use_cache else '0' if args.use_cache: os.environ['LMI_SOFTWARE_CACHE_DIR'] = CACHE_DIR + os.environ['LMI_SOFTWARE_REPOSITORIES'] = args.test_repos + os.environ['LMI_SOFTWARE_PACKAGES'] = args.test_packages def load_tests(loader, standard_tests, pattern): """ @@ -252,8 +263,16 @@ def main(): CACHE_DIR = tempfile.mkdtemp(suffix="software_database") elif args.use_cache: CACHE_DIR = args.cache_dir + if not os.path.exists(args.cache_dir): + os.makedirs(args.cache_dir) try_connection(args) - rpmcache.get_pkg_database(args.force_update, args.use_cache, CACHE_DIR) + if args.test_repos: + repolist = args.test_repos.split(',') + else: + repolist = [] + rpmcache.get_pkg_database(args.force_update, args.use_cache, + CACHE_DIR, repolist=repolist) + #rpmcache.make_pkg_database(packages prepare_environment(args) test_program = unittest.main(argv=ut_args, testLoader=LMITestLoader(), exit=False) diff --git a/src/software/test/test_software_file_check.py b/src/software/test/test_software_file_check.py index 4228c18..7b97ee9 100755 --- a/src/software/test/test_software_file_check.py +++ b/src/software/test/test_software_file_check.py @@ -217,9 +217,10 @@ class TestSoftwareFileCheck(common.SoftwareBaseTestCase): "File %s:%s should exist"%(pkg.name, filepath)) self.assertTrue(cur_pflags.type, "File type should match for symlink %s:%s"%(pkg.name, filepath)) - self.assertFalse(cur_pflags.size, - "File size should not match for symlink %s:%s"%( - pkg.name, filepath)) + # file size not checked for symlinks + #self.assertFalse(cur_pflags.size, + #"File size should not match for symlink %s:%s"%( + #pkg.name, filepath)) self.assertTrue(cur_pflags.mode, "File mode should match for symlink %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.checksum, @@ -432,7 +433,7 @@ class TestSoftwareFileCheck(common.SoftwareBaseTestCase): and not common.verify_pkg(pkg.name)): common.remove_pkg(pkg.name) if not common.is_installed(pkg.name): - common.install_pkg(pkg) + self.install_pkg(pkg) self.assertTrue(common.is_installed(pkg), "Package %s must be installed"%pkg) @@ -449,12 +450,14 @@ class TestSoftwareFileCheck(common.SoftwareBaseTestCase): for key in self.KEYS: if key.lower() == "softwareelementid": self.assertEqualSEID(inst[key], objpath[key], - "OP keys should match for %s:%s"%( - pkg.name, filepath)) + "OP key %s values should match for %s:%s"%( + key, pkg.name, filepath)) + elif key.lower() == "targetoperatingsystem": + self.assertIsInstance(objpath[key], (int, long)) else: self.assertEqual(objpath[key], inst[key], - "OP keys should match for %s:%s"%( - pkg.name, filepath)) + "OP key %s values should match for %s:%s"%( + key, pkg.name, filepath)) self.assertTrue(inst["FileExists"], "File %s:%s must exist"%(pkg.name, filepath)) @@ -467,6 +470,10 @@ class TestSoftwareFileCheck(common.SoftwareBaseTestCase): for prop in ( "FileType", "FileUserID", "FileGroupID" , "FileMode", "FileSize", "LinkTarget" , "FileChecksum", "FileModeFlags"): + if ( ( os.path.islink(filepath) + or (not os.path.isfile(filepath))) + and prop == "FileSize"): + continue self.assertEqual(inst["Expected"+prop], inst[prop], "%s should match for %s:%s"%(prop, pkg.name, filepath)) if os.path.islink(filepath): @@ -486,7 +493,7 @@ class TestSoftwareFileCheck(common.SoftwareBaseTestCase): if common.is_installed(pkg) and not common.verify_pkg(pkg.name): common.remove_pkg(pkg.name) if not common.is_installed(pkg.name): - common.install_pkg(pkg) + self.install_pkg(pkg) self.assertTrue(common.is_installed(pkg), "Package %s must be installed"%pkg) for filepath in files: diff --git a/src/software/test/test_software_installed_package.py b/src/software/test/test_software_installed_package.py index 9de627a..93669f9 100755 --- a/src/software/test/test_software_installed_package.py +++ b/src/software/test/test_software_installed_package.py @@ -24,13 +24,15 @@ Unit tests for LMI_SoftwareInstalledPackage provider. import os import pywbem -import shutil import socket import stat import unittest import common -import rpmcache + +def make_path_tuple(objpath): + return tuple(objpath[a] for a in ("SoftwareElementID", "Name", "Version", + "SoftwareElementState")) class TestSoftwareInstalledPackage(common.SoftwareBaseTestCase): """ @@ -71,7 +73,7 @@ class TestSoftwareInstalledPackage(common.SoftwareBaseTestCase): """ for pkg in self.pkgdb: if not common.is_installed(pkg): - common.install_pkg(pkg) + self.install_pkg(pkg) objpath = self.make_op(pkg) inst = self.conn.GetInstance(InstanceName=objpath, LocalOnly=False) self.assertIsSubclass(inst.path.classname, self.CLASS_NAME) @@ -93,32 +95,46 @@ class TestSoftwareInstalledPackage(common.SoftwareBaseTestCase): "Package %s should not be installed"%pkg) @common.mark_dangerous - def test_enum_instances(self): + def test_enum_instance_names(self): """ Tests EnumInstances call. TODO: test this in non-dangerous way """ - pkg = self.pkgdb[0] - if common.is_installed(pkg.name): + pkg = self.pkgdb[0] if len(self.pkgdb) > 0 else None + if pkg and common.is_installed(pkg.name): common.remove_pkg(pkg.name) insts1 = self.conn.EnumerateInstanceNames(ClassName=self.CLASS_NAME) - common.install_pkg(pkg) - insts2 = self.conn.EnumerateInstanceNames(ClassName=self.CLASS_NAME) - self.assertEqual(len(insts1) + 1, len(insts2)) + if pkg: + self.install_pkg(pkg) + insts2 = self.conn.EnumerateInstanceNames(ClassName=self.CLASS_NAME) + self.assertEqual(len(insts1) + 1, len(insts2)) - objpath = self.make_op(pkg) - self.assertIn(objpath['Software'], - (inst['Software'] for inst in insts2)) + if pkg: + objpath = self.make_op(pkg) + self.assertIn(make_path_tuple(objpath['Software']), + set(make_path_tuple(inst['Software']) for inst in insts2)) self.assertTrue(all(isinstance(inst, pywbem.CIMInstanceName) for inst in insts1)) + @common.mark_dangerous + def test_enum_instances(self): + """ + Tests EnumInstances call. + """ + pkg = self.pkgdb[0] if len(self.pkgdb) > 0 else None + if pkg and not common.is_installed(pkg.name): + self.install_pkg(pkg.name) insts1 = self.conn.EnumerateInstances(ClassName=self.CLASS_NAME) - common.remove_pkg(pkg.name) - insts2 = self.conn.EnumerateInstances(ClassName=self.CLASS_NAME) + if pkg: + common.remove_pkg(pkg.name) + insts2 = self.conn.EnumerateInstances(ClassName=self.CLASS_NAME) + objpath = self.make_op(pkg) + self.assertEqual(len(insts2) + 1, len(insts1)) + path_values = set(make_path_tuple(p["Software"]) for p in insts1) + self.assertIn(make_path_tuple(objpath['Software']), path_values) + path_values = set(make_path_tuple(p["Software"]) for p in insts2) + self.assertNotIn(make_path_tuple(objpath['Software']), path_values) - self.assertEqual(len(insts2) + 1, len(insts1)) - self.assertIn(objpath['Software'], - (inst['Software'] for inst in insts1)) self.assertTrue(all(inst['Software'] == inst.path['Software'] for inst in insts1)) self.assertTrue(all(inst['System'] == inst.path['System'] @@ -164,7 +180,7 @@ class TestSoftwareInstalledPackage(common.SoftwareBaseTestCase): """ for pkg in self.pkgdb: if not common.is_installed(pkg.name): - common.install_pkg(pkg) + self.install_pkg(pkg) self.assertTrue(common.is_installed(pkg.name), "Package %s must be installed"%pkg) objpath = self.make_op(pkg) @@ -173,7 +189,8 @@ class TestSoftwareInstalledPackage(common.SoftwareBaseTestCase): "Package %s must be uninstalled"%pkg) with self.assertRaises(pywbem.CIMError) as cmngr: self.conn.DeleteInstance(objpath) - self.assertEqual(pywbem.CIM_ERR_NOT_FOUND, cmngr.exception.args[0], + self.assertIn(cmngr.exception.args[0], + [pywbem.CIM_ERR_FAILED, pywbem.CIM_ERR_NOT_FOUND], "Package %s can not be uninstalled again"%pkg) @common.mark_dangerous @@ -190,7 +207,7 @@ class TestSoftwareInstalledPackage(common.SoftwareBaseTestCase): and not common.is_installed(pkg))) : common.remove_pkg(pkg.name) if not common.is_installed(pkg.name): - common.install_pkg(pkg) + self.install_pkg(pkg) self.assertTrue(common.is_installed(pkg), "Package %s must be installed"%pkg) @@ -244,8 +261,8 @@ class TestSoftwareInstalledPackage(common.SoftwareBaseTestCase): self.assertEqual(1, len(oparms)) self.assertIn("Failed", oparms) self.assertEqual(len(oparms["Failed"]), cnt_bad, - "Number of errors should match number of elements in" - " Failed for %s:%s"%(pkg.name, file_path)) + "Number of errors not correct. Failed for %s:%s" % ( + pkg.name, file_path)) self.assertIn(file_path, (p["Name"] for p in oparms["Failed"]), "File %s:%s should also be in failed"%(pkg.name, file_path)) @@ -259,7 +276,7 @@ class TestSoftwareInstalledPackage(common.SoftwareBaseTestCase): and not common.is_installed(pkg, False)): common.remove_pkg(pkg.name) if not common.is_installed(pkg.name): - common.install_pkg(pkg, False) + self.install_pkg(pkg, False) self.assertTrue(common.is_installed(pkg, False), "Package %s must be installed"%pkg.get_nevra(False)) @@ -288,17 +305,19 @@ class TestSoftwareInstalledPackage(common.SoftwareBaseTestCase): self.assertTrue(common.is_installed(pkg.name), "Package %s must be installed"%pkg) self.assertFalse(common.is_installed(pkg, False), - "Older package %s must not be installed"%pkg.get_nevra(False)) + "Older package %s must not be installed" % + pkg.get_nevra(False)) with self.assertRaises(pywbem.CIMError) as cmngr: self.conn.InvokeMethod( MethodName="Update", ObjectName=objpath) self.assertEqual(pywbem.CIM_ERR_NOT_FOUND, cmngr.exception.args[0], - "Older package %s should not be installed"%pkg.get_nevra(False)) + "Older package %s should not be installed" % + pkg.get_nevra(False)) common.remove_pkg(pkg.name) - common.install_pkg(pkg, False) + self.install_pkg(pkg, False) self.assertTrue(common.is_installed(pkg, False)) (rval, oparms) = self.conn.InvokeMethod( diff --git a/src/software/test/test_software_package.py b/src/software/test/test_software_package.py index 3411678..4cd5da9 100755 --- a/src/software/test/test_software_package.py +++ b/src/software/test/test_software_package.py @@ -66,11 +66,14 @@ class TestSoftwarePackage(common.SoftwareBaseTestCase): #pylint: disable=R0904 for key in self.KEYS: self.assertTrue(inst.properties.has_key(key), "OP is missing \"%s\" key for package %s"%(key, pkg)) - self.assertEqual(inst.path[key], inst[key], - "Object paths of instance should match for %s"%pkg) + if key == "TargetOperatingSystem": + self.assertIsInstance(inst.path[key], (int, long)) + else: + self.assertEqual(inst.path[key], inst[key], + "Object paths of instance should match for %s"%pkg) self.assertEqual(pkg.up_rel, inst['Release'], "Release property should match for %s"%pkg) - common.install_pkg(pkg) + self.install_pkg(pkg) objpath['SoftwareElementState'] = pywbem.Uint16(2) inst = self.conn.GetInstance(InstanceName=objpath, LocalOnly=False) self.assertEqual(inst.path, objpath, @@ -123,7 +126,7 @@ class TestSoftwarePackage(common.SoftwareBaseTestCase): #pylint: disable=R0904 """ for pkg in self.pkgdb: if not common.is_installed(pkg.name): - common.install_pkg(pkg) + self.install_pkg(pkg) objpath = self.make_op(pkg) (rval, oparms) = self.conn.InvokeMethod( MethodName="Remove", |