diff options
author | Michal Minar <miminar@redhat.com> | 2012-10-10 17:55:12 +0200 |
---|---|---|
committer | Michal Minar <miminar@redhat.com> | 2012-10-10 17:55:12 +0200 |
commit | 2aa4f8635b13e1849763a1964eb45032ee30948c (patch) | |
tree | 3563082116a1a3464a365668505a4db68d55a20a /src/software | |
parent | 167c13bdf07c2a86ff7043b4b874073556858e69 (diff) | |
download | openlmi-providers-2aa4f8635b13e1849763a1964eb45032ee30948c.tar.gz openlmi-providers-2aa4f8635b13e1849763a1964eb45032ee30948c.tar.xz openlmi-providers-2aa4f8635b13e1849763a1964eb45032ee30948c.zip |
renamed yum package providers to software
renamed LMI_Yum* to LMI_Software*
removed attributes GroupString and LicenseString from LMI_SoftwarePackage
modified attributes Group, Architecture and License of
LMI_SoftwarePackage to return string instead of Uints
- because values and valuemaps would need frequent updates
Diffstat (limited to 'src/software')
-rw-r--r-- | src/software/TODO | 4 | ||||
-rw-r--r-- | src/software/providers/LMI_SoftwareFileCheck.py | 408 | ||||
-rw-r--r-- | src/software/providers/LMI_SoftwareInstalledPackage.py | 475 | ||||
-rw-r--r-- | src/software/providers/LMI_SoftwarePackage.py | 518 | ||||
-rw-r--r-- | src/software/providers/LMI_SoftwarePackageFile.py | 301 | ||||
-rw-r--r-- | src/software/providers/__init__.py | 16 | ||||
-rw-r--r-- | src/software/providers/util/__init__.py | 17 | ||||
-rw-r--r-- | src/software/providers/util/common.py | 804 | ||||
-rw-r--r-- | src/software/providers/util/singletonmixin.py | 508 | ||||
-rw-r--r-- | src/software/setup.py | 16 | ||||
-rw-r--r-- | src/software/test/README | 5 | ||||
-rw-r--r-- | src/software/test/common.py | 109 | ||||
-rwxr-xr-x | src/software/test/test_software_file_check.py | 289 | ||||
-rwxr-xr-x | src/software/test/test_software_installed_package.py | 241 | ||||
-rwxr-xr-x | src/software/test/test_software_package.py | 89 |
15 files changed, 3800 insertions, 0 deletions
diff --git a/src/software/TODO b/src/software/TODO new file mode 100644 index 0000000..18c155b --- /dev/null +++ b/src/software/TODO @@ -0,0 +1,4 @@ +provide a way to manage repositories +associate LMI_YumPackages with each LMI_YumRepository for available packages + this will solve enumeration of available packages +make installation/update/remove asynchronous jobs diff --git a/src/software/providers/LMI_SoftwareFileCheck.py b/src/software/providers/LMI_SoftwareFileCheck.py new file mode 100644 index 0000000..0058f78 --- /dev/null +++ b/src/software/providers/LMI_SoftwareFileCheck.py @@ -0,0 +1,408 @@ +# 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/>. + +"""Python Provider for LMI_SoftwareFileCheck + +Instruments the CIM class LMI_SoftwareFileCheck + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 +from util.common import * + +filecheck2model = SoftwareFileCheck.filecheck_wrapper( + 'root/cimv2', 'LMI_SoftwareFileCheck') + +class LMI_SoftwareFileCheck(CIMProvider2): + """Instrument the CIM class LMI_SoftwareFileCheck + + Identifies a file contained by RPM package. It's located in directory + identified in FileName. The Invoke methods check for file existence + and whether its attributes match those given by RPM package. + + """ + + def __init__ (self, env): + logger = env.get_logger() + logger.log_debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + + def get_instance(self, env, model): + """Return an instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + model -- A template of the pywbem.CIMInstance to be returned. The + key properties are set on this instance to correspond to the + instanceName that was requested. The properties of the model + are already filtered according to the PropertyList from the + request. Only properties present in the model need to be + given values. If you prefer, you can set all of the + values, and the instance will be filtered for you. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM + Instance does not exist in the specified namespace) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.get_instance()' \ + % self.__class__.__name__) + + vpkg = SoftwareFileCheck.object_path2yumcheck(env, model.path) + pkg = vpkg.po + fi = pkg.hdr.fiFromHeader() + return filecheck2model(vpkg, model['Name'], env, False) + + def enum_instances(self, env, model, keys_only): + """Enumerate instances. + + The WBEM operations EnumerateInstances and EnumerateInstanceNames + are both mapped to this method. + This method is a python generator + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + model -- A template of the pywbem.CIMInstances to be generated. + The properties of the model are already filtered according to + the PropertyList from the request. Only properties present in + the model need to be given values. If you prefer, you can + always set all of the values, and the instance will be filtered + for you. + keys_only -- A boolean. True if only the key properties should be + set on the generated instances. + + Possible Errors: + 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) # Remove to implement + + def set_instance(self, env, instance, modify_existing): + """Return a newly created or modified instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + instance -- The new pywbem.CIMInstance. If modifying an existing + instance, the properties on this instance have been filtered by + the PropertyList from the request. + modify_existing -- True if ModifyInstance, False if CreateInstance + + Return the new instance. The keys must be set on the new instance. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_ALREADY_EXISTS (the CIM Instance already exists -- only + valid if modify_existing is False, indicating that the operation + was CreateInstance) + CIM_ERR_NOT_FOUND (the CIM Instance does not exist -- only valid + if modify_existing is True, indicating that the operation + was ModifyInstance) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.set_instance()' \ + % self.__class__.__name__) + # TODO create or modify the instance + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) # Remove to implement + return instance + + def delete_instance(self, env, instance_name): + """Delete an instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + instance_name -- A pywbem.CIMInstanceName specifying the instance + to delete. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_NAMESPACE + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_INVALID_CLASS (the CIM Class does not exist in the specified + namespace) + CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM + Instance does not exist in the specified namespace) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.delete_instance()' \ + % self.__class__.__name__) + + # TODO delete the resource + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) # Remove to implement + + def cim_method_invoke(self, env, object_name): + """Implements LMI_SoftwareFileCheck.Invoke() + + The Invoke method evaluates this Check. The details of the + evaluation are described by the specific subclasses of CIM_Check. + When the SoftwareElement being checked is already installed, the + CIM_InstalledSoftwareElement association identifies the + CIM_ComputerSystem in whose context the Invoke is executed. If + this association is not in place, then the InvokeOnSystem method + should be used - since it identifies the TargetSystem as an input + parameter of the method. \nThe results of the Invoke method are + based on the return value. A zero is returned if the condition is + satisfied. A one is returned if the method is not supported. Any + other value indicates the condition is not satisfied. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method Invoke() + should be invoked. + + Returns a two-tuple containing the return value (type pywbem.Uint32) + and a list of CIMParameter objects representing the output parameters + + Output parameters: none + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, + unrecognized or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the target CIM Class or instance does not + exist in the specified namespace) + CIM_ERR_METHOD_NOT_AVAILABLE (the CIM Server is unable to honor + the invocation request) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.cim_method_invoke()' \ + % self.__class__.__name__) + + with YumDB.getInstance(env) as yb: + vpkg = SoftwareFileCheck.object_path2yumcheck(env, object_name) + fc = SoftwareFileCheck.test_file(env, + SoftwareFileCheck.pkg_checksum_type(vpkg.po), + vpkg._files[object_name["Name"]]) + out_params = [] + ret = 0 if SoftwareFileCheck.filecheck_passed(fc) else 2 + return (pywbem.Uint32(ret), out_params) + + 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 + evaluation are described by the specific subclasses of CIM_Check. + The method\'s TargetSystem input parameter specifies the + ComputerSystem in whose context the method is invoked. \nThe + results of the InvokeOnSystem method are based on the return + value. A zero is returned if the condition is satisfied. A one is + returned if the method is not supported. Any other value indicates + the condition is not satisfied. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method InvokeOnSystem() + should be invoked. + param_targetsystem -- The input parameter TargetSystem ( + type REF (pywbem.CIMInstanceName( + classname='CIM_ComputerSystem', ...)) + Reference to ComputerSystem in whose context the method is to + be invoked. + + + Returns a two-tuple containing the return value (type pywbem.Uint32) + and a list of CIMParameter objects representing the output parameters + + Output parameters: none + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, + unrecognized or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the target CIM Class or instance does not + exist in the specified namespace) + CIM_ERR_METHOD_NOT_AVAILABLE (the CIM Server is unable to honor + the invocation request) + 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) # Remove to implemented + out_params = [] + #rval = # TODO (type pywbem.Uint32) + return (rval, out_params) + + 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 + +## get_providers() for associating CIM Class Name to python provider class name + +def get_providers(env): + lmi_softwarefilecheck_prov = LMI_SoftwareFileCheck(env) + return {'LMI_SoftwareFileCheck': lmi_softwarefilecheck_prov} diff --git a/src/software/providers/LMI_SoftwareInstalledPackage.py b/src/software/providers/LMI_SoftwareInstalledPackage.py new file mode 100644 index 0000000..008a47b --- /dev/null +++ b/src/software/providers/LMI_SoftwareInstalledPackage.py @@ -0,0 +1,475 @@ +# -*- 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/>. + +"""Python Provider for LMI_SoftwareInstalledPackage + +Instruments the CIM class LMI_SoftwareInstalledPackage + +""" + +import itertools +import pywbem +from pywbem.cim_provider2 import CIMProvider2 +from LMI_SoftwarePackage import pkg2model, LMI_SoftwarePackage +from LMI_SoftwareFileCheck import filecheck2model +from util.common import * + +class LMI_SoftwareInstalledPackage(CIMProvider2): + """Instrument the CIM class LMI_SoftwareInstalledPackage + + The InstalledSoftwareElement association allows the identification of + the ComputerSystem on which a particular SoftwareElement is installed. + + """ + + def __init__ (self, env): + logger = env.get_logger() + logger.log_debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + def get_instance(self, env, model): + """Return an instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + model -- A template of the pywbem.CIMInstance to be returned. The + key properties are set on this instance to correspond to the + instanceName that was requested. The properties of the model + are already filtered according to the PropertyList from the + request. Only properties present in the model need to be + given values. If you prefer, you can set all of the + values, and the instance will be filtered for you. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM + Instance does not exist in the specified namespace) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.get_instance()' \ + % self.__class__.__name__) + + check_computer_system_op(env, model['System']) + with YumDB.getInstance(env): + pkg = SoftwarePackage.object_path2pkg(env, model['Software']) + model['Software'] = pkg2model(env, pkg, True) + return model + + def enum_instances(self, env, model, keys_only): + """Enumerate instances. + + The WBEM operations EnumerateInstances and EnumerateInstanceNames + are both mapped to this method. + This method is a python generator + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + model -- A template of the pywbem.CIMInstances to be generated. + The properties of the model are already filtered according to + the PropertyList from the request. Only properties present in + the model need to be given values. If you prefer, you can + always set all of the values, and the instance will be filtered + for you. + keys_only -- A boolean. True if only the key properties should be + set on the generated instances. + + Possible Errors: + 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. + model.path.update({'System': None, 'Software': None}) + + yum_package_path = pywbem.CIMInstanceName("LMI_SoftwarePackage", + namespace=model.path.namespace, + host=model.path.host) + model['System'] = get_computer_system_op() + with YumDB.getInstance(env) as yb: + for rpm in yb.rpmdb: + iname = pkg2model(env, rpm, True, yum_package_path) + model['Software'] = iname + if keys_only: + yield model + else: + try: + yield self.get_instance(env, model) + except pywbem.CIMError, (num, msg): + if num not in (pywbem.CIM_ERR_NOT_FOUND, + pywbem.CIM_ERR_ACCESS_DENIED): + raise + + def set_instance(self, env, instance, modify_existing): + """Return a newly created or modified instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + instance -- The new pywbem.CIMInstance. If modifying an existing + instance, the properties on this instance have been filtered by + the PropertyList from the request. + modify_existing -- True if ModifyInstance, False if CreateInstance + + Return the new instance. The keys must be set on the new instance. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_ALREADY_EXISTS (the CIM Instance already exists -- only + valid if modify_existing is False, indicating that the operation + was CreateInstance) + CIM_ERR_NOT_FOUND (the CIM Instance does not exist -- only valid + if modify_existing is True, indicating that the operation + was ModifyInstance) + 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_FOUND, + "MofifyInstance is not supported") + + check_computer_system_op(env, instance['System']) + + with YumDB.getInstance(env) as yb: + pkg = SoftwarePackage.object_path2pkg_search(env, instance['Software']) + if isinstance(pkg, yum.rpmsack.RPMInstalledPackage): + raise pywbem.CIMError(pywbem.CIM_ERR_ALREADY_EXISTS, + "Package is already installed.") + + logger.log_info('installing package {}'.format(pkg.nevra)) + # install + yb.install(pkg) + yb.buildTransaction() + yb.processTransaction() + logger.log_info('package installed'.format(pkg.nevra)) + + # return instance + pkg_iname = pkg2model(env, pkg, True) + pkg_iname["SoftwareElementState"] = \ + LMI_SoftwarePackage.Values.SoftwareElementState.Executable + instance["Software"] = pkg_iname + return LMI_SoftwareInstalledPackage(env).get_instance(env, instance) + + def delete_instance(self, env, instance_name): + """Delete an instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + instance_name -- A pywbem.CIMInstanceName specifying the instance + to delete. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_NAMESPACE + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_INVALID_CLASS (the CIM Class does not exist in the specified + namespace) + CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM + Instance does not exist in the specified namespace) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.delete_instance()' \ + % self.__class__.__name__) + + check_computer_system_op(env, instance_name['System']) + with YumDB.getInstance(env) as yb: + pkg = SoftwarePackage.object_path2pkg(env, instance_name["Software"]) + logger.log_info('removing package "%s"' % pkg.nevra) + yb.remove(pkg) + yb.buildTransaction() + yb.processTransaction() + logger.log_info('package "%s" removed' % pkg.nevra) + + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + All four association-related operations (Associators, AssociatorNames, + References, ReferenceNames) are mapped to this method. + This method is a python generator + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName that defines the source + CIM Object whose associated Objects are to be returned. + model -- A template pywbem.CIMInstance to serve as a model + of the objects to be returned. Only properties present on this + model need to be set. + result_class_name -- If not empty, this string acts as a filter on + the returned set of Instances by mandating that each returned + Instances MUST represent an association between object_name + and an Instance of a Class whose name matches this parameter + or a subclass. + role -- If not empty, MUST be a valid Property name. It acts as a + filter on the returned set of Instances by mandating that each + returned Instance MUST refer to object_name via a Property + whose name matches the value of this parameter. + result_role -- If not empty, MUST be a valid Property name. It acts + as a filter on the returned set of Instances by mandating that + each returned Instance MUST represent associations of + object_name to other Instances, where the other Instances play + the specified result_role in the association (i.e. the + name of the Property in the Association Class that refers to + the Object related to object_name MUST match the value of this + parameter). + keys_only -- A boolean. True if only the key properties should be + set on the generated instances. + + The following diagram may be helpful in understanding the role, + result_role, and result_class_name parameters. + +------------------------+ +-------------------+ + | object_name.classname | | result_class_name | + | ~~~~~~~~~~~~~~~~~~~~~ | | ~~~~~~~~~~~~~~~~~ | + +------------------------+ +-------------------+ + | +-----------------------------------+ | + | | [Association] model.classname | | + | object_name | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | + +--------------+ object_name.classname REF role | | + (CIMInstanceName) | result_class_name REF result_role +------+ + | |(CIMInstanceName) + +-----------------------------------+ + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_NAMESPACE + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.references()' \ + % self.__class__.__name__) + ch = env.get_cimom_handle() + + # If you want to get references for free, implemented in terms + # of enum_instances, just leave the code below unaltered. + if ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='CIM_ComputerSystem') or \ + ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='LMI_SoftwarePackage'): + return self.simple_refs(env, object_name, model, + result_class_name, role, result_role, keys_only) + + def cim_method_checkintegrity(self, env, object_name): + """Implements LMI_SoftwarePackage.CheckIntegrity() + + Verify existence and attributes of files installed from RPM + package. If all files installed exist and their attributes + matches, method returns "Pass". "Not passed" is returned when + arbitrary file differs in its attributes. And "Error" if + verification could not be done. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method CheckIntegrity() + should be invoked. + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.CheckIntegrity) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Failed -- (type REF (pywbem.CIMInstanceName( + classname='LMI_SoftwareFileCheck', ...)) + Array of references to LMI_SoftwareFileCheck, that did not pass + verification. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, + unrecognized or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the target CIM Class or instance does not + exist in the specified namespace) + CIM_ERR_METHOD_NOT_AVAILABLE (the CIM Server is unable to honor + the invocation request) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.cim_method_checkintegrity()' \ + % self.__class__.__name__) + + failed = [] + with YumDB.getInstance(env) as yb: + 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: + fc = SoftwareFileCheck.test_file(env, csum, vpf) + if SoftwareFileCheck.filecheck_passed(fc): continue + failed.append(filecheck2model( + vpkg, vpf.filename, env, keys_only=True, fc=fc)) + out_params = [ pywbem.CIMParameter('Failed', type='reference', + value=failed) ] + return ( getattr(self.Values.CheckIntegrity, + 'Pass' if len(failed) == 0 else 'Not_passed') + , out_params ) + + def cim_method_update(self, env, object_name, + param_epoch=None, + param_release=None, + param_version=None): + """Implements LMI_SoftwareInstalledPackage.Update() + + Updates the package to latest version. When any of "version" or + "release" argument is given, install only matching available + package. Otherwise try to update to newest package available. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method Update() + should be invoked. + param_release -- The input parameter release (type unicode) + Specify particular release of package to update to. Update to + newest, when empty + param_version -- The input parameter version (type unicode) + Specify particular version of package to update to. Update to + newest, when empty + + Returns a two-tuple containing the return value ( + type pywbem.Uint16 self.Values.Update) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Installed -- (type REF (pywbem.CIMInstanceName( + classname='LMI_SoftwareInstalledPackage', ...)) + The reference to newly installed package, if installation was + successful. Otherwise reference to current package. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, + unrecognized or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the target CIM Class or instance does not + exist in the specified namespace) + CIM_ERR_METHOD_NOT_AVAILABLE (the CIM Server is unable to honor + the invocation request) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.cim_method_update()' \ + % self.__class__.__name__) + + check_computer_system_op(env, object_name['System']) + + with YumDB.getInstance(env) as yb: + 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"])) + + pl = yb.doPackageLists('all', showdups=True) + exact,_,_ = yum.packages.parsePackages( + itertools.chain(pl.available, pl.installed), + [orig.name]) + # NOTE: available ∩ installed = ∅ + exact = sorted(exact, key=lambda a:a.evra) + + try: + pkg = [ p for p in 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=pkg2model(env, pkg, True))] + if orig.evra == pkg.evra: + logger.log_info('already up to date') + return (self.Values.Update.Already_newest, out_params) + + yb.update(update_to=True, + name=pkg.name, + epoch=pkg.epoch, + version=pkg.version, + release=pkg.release) + yb.buildTransaction() + yb.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 + +## get_providers() for associating CIM Class Name to python provider class name + +def get_providers(env): + lmi_softwareinstalledpackage_prov = LMI_SoftwareInstalledPackage(env) + return {'LMI_SoftwareInstalledPackage': lmi_softwareinstalledpackage_prov} diff --git a/src/software/providers/LMI_SoftwarePackage.py b/src/software/providers/LMI_SoftwarePackage.py new file mode 100644 index 0000000..9ef3266 --- /dev/null +++ b/src/software/providers/LMI_SoftwarePackage.py @@ -0,0 +1,518 @@ +# -*- 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/>. + +"""Python Provider for LMI_SoftwarePackage + +Instruments the CIM class LMI_SoftwarePackage + +""" + +import itertools +import datetime +import pywbem +from pywbem.cim_provider2 import CIMProvider2 +from util.common import * + +pkg2model = SoftwarePackage.pkg2model_wrapper('root/cimv2', "LMI_SoftwarePackage") + +class LMI_SoftwarePackage(CIMProvider2): + """Instrument the CIM class LMI_SoftwarePackage + + RPM package installed on particular computer system with YUM (The + Yellowdog Updated, Modified) package manager. + + """ + + def __init__ (self, env): + logger = env.get_logger() + logger.log_debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + def get_instance(self, env, model): + """Return an instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + model -- A template of the pywbem.CIMInstance to be returned. The + key properties are set on this instance to correspond to the + instanceName that was requested. The properties of the model + are already filtered according to the PropertyList from the + request. Only properties present in the model need to be + given values. If you prefer, you can set all of the + values, and the instance will be filtered for you. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM + Instance does not exist in the specified namespace) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + 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 pkg2model(env, pkg, keys_only=False, model=model) + + def enum_instances(self, env, model, keys_only): + """Enumerate instances. + + The WBEM operations EnumerateInstances and EnumerateInstanceNames + are both mapped to this method. + This method is a python generator + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + model -- A template of the pywbem.CIMInstances to be generated. + The properties of the model are already filtered according to + the PropertyList from the request. Only properties present in + the model need to be given values. If you prefer, you can + always set all of the values, and the instance will be filtered + for you. + keys_only -- A boolean. True if only the key properties should be + set on the generated instances. + + Possible Errors: + 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. + model.path.update({'TargetOperatingSystem': None, 'Version': None, + 'SoftwareElementState': None, 'Name': None, + 'SoftwareElementID': None}) + + with YumDB.getInstance(env) as yb: + # get all packages + pl = yb.doPackageLists('all', showdups=True) + pl = itertools.chain(pl.installed, pl.available) + # NOTE: available ∩ installed = ∅ + for pkg in sorted(pl, key=lambda a:a.evra): + yield pkg2model(env, pkg, keys_only, model) + + def set_instance(self, env, instance, modify_existing): + """Return a newly created or modified instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + instance -- The new pywbem.CIMInstance. If modifying an existing + instance, the properties on this instance have been filtered by + the PropertyList from the request. + modify_existing -- True if ModifyInstance, False if CreateInstance + + Return the new instance. The keys must be set on the new instance. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_ALREADY_EXISTS (the CIM Instance already exists -- only + valid if modify_existing is False, indicating that the operation + was CreateInstance) + CIM_ERR_NOT_FOUND (the CIM Instance does not exist -- only valid + if modify_existing is True, indicating that the operation + was ModifyInstance) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.set_instance()' \ + % self.__class__.__name__) + # TODO create or modify the instance + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) # Remove to implement + return instance + + def delete_instance(self, env, instance_name): + """Delete an instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + instance_name -- A pywbem.CIMInstanceName specifying the instance + to delete. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_NAMESPACE + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_INVALID_CLASS (the CIM Class does not exist in the specified + namespace) + CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM + Instance does not exist in the specified namespace) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.delete_instance()' \ + % self.__class__.__name__) + + # TODO delete the resource + # Remove to implement + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + def cim_method_install(self, env, object_name): + """Implements LMI_SoftwarePackage.Install() + + Will install available package. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + 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) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Installed -- (type REF (pywbem.CIMInstanceName( + classname='LMI_SoftwareInstalledPackage', ...)) + The reference to newly installed package, if installation was + successful. Null otherwise. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, + unrecognized or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the target CIM Class or instance does not + exist in the specified namespace) + CIM_ERR_METHOD_NOT_AVAILABLE (the CIM Server is unable to honor + the invocation request) + 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 yb: + # get available packages + pkg = SoftwarePackage.object_path2pkg_search(env, object_name) + out_params = [ pywbem.CIMParameter('Installed', type='reference') ] + if isinstance(pkg, yum.rpmsack.RPMInstalledPackage): + out_params[0].value = pkg2model(env, pkg, True) + return (self.Values.Install.Already_installed, out_params) + + logger.log_info('installing package {}'.format(pkg.nevra)) + # install + yb.install(pkg) + yb.buildTransaction() + yb.processTransaction() + logger.log_info('package installed'.format(pkg.nevra)) + + out_params[0].value = pkg2model(env, pkg, True, object_name) + out_params[0].value['SoftwareElementState'] = \ + self.Values.SoftwareElementState.Executable + return (self.Values.Install.Successful_installation, out_params) + + def cim_method_remove(self, env, object_name): + """Implements LMI_SoftwarePackage.Remove() + + Will uninstall installed package. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + 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) + and a list of CIMParameter objects representing the output parameters + + Output parameters: none + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, + unrecognized or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the target CIM Class or instance does not + exist in the specified namespace) + CIM_ERR_METHOD_NOT_AVAILABLE (the CIM Server is unable to honor + the invocation request) + 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 yb: + pkg = SoftwarePackage.object_path2pkg(env, object_name, 'all') + if isinstance(pkg, yum.rpmsack.RPMInstalledPackage): + logger.log_info('removing package "%s"' % pkg.nevra) + yb.remove(pkg) + yb.buildTransaction() + yb.processTransaction() + logger.log_info('package "%s" removed' % pkg.nevra) + rval = self.Values.Remove.Successful_removal + else: + rval = self.Values.Remove.Not_installed + #rval = # TODO (type pywbem.Uint32 self.Values.Remove) + 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 + +## get_providers() for associating CIM Class Name to python provider class name + +def get_providers(env): + lmi_softwarepackage_prov = LMI_SoftwarePackage(env) + return {'LMI_SoftwarePackage': lmi_softwarepackage_prov} diff --git a/src/software/providers/LMI_SoftwarePackageFile.py b/src/software/providers/LMI_SoftwarePackageFile.py new file mode 100644 index 0000000..389dbbf --- /dev/null +++ b/src/software/providers/LMI_SoftwarePackageFile.py @@ -0,0 +1,301 @@ +# 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/>. + +"""Python Provider for LMI_SoftwarePackageFile + +Instruments the CIM class LMI_SoftwarePackageFile + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 +from LMI_SoftwareFileCheck import filecheck2model +from LMI_SoftwarePackage import pkg2model +from util.common import * + +class LMI_SoftwarePackageFile(CIMProvider2): + """Instrument the CIM class LMI_SoftwarePackageFile + + This association ties a SoftwareElement to a specific Check to validate + its state or its movement to the next state. Note that + SoftwareElements in a running state cannot transition to another + state. Therefore, the value of the Phase property is restricted to 0 + ("In-State") for SoftwareElements in the running state. + + """ + + def __init__ (self, env): + logger = env.get_logger() + logger.log_debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + def get_instance(self, env, model): + """Return an instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + model -- A template of the pywbem.CIMInstance to be returned. The + key properties are set on this instance to correspond to the + instanceName that was requested. The properties of the model + are already filtered according to the PropertyList from the + request. Only properties present in the model need to be + given values. If you prefer, you can set all of the + values, and the instance will be filtered for you. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM + Instance does not exist in the specified namespace) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.get_instance()' \ + % self.__class__.__name__) + + vpkg = SoftwareFileCheck.object_path2yumcheck(env, model['Check']) + model['Check'] = filecheck2model(vpkg, model['Check']['Name'], + env, keys_only=True) + model['Element'] = pkg2model(env, vpkg.po, keys_only=True) + return model + + def enum_instances(self, env, model, keys_only): + """Enumerate instances. + + The WBEM operations EnumerateInstances and EnumerateInstanceNames + are both mapped to this method. + This method is a python generator + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + model -- A template of the pywbem.CIMInstances to be generated. + The properties of the model are already filtered according to + the PropertyList from the request. Only properties present in + the model need to be given values. If you prefer, you can + always set all of the values, and the instance will be filtered + for you. + keys_only -- A boolean. True if only the key properties should be + set on the generated instances. + + Possible Errors: + 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. + model.path.update({'Check': None, 'Element': None}) + + while False: # TODO more instances? + # TODO fetch system resource + # Key properties + #model['Check'] = pywbem.CIMInstanceName(classname='LMI_SoftwareFileCheck', ...) # TODO (type = REF (pywbem.CIMInstanceName(classname='LMI_SoftwareFileCheck', ...)) + #model['Element'] = pywbem.CIMInstanceName(classname='LMI_SoftwarePackage', ...) # TODO (type = REF (pywbem.CIMInstanceName(classname='LMI_SoftwarePackage', ...)) + if keys_only: + yield model + else: + try: + yield self.get_instance(env, model) + except pywbem.CIMError, (num, msg): + if num not in (pywbem.CIM_ERR_NOT_FOUND, + pywbem.CIM_ERR_ACCESS_DENIED): + raise + + def set_instance(self, env, instance, modify_existing): + """Return a newly created or modified instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + instance -- The new pywbem.CIMInstance. If modifying an existing + instance, the properties on this instance have been filtered by + the PropertyList from the request. + modify_existing -- True if ModifyInstance, False if CreateInstance + + Return the new instance. The keys must be set on the new instance. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_ALREADY_EXISTS (the CIM Instance already exists -- only + valid if modify_existing is False, indicating that the operation + was CreateInstance) + CIM_ERR_NOT_FOUND (the CIM Instance does not exist -- only valid + if modify_existing is True, indicating that the operation + was ModifyInstance) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.set_instance()' \ + % self.__class__.__name__) + # TODO create or modify the instance + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) # Remove to implement + return instance + + def delete_instance(self, env, instance_name): + """Delete an instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + instance_name -- A pywbem.CIMInstanceName specifying the instance + to delete. + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_NAMESPACE + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_INVALID_CLASS (the CIM Class does not exist in the specified + namespace) + CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM + Instance does not exist in the specified namespace) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.delete_instance()' \ + % self.__class__.__name__) + + # TODO delete the resource + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) # Remove to implement + + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + All four association-related operations (Associators, AssociatorNames, + References, ReferenceNames) are mapped to this method. + This method is a python generator + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName that defines the source + CIM Object whose associated Objects are to be returned. + model -- A template pywbem.CIMInstance to serve as a model + of the objects to be returned. Only properties present on this + model need to be set. + result_class_name -- If not empty, this string acts as a filter on + the returned set of Instances by mandating that each returned + Instances MUST represent an association between object_name + and an Instance of a Class whose name matches this parameter + or a subclass. + role -- If not empty, MUST be a valid Property name. It acts as a + filter on the returned set of Instances by mandating that each + returned Instance MUST refer to object_name via a Property + whose name matches the value of this parameter. + result_role -- If not empty, MUST be a valid Property name. It acts + as a filter on the returned set of Instances by mandating that + each returned Instance MUST represent associations of + object_name to other Instances, where the other Instances play + the specified result_role in the association (i.e. the + name of the Property in the Association Class that refers to + the Object related to object_name MUST match the value of this + parameter). + keys_only -- A boolean. True if only the key properties should be + set on the generated instances. + + The following diagram may be helpful in understanding the role, + result_role, and result_class_name parameters. + +------------------------+ +-------------------+ + | object_name.classname | | result_class_name | + | ~~~~~~~~~~~~~~~~~~~~~ | | ~~~~~~~~~~~~~~~~~ | + +------------------------+ +-------------------+ + | +-----------------------------------+ | + | | [Association] model.classname | | + | object_name | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | + +--------------+ object_name.classname REF role | | + (CIMInstanceName) | result_class_name REF result_role +------+ + | |(CIMInstanceName) + +-----------------------------------+ + + Possible Errors: + CIM_ERR_ACCESS_DENIED + CIM_ERR_NOT_SUPPORTED + CIM_ERR_INVALID_NAMESPACE + CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized + or otherwise incorrect parameters) + CIM_ERR_FAILED (some other unspecified error occurred) + + """ + + logger = env.get_logger() + logger.log_debug('Entering %s.references()' \ + % self.__class__.__name__) + ch = env.get_cimom_handle() + + # 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. + model.path.update({'Check': None, 'Element': None}) + + with YumDB.getInstance(env): + if ( (not role or role.lower() == 'element') + and ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='LMI_SoftwarePackage')): + filecheck_model = pywbem.CIMInstanceName( + classname='LMI_SoftwareFileCheck', + namespace="root/cimv2", + host=model.path.host) + model['Element'] = object_name + + pkg = SoftwarePackage.object_path2pkg(env, object_name) + vpkg = yum.packages._RPMVerifyPackage( + pkg, pkg.hdr.fiFromHeader(), + SoftwareFileCheck.pkg_checksum_type(pkg), [], True) + for fc in vpkg: + model['Check'] = filecheck2model( + vpkg, fc.filename, env, keys_only=True, + model=filecheck_model) + yield model + + if ( (not role or role.lower() == 'check') + and ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='LMI_SoftwareFileCheck')): + model['Check'] = object_name + + vpkg = SoftwareFileCheck.object_path2yumcheck(env, object_name) + model['Element'] = pkg2model( + env, vpkg.po, keys_only=True) + yield model + + class Values(object): + class Phase(object): + In_State = pywbem.Uint16(0) + Next_State = pywbem.Uint16(1) + +## end of class LMI_SoftwarePackageFileProvider + +## get_providers() for associating CIM Class Name to python provider class name + +def get_providers(env): + lmi_softwarepackagefile_prov = LMI_SoftwarePackageFile(env) + return {'LMI_SoftwarePackageFile': lmi_softwarepackagefile_prov} diff --git a/src/software/providers/__init__.py b/src/software/providers/__init__.py new file mode 100644 index 0000000..452bf25 --- /dev/null +++ b/src/software/providers/__init__.py @@ -0,0 +1,16 @@ +# 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/>. diff --git a/src/software/providers/util/__init__.py b/src/software/providers/util/__init__.py new file mode 100644 index 0000000..fddeea6 --- /dev/null +++ b/src/software/providers/util/__init__.py @@ -0,0 +1,17 @@ +# 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/>. + diff --git a/src/software/providers/util/common.py b/src/software/providers/util/common.py new file mode 100644 index 0000000..4190d41 --- /dev/null +++ b/src/software/providers/util/common.py @@ -0,0 +1,804 @@ +# -*- 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/>. + +"""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 +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): + + ignoreSubsequent = True + + def __init__(self, env, *args, **kwargs): + 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 self._db_mtime < os.stat(rpmdb_path).st_mtime + + def is_locked(self): + return self._yum._lockfile is not None + + def update_db(self): + 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(): + 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(): + return pywbem.CIMInstanceName( + classname='CIM_ComputerSystem', + keybindings={ + "CreationClassName": "CIM_ComputerSystem" + , "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): + 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() + ch = env.get_cimom_handle() + if not ch.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 ch.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 a in ('evra', 'nevra', 'name', 'epoch', 'version', 'release', 'arch'): + v = kwargs.get(a, None) + if v and getattr(pkg, a) != v: + return False + return True + +class SoftwarePackage: + """ + Just a namespace for common function related to SoftwarePackage provider. + """ + + @staticmethod + def parse_group(group, env): + try: + res = \ + { "amusements/games" : 3 + , "amusements/graphics" : 4 + , "applications/archiving" : 5 + , "applications/communications" : 6 + , "applications/databases" : 7 + , "applications/editors" : 8 + , "applications/emulators" : 9 + , "applications/engineering" : 10 + , "applications/file" : 11 + , "applications/internet" : 12 + , "applications/multimedia" : 13 + , "applications/productivity" : 14 + , "applications/publishing" : 15 + , "applications/system" : 16 + , "applications/text" : 17 + , "development/build tools" : 18 + , "development/debug" : 19 + , "development/debuggers" : 20 + , "development/documentation" : 21 + , "development/java" : 22 + , "development/languages" : 23 + , "development/libraries" : 24 + , "development/libraries/java" : 25 + , "development/system" : 26 + , "development/tools" : 27 + , "documentation" : 28 + , "internet/www/dynamic content" : 29 + , "system/libraries" : 30 + , "system environment/base" : 31 + , "system environment/daemons" : 32 + , "system environment/kernel" : 33 + , "system environment/libraries" : 34 + , "system environment/shells" : 35 + , "text processing/markup/xml" : 36 + , "user interface/desktops" : 37 + , "user interface/x" : 38 + , "user interface/x hardware support" : 39 + , "utilities" : 40 + }[group.lower()] + except KeyError: + logger = env.get_logger() + if not group or group.lower() == "unspecified": + logger.log_info("unspecified group '{}'".format(group)) + res = 2 + else: + logger.log_error("failed to parse group '{}'".format(group)) + res = 0 + return pywbem.Uint16(res) + + @staticmethod + def object_path2pkg(env, op, package_list='installed'): + """ + @param op 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(op, pywbem.CIMInstanceName): + raise TypeError("op 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) + + tos = get_target_operating_system()[0] + if ( not op['Name'] or not op['SoftwareElementID'] + or not op['SoftwareElementID'].startswith(op['Name']) + or op['SoftwareElementID'].find(op['Version']) == -1): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Wrong keys.") +# if op['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(op['TargetOperatingSystem']): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Wrong target operating system.") + if not op['Name'] or not op['Version']: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'Both "Name" and "Version" must be given') + m = re_nevra.match(op['SoftwareElementID']) + if not m: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Wrong SotwareElementID. Expected valid nevra" + " (name-[epoch:]version-release.arch).") + if op['Version'] != m.group('ver'): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Version does not match version part in SoftwareElementID.") + evra = "{}:{}-{}.{}".format(*( + (m.group(k) if k != "epoch" or m.group(k) else "0") + for k in ("epoch", 'ver', 'rel', 'arch'))) + with YumDB.getInstance(env) as yb: + pl = yb.doPackageLists(package_list, + showdups=package_list != 'installed') + if package_list != 'all': + pl = getattr(pl, package_list) + else: + # NOTE: available ∩ installed = ∅ + pl = itertools.chain(pl.available, pl.installed) + exact,_,_ = yum.packages.parsePackages(pl, [op['Name']]) + for pkg in yum.misc.unique(exact): + if pkg.evra == evra: break + else: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No matching package found.") + return pkg + + @staticmethod + def object_path2pkg_search(env, op): + """ + 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 op 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(op, pywbem.CIMInstance): + def _get_key(k): + v = op.properties.get(k, None) + if isinstance(v, pywbem.CIMProperty): return v.value + if v is not None: return v + logger.log_error('missing key "{}" in inst.props'.format(k)) + return op.path[k] if k in op.path else None + elif isinstance(op, pywbem.CIMInstanceName): + _get_key = lambda k: op[k] if k in op else None + else: + raise TypeError("op must be either CIMInstance or CIMInstanceName") + + # parse and check arguments + match_props = {} # args for match_pkg + if _get_key('SoftwareElementID'): + m = re_nevra.match(_get_key('SoftwareElementID')) + if not m: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "SoftwareElementID could not be parsed.") + match_props['nevra'] = _get_key('SoftwareElementID') + match_props['name'] = m.group('name') + else: + for matchattr, instattr in ( + ('name', 'name'), ('epoch', 'epoch'), ('version', 'ver'), + ('release', 'rel'), ('arch', 'arch')): + if _get_key(matchattr): + match_props[matchattr] = _get_key(matchattr) + + 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 and not 'nevra' in match_props: + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, + "Missing either Name or SoftwareElementID property.") + + # get available packages + pl = YumDB.getInstance(env).doPackageLists('all', showdups=True) + # NOTE: available ∩ installed = ∅ + exact,_,_ = yum.packages.parsePackages( + itertools.chain(pl.available, pl.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) ] + , key=lambda a: a.evra) + 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_wrapper(namespace, classname): + """ + @return a function that transforms YumAvailablePackage object + to CIMInstanceName object + """ + + tos = pywbem.Uint16(get_target_operating_system()[0]) + + 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(classname, namespace=namespace) + if not keys_only: + model = pywbem.CIMInstance(classname, path=model) + if isinstance(model, pywbem.CIMInstance): + def _set_key(k, v): + model[k] = v + model.path[k] = v + 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', tos) + _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 + + return pkg2model + +class SoftwareFileCheck: + """ + Just a namespace for functions related to FileCheck provider. + """ + + passed_flags_descriptions = ( + "Existence", + "File Type", + "File Size", + "File Mode", + "Checksum", + "Device major/minor number", + "Symlink Target", + "User Ownership", "Group Ownership", + "Modify Time") + + checksumtype_str2num = dict((v, k) for (k, v) 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 self: # ensure, that _yum is inited + return SoftwareFileCheck.checksumtype_str2pywbem( + pkg.yumdb_info.checksum_type) + + @staticmethod + def checksumtype_num2hash(csumt): + return getattr(hashlib, yum.constants.RPM_CHECKSUM_TYPES[csumt]) + + @staticmethod + def checksumtype_str2pywbem(alg): + try: + res = SoftwareFileCheck.checksumtype_str2num[alg.lower()] + except KeyError: res = 0 + return pywbem.Uint16(res) + + @staticmethod + def filetype_str2pywbem(ft): + try: + return pywbem.Uint16( + { 'file' : 1 + , 'directory' : 2 + , 'symlink' : 3 + , 'fifo' : 4 + , 'character device' : 5 + , 'block device' : 6 + }[ft]) + except KeyError: + return pywbem.Uint16(0) + + @staticmethod + def filetype_mode2pywbem(mode): + 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 + """ + if mode is None: return None + flags = [] + for i, c 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 c & 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 h in hashers: h.update(buf) + buf = afile.read(blocksize) + return [ h.hexdigest() for h 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()] + 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 f: + rslts = SoftwareFileCheck.hashfile(f, hashers) + except (OSError, IOError) as e: + env.get_logger().log_error("could not open file \"%s\"" + " for reading: %s" % (file_path, e)) + return None, None + return (rslts[0], rslts[1] if len(rslts) > 1 else rslts[0]*2) + + @staticmethod + def object_path2yumcheck(env, op): + """ + @return instance of yum.packages._RPMVerifyPackage + this object holds RPMInstalledPackage under its po attribute + """ + if not isinstance(op, pywbem.CIMInstanceName): + raise TypeError("op must be instance of CIMInstanceName, " + "not \"%s\"" % op.__class__.__name__) + log = env.get_logger() + + tos = get_target_operating_system()[0] + if ( not op['Name'] or not op['SoftwareElementID'] + or not op['CheckID'] + or not op['CheckID'].endswith('#'+op['Name']) + or op['SoftwareElementID'].find(op['Version']) == -1): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Wrong keys.") + if op['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(op['TargetOperatingSystem']): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Wrong target operating system.") + if not op['Name'] or not op['Version']: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'Both "Name" and "Version" must be given') + m = re_nevra.match(op['SoftwareElementID']) + if not m: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Wrong SotwareElementID. Expected valid nevra" + " (name-epoch:version-release.arch).") + if op['Version'] != m.group('ver'): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Version does not match version part in SoftwareElementID.") + + evra = "{}:{}-{}.{}".format(*( + (m.group(k) if k != "epoch" or m.group(k) else "0") + for k in ("epoch", 'ver', 'rel', 'arch'))) + with YumDB.getInstance(env) as yb: + pl = yb.doPackageLists('installed') + exact, matched, unmatched = yum.packages.parsePackages( + pl.installed, [m.group('name')]) + exact = yum.misc.unique(exact) + for pkg in exact: + if pkg.evra == evra: break + else: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No matching package installed.") + + fi = pkg.hdr.fiFromHeader() + vpkg = yum.packages._RPMVerifyPackage(pkg, fi, + SoftwareFileCheck.pkg_checksum_type(pkg), [], True) + if not op['Name'] in vpkg: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "File not found in RPM package.") + return vpkg + + """ + 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, ' + '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, + "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["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(fc): + if not isinstance(fc, SoftwareFileCheck.FileCheck): + raise TypeError("fc must be an instance of FileCheck") + return ( fc.exists + and all( v[0] == v[1] + for k, v in fc._asdict().items() if ( + isinstance(v, tuple) + and ( k != "last_modification_time" + or fc.file_type[0] == \ + SoftwareFileCheck.filetype_str2pywbem("file") + )))) + + @staticmethod + def filecheck_wrapper(namespace, classname): + + def _filecheck2model_flags(fc): + flags = [] + for k, v in fc._asdict().items(): + if isinstance(v, tuple): + if ( k != "last_modification_time" + or fc.file_type[0] == SoftwareFileCheck.filetype_str2pywbem( + 'file')): + # last_modification_time check is valid only for + # regular files + flag = fc.exists and v[0] == v[1] + else: + flag = True + flags.append(flag) + elif isinstance(v, bool): + flags.append(v) + return flags + + def filecheck2model(vpkg, fn, env, keys_only=True, + model=None, fc=None): + if not isinstance(vpkg, yum.packages._RPMVerifyPackage): + raise TypeError( + "vpkg must be an instance of _RPMVerifyPackage") + if not fn in vpkg: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "File \"%s\" not found among package files" % fn) + if model is None: + model = pywbem.CIMInstanceName(classname, namespace=namespace) + if not keys_only: + model = pywbem.CIMInstance(classname, path=model) + if fc is not None: + if not isinstance(fc, SoftwareFileCheck.FileCheck): + raise TypeError("fc must be an instance of FileCheck") + pkg = vpkg.po + vpf = vpkg._files[fn] + 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: + #model['Caption'] = '' # TODO + #model['CheckMode'] = bool() # TODO + #model['CRC1'] = pywbem.Uint32() # TODO + #model['CRC2'] = pywbem.Uint32() # TODO + #model['CreateTimeStamp'] = pywbem.CIMDateTime() # TODO + #model['Description'] = '' # TODO + #model['ElementName'] = '' # TODO + #model['InstanceID'] = '' # TODO + model['FileName'] = os.path.basename(vpf.filename) + model['ChecksumType'] = csumt = \ + pywbem.Uint16(SoftwareFileCheck.pkg_checksum_type(pkg)) + if fc is None: + fc = 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'), + ('Checksum', 'checksum')): + exp, rea = getattr(fc, fattr) + if exp is not None: + model['Expected' + mattr] = exp + if rea is not None: + model[mattr] = rea + model['ExpectedFileModeFlags'] = \ + SoftwareFileCheck.mode2pywbem_flags(fc.file_mode[0]) + if fc.exists: + model['FileModeFlags'] = SoftwareFileCheck.mode2pywbem_flags( + fc.file_mode[1]) + model['FileExists'] = fc.exists + if fc.md5_checksum is not None: + model['MD5Checksum'] = fc.md5_checksum + model['PassedFlags'] = _filecheck2model_flags(fc) + model['PassedFlagsDescriptions'] = list( + SoftwareFileCheck.passed_flags_descriptions) + return model + + return filecheck2model + diff --git a/src/software/providers/util/singletonmixin.py b/src/software/providers/util/singletonmixin.py new file mode 100644 index 0000000..8051695 --- /dev/null +++ b/src/software/providers/util/singletonmixin.py @@ -0,0 +1,508 @@ +""" +A Python Singleton mixin class that makes use of some of the ideas +found at http://c2.com/cgi/wiki?PythonSingleton. Just inherit +from it and you have a singleton. No code is required in +subclasses to create singleton behavior -- inheritance from +Singleton is all that is needed. + +Singleton creation is threadsafe. + +USAGE: + +Just inherit from Singleton. If you need a constructor, include +an __init__() method in your class as you usually would. However, +if your class is S, you instantiate the singleton using S.getInstance() +instead of S(). Repeated calls to S.getInstance() return the +originally-created instance. + +For example: + +class S(Singleton): + + def __init__(self, a, b=1): + pass + +S1 = S.getInstance(1, b=3) + + +Most of the time, that's all you need to know. However, there are some +other useful behaviors. Read on for a full description: + +1) Getting the singleton: + + S.getInstance() + +returns the instance of S. If none exists, it is created. + +2) The usual idiom to construct an instance by calling the class, i.e. + + S() + +is disabled for the sake of clarity. + +For one thing, the S() syntax means instantiation, but getInstance() +usually does not cause instantiation. So the S() syntax would +be misleading. + +Because of that, if S() were allowed, a programmer who didn't +happen to notice the inheritance from Singleton (or who +wasn't fully aware of what a Singleton pattern +does) might think he was creating a new instance, +which could lead to very unexpected behavior. + +So, overall, it is felt that it is better to make things clearer +by requiring the call of a class method that is defined in +Singleton. An attempt to instantiate via S() will result +in a SingletonException being raised. + +3) Use __S.__init__() for instantiation processing, +since S.getInstance() runs S.__init__(), passing it the args it has received. + +If no data needs to be passed in at instantiation time, you don't need S.__init__(). + +4) If S.__init__(.) requires parameters, include them ONLY in the +first call to S.getInstance(). If subsequent calls have arguments, +a SingletonException is raised by default. + +If you find it more convenient for subsequent calls to be allowed to +have arguments, but for those argumentsto be ignored, just include +'ignoreSubsequent = True' in your class definition, i.e.: + + class S(Singleton): + + ignoreSubsequent = True + + def __init__(self, a, b=1): + pass + +5) For testing, it is sometimes convenient for all existing singleton +instances to be forgotten, so that new instantiations can occur. For that +reason, a forgetAllSingletons() function is included. Just call + + forgetAllSingletons() + +and it is as if no earlier instantiations have occurred. + +6) As an implementation detail, classes that inherit +from Singleton may not have their own __new__ +methods. To make sure this requirement is followed, +an exception is raised if a Singleton subclass includ +es __new__. This happens at subclass instantiation +time (by means of the MetaSingleton metaclass. + + +By Gary Robinson, grobinson@flyfi.com. No rights reserved -- +placed in the public domain -- which is only reasonable considering +how much it owes to other people's code and ideas which are in the +public domain. The idea of using a metaclass came from +a comment on Gary's blog (see +http://www.garyrobinson.net/2004/03/python_singleto.html#comments). +Other improvements came from comments and email from other +people who saw it online. (See the blog post and comments +for further credits.) + +Not guaranteed to be fit for any particular purpose. Use at your +own risk. +""" + +import threading + +class SingletonException(Exception): + pass + +_stSingletons = set() +_lockForSingletons = threading.RLock() +_lockForSingletonCreation = threading.RLock() # Ensure only one instance of each Singleton + # class is created. This is not bound to the + # individual Singleton class since we need to + # ensure that there is only one mutex for each + # Singleton class, which would require having + # a lock when setting up the Singleton class, + # which is what this is anyway. So, when any + # Singleton is created, we lock this lock and + # then we don't need to lock it again for that + # class. + +def _createSingletonInstance(cls, lstArgs, dctKwArgs): + _lockForSingletonCreation.acquire() + try: + if cls._isInstantiated(): # some other thread got here first + return + + instance = cls.__new__(cls) + try: + instance.__init__(*lstArgs, **dctKwArgs) + except TypeError, e: + if e.message.find('__init__() takes') != -1: + raise SingletonException, 'If the singleton requires __init__ args, supply them on first call to getInstance().' + else: + raise + cls.cInstance = instance + _addSingleton(cls) + finally: + _lockForSingletonCreation.release() + +def _addSingleton(cls): + _lockForSingletons.acquire() + try: + assert cls not in _stSingletons + _stSingletons.add(cls) + finally: + _lockForSingletons.release() + +def _removeSingleton(cls): + _lockForSingletons.acquire() + try: + if cls in _stSingletons: + _stSingletons.remove(cls) + finally: + _lockForSingletons.release() + +def forgetAllSingletons(): + '''This is useful in tests, since it is hard to know which singletons need to be cleared to make a test work.''' + _lockForSingletons.acquire() + try: + for cls in _stSingletons.copy(): + cls._forgetClassInstanceReferenceForTesting() + + # Might have created some Singletons in the process of tearing down. + # Try one more time - there should be a limit to this. + iNumSingletons = len(_stSingletons) + if len(_stSingletons) > 0: + for cls in _stSingletons.copy(): + cls._forgetClassInstanceReferenceForTesting() + iNumSingletons -= 1 + assert iNumSingletons == len(_stSingletons), 'Added a singleton while destroying ' + str(cls) + assert len(_stSingletons) == 0, _stSingletons + finally: + _lockForSingletons.release() + +class MetaSingleton(type): + def __new__(metaclass, strName, tupBases, dct): + if dct.has_key('__new__'): + raise SingletonException, 'Can not override __new__ in a Singleton' + return super(MetaSingleton, metaclass).__new__(metaclass, strName, tupBases, dct) + + def __call__(cls, *lstArgs, **dictArgs): + raise SingletonException, 'Singletons may only be instantiated through getInstance()' + +class Singleton(object): + __metaclass__ = MetaSingleton + + def getInstance(cls, *lstArgs, **dctKwArgs): + """ + Call this to instantiate an instance or retrieve the existing instance. + If the singleton requires args to be instantiated, include them the first + time you call getInstance. + """ + if cls._isInstantiated(): + if (lstArgs or dctKwArgs) and not hasattr(cls, 'ignoreSubsequent'): + raise SingletonException, 'Singleton already instantiated, but getInstance() called with args.' + else: + _createSingletonInstance(cls, lstArgs, dctKwArgs) + + return cls.cInstance + getInstance = classmethod(getInstance) + + def _isInstantiated(cls): + # Don't use hasattr(cls, 'cInstance'), because that screws things up if there is a singleton that + # extends another singleton. hasattr looks in the base class if it doesn't find in subclass. + return 'cInstance' in cls.__dict__ + _isInstantiated = classmethod(_isInstantiated) + + # This can be handy for public use also + isInstantiated = _isInstantiated + + def _forgetClassInstanceReferenceForTesting(cls): + """ + This is designed for convenience in testing -- sometimes you + want to get rid of a singleton during test code to see what + happens when you call getInstance() under a new situation. + + To really delete the object, all external references to it + also need to be deleted. + """ + try: + if hasattr(cls.cInstance, '_prepareToForgetSingleton'): + # tell instance to release anything it might be holding onto. + cls.cInstance._prepareToForgetSingleton() + del cls.cInstance + _removeSingleton(cls) + except AttributeError: + # run up the chain of base classes until we find the one that has the instance + # and then delete it there + for baseClass in cls.__bases__: + if issubclass(baseClass, Singleton): + baseClass._forgetClassInstanceReferenceForTesting() + _forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting) + + +if __name__ == '__main__': + + import unittest + import time + + class singletonmixin_Public_TestCase(unittest.TestCase): + def testReturnsSameObject(self): + """ + Demonstrates normal use -- just call getInstance and it returns a singleton instance + """ + + class A(Singleton): + def __init__(self): + super(A, self).__init__() + + a1 = A.getInstance() + a2 = A.getInstance() + self.assertEquals(id(a1), id(a2)) + + def testInstantiateWithMultiArgConstructor(self): + """ + If the singleton needs args to construct, include them in the first + call to get instances. + """ + + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + + b1 = B.getInstance('arg1 value', 'arg2 value') + b2 = B.getInstance() + self.assertEquals(b1.arg1, 'arg1 value') + self.assertEquals(b1.arg2, 'arg2 value') + self.assertEquals(id(b1), id(b2)) + + def testInstantiateWithKeywordArg(self): + + class B(Singleton): + + def __init__(self, arg1=5): + super(B, self).__init__() + self.arg1 = arg1 + + b1 = B.getInstance('arg1 value') + b2 = B.getInstance() + self.assertEquals(b1.arg1, 'arg1 value') + self.assertEquals(id(b1), id(b2)) + + def testTryToInstantiateWithoutNeededArgs(self): + + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + + self.assertRaises(SingletonException, B.getInstance) + + def testPassTypeErrorIfAllArgsThere(self): + """ + Make sure the test for capturing missing args doesn't interfere with a normal TypeError. + """ + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + raise TypeError, 'some type error' + + self.assertRaises(TypeError, B.getInstance, 1, 2) + + def testTryToInstantiateWithoutGetInstance(self): + """ + Demonstrates that singletons can ONLY be instantiated through + getInstance, as long as they call Singleton.__init__ during construction. + + If this check is not required, you don't need to call Singleton.__init__(). + """ + + class A(Singleton): + def __init__(self): + super(A, self).__init__() + + self.assertRaises(SingletonException, A) + + def testDontAllowNew(self): + + def instantiatedAnIllegalClass(): + class A(Singleton): + def __init__(self): + super(A, self).__init__() + + def __new__(metaclass, strName, tupBases, dct): + return super(MetaSingleton, metaclass).__new__(metaclass, strName, tupBases, dct) + + self.assertRaises(SingletonException, instantiatedAnIllegalClass) + + + def testDontAllowArgsAfterConstruction(self): + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + + B.getInstance('arg1 value', 'arg2 value') + self.assertRaises(SingletonException, B, 'arg1 value', 'arg2 value') + + def test_forgetClassInstanceReferenceForTesting(self): + class A(Singleton): + def __init__(self): + super(A, self).__init__() + class B(A): + def __init__(self): + super(B, self).__init__() + + # check that changing the class after forgetting the instance produces + # an instance of the new class + a = A.getInstance() + assert a.__class__.__name__ == 'A' + A._forgetClassInstanceReferenceForTesting() + b = B.getInstance() + assert b.__class__.__name__ == 'B' + + # check that invoking the 'forget' on a subclass still deletes the instance + B._forgetClassInstanceReferenceForTesting() + a = A.getInstance() + B._forgetClassInstanceReferenceForTesting() + b = B.getInstance() + assert b.__class__.__name__ == 'B' + + def test_forgetAllSingletons(self): + # Should work if there are no singletons + forgetAllSingletons() + + class A(Singleton): + ciInitCount = 0 + def __init__(self): + super(A, self).__init__() + A.ciInitCount += 1 + + A.getInstance() + self.assertEqual(A.ciInitCount, 1) + + A.getInstance() + self.assertEqual(A.ciInitCount, 1) + + forgetAllSingletons() + A.getInstance() + self.assertEqual(A.ciInitCount, 2) + + def test_threadedCreation(self): + # Check that only one Singleton is created even if multiple + # threads try at the same time. If fails, would see assert in _addSingleton + class Test_Singleton(Singleton): + def __init__(self): + super(Test_Singleton, self).__init__() + + class Test_SingletonThread(threading.Thread): + def __init__(self, fTargetTime): + super(Test_SingletonThread, self).__init__() + self._fTargetTime = fTargetTime + self._eException = None + + def run(self): + try: + fSleepTime = self._fTargetTime - time.time() + if fSleepTime > 0: + time.sleep(fSleepTime) + Test_Singleton.getInstance() + except Exception, e: + self._eException = e + + fTargetTime = time.time() + 0.1 + lstThreads = [] + for _ in xrange(100): + t = Test_SingletonThread(fTargetTime) + t.start() + lstThreads.append(t) + eException = None + for t in lstThreads: + t.join() + if t._eException and not eException: + eException = t._eException + if eException: + raise eException + + def testNoInit(self): + """ + Demonstrates use with a class not defining __init__ + """ + + class A(Singleton): + pass + + #INTENTIONALLY UNDEFINED: + #def __init__(self): + # super(A, self).__init__() + + A.getInstance() #Make sure no exception is raised + + def testMultipleGetInstancesWithArgs(self): + + class A(Singleton): + + ignoreSubsequent = True + + def __init__(self, a, b=1): + pass + + a1 = A.getInstance(1) + a2 = A.getInstance(2) # ignores the second call because of ignoreSubsequent + + class B(Singleton): + + def __init__(self, a, b=1): + pass + + b1 = B.getInstance(1) + self.assertRaises(SingletonException, B.getInstance, 2) # No ignoreSubsequent included + + class C(Singleton): + + def __init__(self, a=1): + pass + + c1 = C.getInstance(a=1) + self.assertRaises(SingletonException, C.getInstance, a=2) # No ignoreSubsequent included + + def testInheritance(self): + """ + It's sometimes said that you can't subclass a singleton (see, for instance, + http://steve.yegge.googlepages.com/singleton-considered-stupid point e). This + test shows that at least rudimentary subclassing works fine for us. + """ + + class A(Singleton): + + def setX(self, x): + self.x = x + + def setZ(self, z): + raise NotImplementedError + + class B(A): + + def setX(self, x): + self.x = -x + + def setY(self, y): + self.y = y + + a = A.getInstance() + a.setX(5) + b = B.getInstance() + b.setX(5) + b.setY(50) + self.assertEqual((a.x, b.x, b.y), (5, -5, 50)) + self.assertRaises(AttributeError, eval, 'a.setY', {}, locals()) + self.assertRaises(NotImplementedError, b.setZ, 500) + + unittest.main() + diff --git a/src/software/setup.py b/src/software/setup.py new file mode 100644 index 0000000..4823b59 --- /dev/null +++ b/src/software/setup.py @@ -0,0 +1,16 @@ +from distutils.core import setup +setup( + name='cura-yum', + description='Software Management providers', + author='Michal Minar', + author_email='miminar@redhat.com', + url='https://fedorahosted.org/cura/', + version='0.3', + package_dir={'cura.yum': 'providers'}, + packages=['cura.yum', 'cura.yum.util'], + classifiers=[ + 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', + 'Operating System :: POSIX :: Linux', + 'Topic :: System :: Systems Administration', + ] + ) diff --git a/src/software/test/README b/src/software/test/README new file mode 100644 index 0000000..d60d1e8 --- /dev/null +++ b/src/software/test/README @@ -0,0 +1,5 @@ +Tests must be run as root on machine with sfcbd broker with +test dir as CWD. File common.py contains connection settings and +also test data. +This directory should contain all rpm packages, that will take part in testing +to speed it up. These packages are listed in common.py file. diff --git a/src/software/test/common.py b/src/software/test/common.py new file mode 100644 index 0000000..d88b2bb --- /dev/null +++ b/src/software/test/common.py @@ -0,0 +1,109 @@ +from collections import namedtuple +import os +import pywbem +import re +import subprocess +import unittest + +SCHEMA="http" +HOSTNAME="localhost" +PORT=5988 +USER='' +PASSWORD='' + +Package = namedtuple("Package", + "name, epoch, ver, rel, arch, " + "updatable, up_epoch, up_ver, up_rel") +# This is a database of packages used in tests. If you modify this, please +# ensure, that all corresponding rpm packages are present in test directory. +packages = [ Package(*args) for args in + ( ( "python-xlib-doc", "0", "0.15", "0.6.rc1.fc17", "noarch", False + , None, None, None) + , ( "slv2", "0", "0.6.6", "8.fc17", "x86_64", True + , "0", "0.6.6", "9.fc17") + ) ] + +pkg_files = ( + ( "bash-completion-1:2.0-1.fc17.noarch", + ( "/usr/share/bash-completion/completions/vgs" # symlink + , "/usr/share/doc/bash-completion-2.0/README" # file + , "/usr/share/doc/bash-completion-2.0" # directory + ) + , ) + , ) + +re_nevra = re.compile(r'^(?P<name>.+)-(?P<evra>(?P<epoch>\d+):(?P<ver>[^-]+)' + r'-(?P<rel>.+)\.(?P<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 remove_pkg(name, *args): + subprocess.call(["rpm", "--quiet"] + list(args) + ["-e", name]) + +def install_pkg(*args, **kwargs): + if len(args) > 1 or len(kwargs) > 0: + if kwargs.has_key('nevra'): + m = re_nevra.match(kwargs['nevra']) + args = [ m.group(k) for k in ( + 'name', 'epoch', 'ver', 'rel', 'arch') ] + nevra = make_nevra(*args, with_epoch="NEVER") + rpm_name = nevra + ".rpm" + if os.path.exists(rpm_name): + subprocess.call(["rpm", "--quiet", "-i", rpm_name]) + return + subprocess.call(["yum", "-q", "-y", "install", args[0]]) + +def is_installed(*args, **kwargs): + if len(args) == 1: + return subprocess.call(["rpm", "--quiet", "-q", args[0]]) == 0 + else: + if kwargs.has_key("nevra"): + nevra = kwargs["nevra"] + name = re_nevra.match(nevra).group('name') + else: + nevra = make_nevra(*args) + name = args[0] + try: + out = subprocess.check_output( + ["rpm", "-q", "--qf", "%{NEVRA}", name]) + return out == nevra + except subprocess.CalledProcessError: + return False + +def verify_pkg(name): + return subprocess.call(["rpm", "--quiet", "-Va", name]) == 0 + +class SoftwareBaseTestCase(unittest.TestCase): + + def setUp(self): + unittest.TestCase.setUp(self) + self.url = "%s://%s:%d" % (SCHEMA, HOSTNAME, PORT) + self.conn = pywbem.WBEMConnection(self.url, (USER, PASSWORD)) + self.op = pywbem.CIMInstanceName( + namespace="root/cimv2", classname=self.CLASS_NAME) + + def tearDown(self): + del self.conn + + def assertIsSubclass(self, cls, base_cls): + if not isinstance(cls, basestring): + raise TypeError("cls must be a string") + if not isinstance(base_cls, basestring): + raise TypeError("base_cls must be a string") + return self.assertTrue(pywbem.is_subclass(self.conn, + "root/cimv2", base_cls, cls)) + diff --git a/src/software/test/test_software_file_check.py b/src/software/test/test_software_file_check.py new file mode 100755 index 0000000..66b2452 --- /dev/null +++ b/src/software/test/test_software_file_check.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python + +from common import * +import hashlib +import shutil +import socket +import stat + +re_checksum = re.compile(r'^([0-9a-fA-F]+)\s+.*') + +PassedFlags = namedtuple("PassedFlags", + "exists, type, size, mode, checksum, dev, ltarget, uid, gid, mtime") + +class TestSoftwareInstalledPackage(SoftwareBaseTestCase): + + CLASS_NAME = "LMI_SoftwareFileCheck" + KEYS = ( "CheckID", "Name", "SoftwareElementID", "SoftwareElementState" + , "TargetOperatingSystem", "Version") + + hash_num2algo = { + 1 : hashlib.md5, + 2 : hashlib.sha1, + 8 : hashlib.sha256, + 9 : hashlib.sha384, + 10 : hashlib.sha512, + 11 : hashlib.sha224 + } + + hash_num2cmd = { + 1 : "md5sum", + 2 : "sha1sum", + 8 : "sha256sum", + 9 : "sha384sum", + 10 : "sha512sum", + 11 : "sha224sum" + } + + hash_num2length = { + 1 : 32, + 2 : 40, + 8 : 64, + 9 : 96, + 10 : 128, + 11 : 56 + } + + def make_op(self, nevra, filename): + op = self.op.copy() + m = re_nevra.match(nevra) + name, version = [ m.group(k) for k in ("name", "ver") ] + op["Name"] = filename + op["Version"] = version + op["CheckID"] = "%s#%s" % (name, filename) + op["SoftwareElementState"] = pywbem.Uint16(2) + op["TargetOperatingSystem"] = pywbem.Uint16(36) + op["SoftwareElementID"] = nevra + return op + + def make_checksum_str(self, csumnum, filename): + return re_checksum.match(subprocess.check_output([ + self.hash_num2cmd[csumnum], filename])).group(1).lower() + + def do_test_symlink(self, op, nevra, f, inst): + target = os.readlink(f) + stats = os.lstat(f) + + self.assertEqual(inst["FileType"], pywbem.Uint16(3)) + self.assertEqual(inst["FileUserID"], stats.st_uid) + self.assertEqual(inst["FileGroupID"], stats.st_gid) + self.assertEqual(inst["FileMode"], stats.st_mode) + self.assertEqual(inst["FileSize"], stats.st_size) + self.assertEqual(inst["LinkTarget"], target) + self.assertEqual(inst["Checksum"], + "0"*self.hash_num2length[inst["ChecksumType"]]) + self.assertEqual(inst["LastModificationTime"], + int(stats.st_mtime)) + + # modify owner + prev_user = inst["FileUserID"] + prev_mtime = inst["LastModificationTime"] + prev_pflags = PassedFlags(*inst["PassedFlags"]) + os.lchown(f, stats.st_uid + 1, -1) + inst = self.conn.GetInstance(InstanceName=inst.path) + self.assertEqual(inst["FileUserID"], inst["ExpectedFileUserID"] + 1) + self.assertEqual(inst["FileUserID"], prev_user + 1) + self.assertEqual(inst["FileGroupID"], stats.st_gid) + cur_pflags = PassedFlags(*inst["PassedFlags"]) + #self.assertGreater(inst["LastModificationTime"], prev_mtime) + + self.assertTrue(cur_pflags.exists) + self.assertTrue(cur_pflags.type) + self.assertTrue(cur_pflags.size) + self.assertTrue(cur_pflags.mode) + self.assertTrue(cur_pflags.checksum) + self.assertTrue(cur_pflags.dev) + self.assertTrue(cur_pflags.ltarget) + self.assertFalse(cur_pflags.uid) + self.assertTrue(cur_pflags.gid) + self.assertTrue(cur_pflags.mtime) + + # modify link_target + os.remove(f) + os.symlink("wrong" + "*"*len(inst["ExpectedLinkTarget"]), f) + os.lchown(f, inst["ExpectedFileUserID"], inst["ExpectedFileGroupID"]) + + inst = self.conn.GetInstance(InstanceName=inst.path) + cur_pflags = PassedFlags(*inst["PassedFlags"]) + self.assertGreater(len(inst["LinkTarget"]), + len(inst["ExpectedLinkTarget"])) + + self.assertTrue(cur_pflags.exists) + self.assertTrue(cur_pflags.type) + self.assertFalse(cur_pflags.size) + self.assertTrue(cur_pflags.mode) + self.assertTrue(cur_pflags.checksum) + self.assertTrue(cur_pflags.dev) + self.assertFalse(cur_pflags.ltarget) + self.assertTrue(cur_pflags.uid) + self.assertTrue(cur_pflags.gid) + self.assertTrue(cur_pflags.mtime) + + def do_test_directory(self, op, nevra, f, inst): + stats = os.lstat(f) + + self.assertEqual(inst["FileType"], pywbem.Uint16(2)) + self.assertEqual(inst["FileUserID"], stats.st_uid) + self.assertEqual(inst["FileGroupID"], stats.st_gid) + self.assertEqual(inst["FileMode"], stats.st_mode) + self.assertEqual(inst["FileSize"], stats.st_size) + self.assertIs(inst["LinkTarget"], None) + self.assertEqual(inst["Checksum"], + "0"*self.hash_num2length[inst["ChecksumType"]]) + self.assertEqual(inst["LastModificationTime"], + int(stats.st_mtime)) + + def do_test_file(self, op, nevra, f, inst): + stats = os.lstat(f) + + self.assertEqual(inst["FileType"], pywbem.Uint16(1)) + self.assertEqual(inst["FileUserID"], stats.st_uid) + self.assertEqual(inst["FileGroupID"], stats.st_gid) + self.assertEqual(inst["FileMode"], stats.st_mode) + self.assertEqual(inst["FileSize"], stats.st_size) + self.assertIs(inst["LinkTarget"], None) + csum = self.make_checksum_str(inst['ChecksumType'], f) + self.assertEqual(inst["Checksum"].lower(), csum) + self.assertEqual(inst["ExpectedLastModificationTime"], + inst["LastModificationTime"]) + self.assertEqual(inst["LastModificationTime"], + int(stats.st_mtime)) + + # make it longer + with open(f, "a+") as fobj: + fobj.write("data\n") + inst = self.conn.GetInstance(InstanceName=inst.path) + cur_pflags = PassedFlags(*inst["PassedFlags"]) + self.assertGreater(inst["FileSize"], inst["ExpectedFileSize"]) + self.assertGreater(inst["LastModificationTime"], + inst["ExpectedLastModificationTime"]) + + self.assertTrue(cur_pflags.exists) + self.assertTrue(cur_pflags.type) + self.assertFalse(cur_pflags.size) + self.assertTrue(cur_pflags.mode) + self.assertFalse(cur_pflags.checksum) + self.assertTrue(cur_pflags.dev) + self.assertTrue(cur_pflags.ltarget) + self.assertTrue(cur_pflags.uid) + self.assertTrue(cur_pflags.gid) + self.assertFalse(cur_pflags.mtime) + + # change file type + os.remove(f) + os.symlink(f, f) + os.lchown(f, inst["ExpectedFileUserID"], inst["ExpectedFileGroupID"]) + inst = self.conn.GetInstance(InstanceName=inst.path) + cur_pflags = PassedFlags(*inst["PassedFlags"]) + self.assertNotEqual(inst["LinkTarget"], inst["ExpectedLinkTarget"]) + self.assertNotEqual(inst["FileSize"], inst["ExpectedFileSize"]) + self.assertGreater(inst["LastModificationTime"], + inst["ExpectedLastModificationTime"]) + self.assertNotEqual(inst["FileType"], inst["ExpectedFileType"]) + self.assertEqual(inst["FileType"], pywbem.Uint16(3)) + + self.assertTrue(cur_pflags.exists) + self.assertFalse(cur_pflags.type) + self.assertFalse(cur_pflags.size) + self.assertFalse(cur_pflags.mode) + self.assertFalse(cur_pflags.checksum) + self.assertTrue(cur_pflags.dev) + self.assertFalse(cur_pflags.ltarget) + self.assertTrue(cur_pflags.uid) + self.assertTrue(cur_pflags.gid) + self.assertFalse(cur_pflags.mtime) + + # remove it + os.remove(f) + inst = self.conn.GetInstance(InstanceName=inst.path) + cur_pflags = PassedFlags(*inst["PassedFlags"]) + self.assertEqual(inst["LinkTarget"], inst["ExpectedLinkTarget"]) + self.assertNotEqual(inst["FileSize"], inst["ExpectedFileSize"]) + self.assertIsNone(inst["LastModificationTime"]) + self.assertIsNone(inst["FileType"]) + self.assertIsNone(inst["Checksum"]) + self.assertIsNone(inst["FileMode"]) + self.assertIsNone(inst["FileUserID"]) + self.assertIsNone(inst["FileGroupID"]) + + self.assertFalse(cur_pflags.exists) + self.assertFalse(cur_pflags.type) + self.assertFalse(cur_pflags.size) + self.assertFalse(cur_pflags.mode) + self.assertFalse(cur_pflags.checksum) + self.assertFalse(cur_pflags.dev) + self.assertFalse(cur_pflags.ltarget) + self.assertFalse(cur_pflags.uid) + self.assertFalse(cur_pflags.gid) + self.assertFalse(cur_pflags.mtime) + + def test_get_instance(self): + for nevra, files in pkg_files: + m = re_nevra.match(nevra) + name, version = [ m.group(k) for k in ("name", "ver") ] + if is_installed(nevra=nevra) and not verify_pkg(name): + remove_pkg(name) + if not is_installed(name): + install_pkg(nevra=nevra) + self.assertTrue(is_installed(nevra=nevra)) + for f in files: + op = self.make_op(nevra, f) + + inst = self.conn.GetInstance(InstanceName=op, LocalOnly=False) + self.assertIsInstance(inst, pywbem.CIMInstance) + self.assertEqual(inst.path, op) + for key in self.KEYS: + self.assertEqual(inst[key], op[key]) + + self.assertTrue(inst["FileExists"]) + self.assertEqual(len(inst["PassedFlags"]), 10) + self.assertTrue(all(f is True for f in inst["PassedFlags"])) + for prop in ( "FileType", "FileUserID", "FileGroupID" + , "FileMode", "FileSize", "LinkTarget" + , "Checksum", "FileModeFlags"): + self.assertEqual(inst["Expected"+prop], inst[prop]) + if os.path.islink(f): + self.do_test_symlink(op, nevra, f, inst) + elif os.path.isdir(f): + self.do_test_directory(op, nevra, f, inst) + elif os.path.isfile(f): + self.do_test_file(op, nevra, f, inst) + + def test_invoke_method(self): + for nevra, files in pkg_files: + m = re_nevra.match(nevra) + name, version = [ m.group(k) for k in ("name", "ver") ] + if is_installed(nevra=nevra) and not verify_pkg(name): + remove_pkg(name) + if not is_installed(name): + install_pkg(nevra=nevra) + self.assertTrue(is_installed(nevra=nevra)) + for f in files: + op = self.make_op(nevra, f) + + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Invoke", + ObjectName=op) + self.assertEqual(rval, pywbem.Uint32(0)) + + # modify file + if os.path.isfile(f): + os.remove(f) + else: + os.lchown(f, os.stat(f).st_uid + 1, -1) + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Invoke", + ObjectName=op) + self.assertEqual(rval, pywbem.Uint32(2)) + + @classmethod + def tearDownClass(cls): + for pkg_nevra, files in pkg_files: + name = re_nevra.match(pkg_nevra).group('name') + if is_installed(name) and not verify_pkg(name): + remove_pkg(name) + if not is_installed(name): + install_pkg(nevra=pkg_nevra) + +if __name__ == "__main__": + unittest.main() diff --git a/src/software/test/test_software_installed_package.py b/src/software/test/test_software_installed_package.py new file mode 100755 index 0000000..5bcb9d2 --- /dev/null +++ b/src/software/test/test_software_installed_package.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python + +from common import * +import shutil +import socket +import stat + +class TestSoftwareInstalledPackage(SoftwareBaseTestCase): + + CLASS_NAME = "LMI_SoftwareInstalledPackage" + KEYS = ( "Software", "System") + + def make_op(self, name, epoch, ver, rel, arch, ses=2): + op = self.op.copy() + pkg_op = pywbem.CIMInstanceName( + classname="LMI_SoftwarePackage", namespace="root/cimv2", + keybindings={ + "Name" : name, + "SoftwareElementID" : make_nevra( + name, epoch, ver, rel, arch, "ALWAYS"), + "SoftwareElementState" : pywbem.Uint16(ses), + "TargetOperatingSystem" : pywbem.Uint16(36), + "Version" : ver }) + system_op = pywbem.CIMInstanceName( + classname="CIM_ComputerSystem", namespace="root/cimv2", + keybindings={ + "CreationClassName" : "CIM_ComputerSystem", + "Name" : socket.gethostname() }) + op["Software"] = pkg_op + op["System"] = system_op + return op + + def test_get_instance(self): + for pkg in packages: + if not is_installed(*pkg[:5]): + install_pkg(*pkg[:5]) + op = self.make_op(*pkg[:5]) + inst = self.conn.GetInstance(InstanceName=op, LocalOnly=False) + self.assertIsSubclass(inst.path.classname, self.CLASS_NAME) + self.assertIsSubclass( + inst.path['System'].classname, op['System'].classname) + self.assertIsSubclass( + inst.path['Software'].classname, op['Software'].classname) + self.assertEqual(len(inst.path.keys()), 2) + for key in self.KEYS: + self.assertEqual(inst[key], inst.path[key]) + self.assertEqual(inst['Software'], op['Software']) + remove_pkg(pkg.name) + op['Software']['SoftwareElementState'] = pywbem.Uint16(1) + with self.assertRaises(pywbem.CIMError) as cm: + self.conn.GetInstance(InstanceName=op, LocalOnly=False) + self.assertEqual(cm.exception.args[0], pywbem.CIM_ERR_NOT_FOUND) + + def test_enum_instances(self): + pkg = packages[0] + if is_installed(*pkg[:5]): + remove_pkg(pkg.name) + insts1 = self.conn.EnumerateInstanceNames(ClassName=self.CLASS_NAME) + install_pkg(*pkg[:5]) + insts2 = self.conn.EnumerateInstanceNames(ClassName=self.CLASS_NAME) + self.assertEqual(len(insts1) + 1, len(insts2)) + + op = self.make_op(*pkg[:5]) + self.assertIn(op['Software'], (inst['Software'] for inst in insts2)) + self.assertTrue(all(isinstance(inst, pywbem.CIMInstanceName) + for inst in insts1)) + + insts1 = self.conn.EnumerateInstances(ClassName=self.CLASS_NAME) + remove_pkg(pkg.name) + insts2 = self.conn.EnumerateInstances(ClassName=self.CLASS_NAME) + + self.assertEqual(len(insts2) + 1, len(insts1)) + self.assertIn(op['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'] + for inst in insts1)) + + def test_create_instance(self): + for pkg in packages: + if is_installed(pkg.name): + remove_pkg(pkg.name) + self.assertFalse(is_installed(pkg.name)) + op = self.make_op(*(list(pkg[:5]) + [1])) + inst = pywbem.CIMInstance(classname=op.classname, path=op) + inst["Software"] = op["Software"] + inst["System"] = op["System"] + iname = self.conn.CreateInstance(NewInstance=inst) + self.assertIsInstance(iname, pywbem.CIMInstanceName) + op["Software"]["SoftwareElementState"] = pywbem.Uint16(2) + self.assertEqual(iname["Software"], op["Software"]) + self.assertIsInstance(iname["System"], pywbem.CIMInstanceName) + self.assertIsSubclass(iname["System"].classname, + op["System"].classname) + self.assertEqual(set(iname.keys()), set(op.keys())) + self.assertTrue(is_installed(pkg.name)) + + with self.assertRaises(pywbem.CIMError) as cm: + self.conn.CreateInstance(NewInstance=inst) + self.assertEqual(cm.exception.args[0], + pywbem.CIM_ERR_ALREADY_EXISTS) + + def test_delete_instance(self): + for pkg in packages: + if not is_installed(pkg.name): + install_pkg(*pkg[:5]) + self.assertTrue(is_installed(pkg.name)) + op = self.make_op(*pkg[:5]) + self.conn.DeleteInstance(op) + self.assertFalse(is_installed(pkg.name)) + with self.assertRaises(pywbem.CIMError) as cm: + self.conn.DeleteInstance(op) + self.assertEqual(cm.exception.args[0], pywbem.CIM_ERR_NOT_FOUND) + + def test_check_integrity(self): + for pkg_nevra, files in pkg_files: + m = re_nevra.match(pkg_nevra) + name, epoch, ver, rel, arch = [ m.group(k) for k in ( + "name", "epoch", "ver", "rel", "arch") ] + if ( (is_installed(nevra=pkg_nevra) and not verify_pkg(name)) + or (is_installed(name) and not is_installed(nevra=pkg_nevra))) : + remove_pkg(name) + if not is_installed(name): + install_pkg(name, epoch, ver, rel, arch) + self.assertTrue(is_installed(nevra=pkg_nevra)) + + op = self.make_op(name, epoch, ver, rel, arch) + (rval, oparms) = self.conn.InvokeMethod( + MethodName="CheckIntegrity", + ObjectName=op) + self.assertEqual(rval, pywbem.Uint32(0)) # check passed + self.assertEqual(len(oparms), 1) + self.assertIn("Failed", oparms) + self.assertEqual(len(oparms["Failed"]), 0) + + cnt_bad = 0 + for f in files: + stats = os.lstat(f) + if os.path.islink(f): # modify symbolic link + target = os.readlink(f) + os.remove(f) + os.symlink(target, f) # just touch symlink + (rval, oparms) = self.conn.InvokeMethod( + MethodName="CheckIntegrity", + ObjectName=op) + # symlink must pass + self.assertEqual(len(oparms["Failed"]), cnt_bad) + os.remove(f) + # now change target + os.symlink("wrong_link_target", f) + elif os.path.isdir(f): # check directory + os.chmod(f, stats.st_mode) # just touch dir + (rval, oparms) = self.conn.InvokeMethod( + MethodName="CheckIntegrity", + ObjectName=op) + # dir must pass + self.assertEqual(len(oparms["Failed"]), cnt_bad) + # modify read access of directory + os.chmod(f, stats.st_mode ^ stat.S_IROTH) + else: # modify regular file + # just touch file - this is enough to make it fail + with open(f, "w+") as fobj: pass + cnt_bad += 1 + (rval, oparms) = self.conn.InvokeMethod( + MethodName="CheckIntegrity", + ObjectName=op) + self.assertEqual(rval, pywbem.Uint32(1)) + self.assertEqual(len(oparms), 1) + self.assertIn("Failed", oparms) + self.assertEqual(len(oparms["Failed"]), cnt_bad) + self.assertIn(f, (p["Name"] for p in oparms["Failed"])) + + def test_method_update(self): + for pkg in packages: + if not pkg.updatable: continue + if is_installed(pkg.name) and not is_installed(*pkg[:5]): + remove_pkg(pkg.name) + if not is_installed(pkg.name): + install_pkg(*pkg[:5]) + self.assertTrue(is_installed(*pkg[:5])) + + op = self.make_op(*pkg[:5]) + op_up = self.make_op(pkg.name, pkg.up_epoch, + pkg.up_ver, pkg.up_rel, pkg.arch) + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Update", + ObjectName=op) + self.assertEqual(rval, pywbem.Uint16(1)) + self.assertEqual(len(oparms), 1) + self.assertIn("Installed", oparms) + self.assertEqual(oparms["Installed"], op_up["Software"]) + self.assertTrue(is_installed(pkg.name, pkg.up_epoch, + pkg.up_ver, pkg.up_rel, pkg.arch)) + + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Update", + ObjectName=op_up) + self.assertEqual(rval, pywbem.Uint16(0)) + self.assertEqual(len(oparms), 1) + self.assertEqual(oparms["Installed"], op_up["Software"]) + + with self.assertRaises(pywbem.CIMError) as cm: + self.conn.InvokeMethod( + MethodName="Update", + ObjectName=op) + self.assertEqual(cm.exception.args[0], + pywbem.CIM_ERR_NOT_FOUND) + + remove_pkg(pkg.name) + install_pkg(*pkg[:5]) + self.assertTrue(is_installed(*pkg[:5])) + + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Update", + ObjectName=op, + Epoch=pkg.epoch, + Version=pkg.ver, + Release=pkg.rel) + self.assertEqual(rval, pywbem.Uint16(0)) + self.assertEqual(oparms["Installed"], op["Software"]) + + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Update", + ObjectName=op, + Epoch=pkg.up_epoch, + Version=pkg.up_ver, + Release=pkg.up_rel) + self.assertEqual(rval, pywbem.Uint16(1)) + self.assertEqual(oparms["Installed"], op_up["Software"]) + + @classmethod + def tearDownClass(cls): + for pkg_nevra, files in pkg_files: + name = re_nevra.match(pkg_nevra).group('name') + if is_installed(name) and not verify_pkg(name): + remove_pkg(name) + if not is_installed(name): + install_pkg(nevra=pkg_nevra) + +if __name__ == "__main__": + unittest.main() diff --git a/src/software/test/test_software_package.py b/src/software/test/test_software_package.py new file mode 100755 index 0000000..cdaf04d --- /dev/null +++ b/src/software/test/test_software_package.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +from common import * + +class TestSoftwarePackage(SoftwareBaseTestCase): + + CLASS_NAME = "LMI_SoftwarePackage" + KEYS = ( "Name", "SoftwareElementID", "SoftwareElementState" + , "TargetOperatingSystem", "Version") + + def make_op(self, name, epoch, ver, rel, arch, ses=2): + op = self.op.copy() + op["Name"] = name + op["SoftwareElementID"] = make_nevra( + name, epoch, ver, rel, arch, "ALWAYS") + op["SoftwareElementState"] = pywbem.Uint16(ses) + op["TargetOperatingSystem"] = pywbem.Uint16(36) + op["Version"] = ver + return op + + def test_get_instance(self): + for pkg in packages: + if is_installed(pkg.name): + remove_pkg(pkg.name) + op = self.make_op(pkg.name, pkg.epoch, pkg.ver, + pkg.rel, pkg.arch, 1) + inst = self.conn.GetInstance(InstanceName=op, LocalOnly=False) + self.assertEqual(inst.path, op) + for key in self.KEYS: + self.assertTrue(inst.properties.has_key(key)) + self.assertEqual(inst.path[key], inst[key]) + self.assertEqual(inst['Release'], pkg.rel) + install_pkg(*pkg[:5]) + op['SoftwareElementState'] = pywbem.Uint16(2) + inst = self.conn.GetInstance(InstanceName=op, LocalOnly=False) + self.assertEqual(inst.path, op) + + # try to leave out epoch part + if pkg.epoch == "0": + op_no_epoch = op.copy() + op_no_epoch["SoftwareElementID"] = make_nevra(pkg.name, pkg.epoch, + pkg.ver, pkg.rel, pkg.arch, "NEVER") + self.assertNotIn(":", op_no_epoch["SoftwareElementID"]) + inst = self.conn.GetInstance( + InstanceName=op_no_epoch, LocalOnly=False) + self.assertIn(inst.path, (op, op_no_epoch)) + + def test_method_install(self): + for pkg in packages: + if is_installed(pkg.name): + remove_pkg(pkg.name) + op = self.make_op(pkg.name, pkg.epoch, pkg.ver, + pkg.rel, pkg.arch, 1) + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Install", + ObjectName=op) + self.assertEqual(rval, pywbem.Uint32(1)) + self.assertEqual(len(oparms), 1) + self.assertTrue(oparms.has_key('Installed')) + op['SoftwareElementState'] = pywbem.Uint16(2) + self.assertEqual(oparms['Installed'], op) + self.assertTrue(is_installed(pkg.name)) + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Install", + ObjectName=op) + self.assertEqual(rval, pywbem.Uint32(0)) + self.assertEqual(len(oparms), 1) + self.assertEqual(oparms['Installed'], op) + + def test_method_remove(self): + for pkg in packages: + if not is_installed(pkg.name): + install_pkg(*pkg[:5]) + op = self.make_op(*pkg[:5]) + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Remove", + ObjectName=op) + self.assertEqual(rval, pywbem.Uint16(1)) + self.assertEqual(len(oparms), 0) + self.assertFalse(is_installed(pkg.name)) + op["SoftwareElementState"] = pywbem.Uint16(1) + (rval, oparms) = self.conn.InvokeMethod( + MethodName="Remove", + ObjectName=op) + self.assertEqual(rval, pywbem.Uint32(0)) + self.assertEqual(len(oparms), 0) + +if __name__ == '__main__': + unittest.main() |