diff options
Diffstat (limited to 'src/software/lmi/software')
47 files changed, 14866 insertions, 0 deletions
diff --git a/src/software/lmi/software/LMI_AffectedSoftwareJobElement.py b/src/software/lmi/software/LMI_AffectedSoftwareJobElement.py new file mode 100644 index 0000000..12e6a35 --- /dev/null +++ b/src/software/lmi/software/LMI_AffectedSoftwareJobElement.py @@ -0,0 +1,250 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 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_AffectedSoftwareJobElement + +Instruments the CIM class LMI_AffectedSoftwareJobElement +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import AffectedSoftwareJobElement +from lmi.software.core import Job +from lmi.software.yumdb import YumDB + +class LMI_AffectedSoftwareJobElement(CIMProvider2): + """Instrument the CIM class LMI_AffectedSoftwareJobElement + + AffectedJobElement represents an association between a Job and the + ManagedElement(s) that may be affected by its execution. It may not be + feasible for the Job to describe all of the affected elements. The + main purpose of this association is to provide information when a Job + requires exclusive use of the 'affected' ManagedElment(s) or when + describing that side effects may result. + """ + + def __init__(self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + self.values = AffectedSoftwareJobElement.Values + + @cmpi_logging.trace_method + 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) + """ + (job, affected) = AffectedSoftwareJobElement.check_path(env, model.path) + model['AffectingElement'] = Job.job2model(job) + + ch = env.get_cimom_handle() + + if ch.is_subclass(affected.namespace, + sub=affected.classname, super='LMI_SoftwareIdentity'): + _, effects, descriptions = AffectedSoftwareJobElement. \ + job2affected_software_identity(job) + model["AffectedElement"] = affected + model["ElementEffects"] = effects + model["OtherElementEffectsDescriptions"] = descriptions + + elif ch.is_subclass(affected.namespace, + sub=affected.classname, super='Linux_ComputerSystem'): + AffectedSoftwareJobElement.fill_model_computer_system( + model, job, keys_only=False) + + elif ch.is_subclass(affected.namespace, + sub=affected.classname, super='LMI_SystemSoftwareCollection'): + AffectedSoftwareJobElement.fill_model_system_collection( + model, keys_only=False) + + elif ch.is_subclass(affected.namespace, sub=affected.classname, + super='LMI_SoftwareIdentityFileCheck'): + AffectedSoftwareJobElement.fill_model_failed_check( + model, affected) + + else: + cmpi_logging.logger.error("Unhandled classname: %s", + affected.classname) + + return model + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'AffectedElement': None, 'AffectingElement': None}) + for job in YumDB.get_instance().get_job_list(): + for mdl in AffectedSoftwareJobElement.generate_models_from_job( + job, keys_only=keys_only, model=model): + yield mdl + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + """ + ch = env.get_cimom_handle() + + if ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='CIM_ManagedElement') or \ + ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='CIM_Job'): + return self.simple_refs(env, object_name, model, + result_class_name, role, result_role, keys_only) diff --git a/src/software/lmi/software/LMI_AssociatedSoftwareInstallationServiceCapabilities.py b/src/software/lmi/software/LMI_AssociatedSoftwareInstallationServiceCapabilities.py new file mode 100644 index 0000000..15b8df9 --- /dev/null +++ b/src/software/lmi/software/LMI_AssociatedSoftwareInstallationServiceCapabilities.py @@ -0,0 +1,231 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 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_AssociatedSoftwareInstallationServiceCapabilities + +Instruments the CIM class LMI_AssociatedSoftwareInstallationServiceCapabilities + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import InstallationService +from lmi.software.core import InstallationServiceCapabilities +from lmi.software.core import AssociatedInstallationServiceCapabilities + +class LMI_AssociatedSoftwareInstallationServiceCapabilities(CIMProvider2): + """Instrument the CIM class \ + LMI_AssociatedSoftwareInstallationServiceCapabilities + + ElementCapabilities represents the association between ManagedElements + and their Capabilities. Note that the cardinality of the + ManagedElement reference is Min(1). This cardinality mandates the + instantiation of the ElementCapabilities association for the + referenced instance of Capabilities. ElementCapabilities describes the + existence requirements and context for the referenced instance of + ManagedElement. Specifically, the ManagedElement MUST exist and + provides the context for the Capabilities. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + self.values = AssociatedInstallationServiceCapabilities.Values + + @cmpi_logging.trace_method + 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) + """ + InstallationService.check_path_property(env, model, "ManagedElement") + InstallationServiceCapabilities.check_path_property( + env, model, "Capabilities") + model['Characteristics'] = [ + self.values.Characteristics.Default, + self.values.Characteristics.Current] + return model + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'Capabilities': None, 'ManagedElement': None}) + + model['Capabilities'] = InstallationServiceCapabilities.get_path() + model['ManagedElement'] = InstallationService.get_path() + if not keys_only: + model['Characteristics'] = [ + self.values.Characteristics.Default, + self.values.Characteristics.Current] + yield model + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + """ + ch = env.get_cimom_handle() + if ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='LMI_SoftwareInstallationServiceCapabilities') or \ + ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='LMI_SoftwareInstallationService'): + return self.simple_refs(env, object_name, model, + result_class_name, role, result_role, keys_only) diff --git a/src/software/lmi/software/LMI_AssociatedSoftwareJobMethodResult.py b/src/software/lmi/software/LMI_AssociatedSoftwareJobMethodResult.py new file mode 100644 index 0000000..fa2bad7 --- /dev/null +++ b/src/software/lmi/software/LMI_AssociatedSoftwareJobMethodResult.py @@ -0,0 +1,248 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 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_AssociatedSoftwareJobMethodResult + +Instruments the CIM class LMI_AssociatedSoftwareJobMethodResult +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import generate_references +from lmi.software.core import Job +from lmi.software.core import MethodResult +from lmi.software.yumdb import YumDB + +@cmpi_logging.trace_function +def generate_job_referents(_env, object_name, model, _keys_only): + """ + Handler for referents enumeration request. + """ + job = Job.object_path2job(object_name) + + model["Job"] = Job.job2model(job) + model["JobParameters"] = MethodResult.job2model(job) + yield model + +@cmpi_logging.trace_function +def generate_result_referents(_env, object_name, model, _keys_only): + """ + Handler for referents enumeration request. + """ + job = MethodResult.object_path2job(object_name) + + model["Job"] = Job.job2model(job) + model["JobParameters"] = MethodResult.job2model(job) + yield model + +class LMI_AssociatedSoftwareJobMethodResult(CIMProvider2): + """Instrument the CIM class LMI_AssociatedSoftwareJobMethodResult + + AssociatedJobMethodResult represents an association between a + ConcreteJob and the MethodResult expressing the parameters for the Job + when the job was created by side-effect of the execution of an + extrinsic method. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + """ + job = Job.object_path2job(model['Job']) + jobid = MethodResult.object_path2jobid(model['JobParameters']) + if job.jobid != jobid: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Job id of Job(%d) does not match JobParameters(%d)." % + (job.jobid, jobid)) + model["Job"] = Job.job2model(job) + model["JobParameters"] = MethodResult.job2model(job) + return model + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'Job': None, 'JobParameters': None}) + + for job in YumDB.get_instance().get_job_list(): + model['Job'] = Job.job2model(job) + model['JobParameters'] = MethodResult.job2model(job) + yield model + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + """ + handlers = [ + ("Job", "LMI_SoftwareJob", generate_job_referents), + ("JobParameters", "LMI_SoftwareMethodResult", + generate_result_referents) + ] + + for ref in generate_references(env, object_name, model, + result_class_name, role, result_role, keys_only, handlers): + yield ref + diff --git a/src/software/lmi/software/LMI_HostedSoftwareCollection.py b/src/software/lmi/software/LMI_HostedSoftwareCollection.py new file mode 100644 index 0000000..7249bd6 --- /dev/null +++ b/src/software/lmi/software/LMI_HostedSoftwareCollection.py @@ -0,0 +1,225 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 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_HostedSoftwareCollection + +Instruments the CIM class LMI_HostedSoftwareCollection + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import ComputerSystem, SystemCollection + +class LMI_HostedSoftwareCollection(CIMProvider2): + """Instrument the CIM class LMI_HostedSoftwareCollection + + HostedSoftwareCollection defines a SystemSpecificCollection in the context of a + scoping System. It represents a Collection that has meaning only in + the context of a System, a Collection whose elements are restricted by + the definition of the System, or both of these types of Collections. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + + """ + ComputerSystem.check_path_property(env, model, "Antecedent") + SystemCollection.check_path_property(env, model, "Dependent") + + model.path.update({"Antecedent":None, "Dependent":None}) + + model["Antecedent"] = ComputerSystem.get_path() + model["Dependent"] = SystemCollection.get_path() + + return model + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'Dependent': None, 'Antecedent': None}) + + model["Antecedent"] = ComputerSystem.get_path() + model["Dependent"] = SystemCollection.get_path() + + yield model + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + """ + ch = env.get_cimom_handle() + + if ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='CIM_SystemSpecificCollection') or \ + ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='CIM_System'): + return self.simple_refs(env, object_name, model, + result_class_name, role, result_role, keys_only) + diff --git a/src/software/lmi/software/LMI_HostedSoftwareIdentityResource.py b/src/software/lmi/software/LMI_HostedSoftwareIdentityResource.py new file mode 100644 index 0000000..767523a --- /dev/null +++ b/src/software/lmi/software/LMI_HostedSoftwareIdentityResource.py @@ -0,0 +1,280 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +"""Python Provider for LMI_HostedSoftwareIdentityResource + +Instruments the CIM class LMI_HostedSoftwareIdentityResource + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import generate_references +from lmi.software.core import ComputerSystem +from lmi.software.core import IdentityResource +from lmi.software.yumdb import YumDB + +@cmpi_logging.trace_function +def generate_system_referents(env, object_name, model, _keys_only): + """ + Handler for referents enumeration request. + """ + ComputerSystem.check_path(env, object_name, "Antecedent") + repomodel = pywbem.CIMInstanceName( + classname='LMI_SoftwareIdentityResource', + namespace='root/cimv2', + host=model.path.host) + model["Antecedent"] = ComputerSystem.get_path() + for repo in YumDB.get_instance().get_repository_list('all'): + model["Dependent"] = IdentityResource.repo2model(repo, model=repomodel) + yield model + +@cmpi_logging.trace_function +def generate_repository_referents(env, object_name, model, _keys_only): + """ + Handler for referents enumeration request. + """ + repo = IdentityResource.object_path2repo( + env, object_name, kind='all') + model["Antecedent"] = ComputerSystem.get_path() + model["Dependent"] = IdentityResource.repo2model(repo) + yield model + +class LMI_HostedSoftwareIdentityResource(CIMProvider2): + """Instrument the CIM class LMI_HostedSoftwareIdentityResource + + CIM_HostedAccessPoint is an association between a Service AccessPoint + and the System on which it is provided. The cardinality of this + association is one-to-many and is weak with respect to the System. + Each System can host many ServiceAccessPoints. Heuristic: If the + implementation of the ServiceAccessPoint is modeled, it must be + implemented by a Device or SoftwareFeature that is part of the System + that is hosting the ServiceAccessPoint. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + + """ + ComputerSystem.check_path_property(env, model, "Antecedent") + if not "Name" in model['Dependent']: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + 'Missing key property "Name" in Dependent!') + + with YumDB.get_instance() as ydb: + repoid = model["Dependent"]["Name"] + repos = ydb.filter_repositories('all', repoid=repoid) + if len(repos) < 1: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'Unknown repository "%s".' % repoid) + model["Antecedent"] = ComputerSystem.get_path() + model["Dependent"] = IdentityResource.repo2model(repos[0]) + + return model + + @cmpi_logging.trace_method + 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) + + """ + + # 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({'Dependent': None, 'Antecedent': None}) + + repolist = YumDB.get_instance().get_repository_list('all') + model["Antecedent"] = ComputerSystem.get_path() + repomodel = pywbem.CIMInstanceName( + classname='LMI_SoftwareIdentityResource', + namespace='root/cimv2') + for repo in repolist: + model["Dependent"] = IdentityResource.repo2model( + repo, model=repomodel) + yield model + + @cmpi_logging.trace_method + 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) + + """ + + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + + """ + + handlers = [ + ("Antecedent", "CIM_ComputerSystem" , generate_system_referents), + ("Dependent", "LMI_SoftwareIdentityResource", + generate_repository_referents) + ] + for ref in generate_references(env, object_name, model, + result_class_name, role, result_role, keys_only, handlers): + yield ref + diff --git a/src/software/lmi/software/LMI_HostedSoftwareInstallationService.py b/src/software/lmi/software/LMI_HostedSoftwareInstallationService.py new file mode 100644 index 0000000..5c650cb --- /dev/null +++ b/src/software/lmi/software/LMI_HostedSoftwareInstallationService.py @@ -0,0 +1,233 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +"""Python Provider for LMI_HostedSoftwareInstallationService + +Instruments the CIM class LMI_HostedSoftwareInstallationService + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import ComputerSystem, InstallationService + +class LMI_HostedSoftwareInstallationService(CIMProvider2): + """Instrument the CIM class LMI_HostedSoftwareInstallationService + + CIM_HostedService is an association between a Service and the System on + which the functionality is located. The cardinality of this + association is one-to-many. A System can host many Services. Services + are weak with respect to their hosting System. Heuristic: A Service is + hosted on the System where the LogicalDevices or SoftwareFeatures that + implement the Service are located. The model does not represent + Services hosted across multiple systems. The model is as an + ApplicationSystem that acts as an aggregation point for Services that + are each located on a single host. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + """ + InstallationService.check_path_property(env, model, 'Dependent') + ComputerSystem.check_path_property(env, model, 'Antecedent') + + model.path.update({"Antecedent":None, "Dependent":None}) + + model["Dependent"] = InstallationService.get_path() + model["Antecedent"] = ComputerSystem.get_path() + + return model + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'Dependent': None, 'Antecedent': None}) + + model["Antecedent"] = ComputerSystem.get_path() + model["Dependent"] = InstallationService.get_path() + + yield model + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + """ + ch = env.get_cimom_handle() + + if ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='LMI_SoftwareInstallationService') or \ + ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='Linux_ComputerSystem'): + return self.simple_refs(env, object_name, model, + result_class_name, role, result_role, keys_only) diff --git a/src/software/lmi/software/LMI_InstalledSoftwareIdentity.py b/src/software/lmi/software/LMI_InstalledSoftwareIdentity.py new file mode 100644 index 0000000..429a7a2 --- /dev/null +++ b/src/software/lmi/software/LMI_InstalledSoftwareIdentity.py @@ -0,0 +1,305 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +"""Python Provider for LMI_InstalledSoftwareIdentity + +Instruments the CIM class LMI_InstalledSoftwareIdentity + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import generate_references +from lmi.software.core import ComputerSystem +from lmi.software.core import Identity +from lmi.software.yumdb import YumDB + +@cmpi_logging.trace_function +def generate_system_referents(env, object_name, model, _keys_only): + """ + Handler for referents enumeration request. + """ + ComputerSystem.check_path(env, object_name, "object_name") + + pkg_model = pywbem.CIMInstanceName( + classname='LMI_SoftwareIdentity', + namespace="root/cimv2", + host=model.path.host) + model["System"] = ComputerSystem.get_path() + with YumDB.get_instance() as ydb: + for pkg_info in ydb.get_package_list('installed', sort=True): + model["InstalledSoftware"] = Identity.pkg2model( + pkg_info, model=pkg_model) + yield model + +@cmpi_logging.trace_function +def generate_package_referents(_env, object_name, model, _keys_only): + """ + Handler for referents enumeration request. + """ + pkg_info = Identity.object_path2pkg(object_name, kind="installed") + model['InstalledSoftware'] = Identity.pkg2model(pkg_info) + model["System"] = ComputerSystem.get_path() + yield model + +class LMI_InstalledSoftwareIdentity(CIMProvider2): + """Instrument the CIM class LMI_InstalledSoftwareIdentity + + The InstalledSoftwareIdentity association identifies the System on + which a SoftwareIdentity is installed. This class is a corollary to + InstalledSoftwareElement, but deals with the asset aspects of software + (as indicated by SoftwareIdentity), versus the deployment aspects (as + indicated by SoftwareElement). + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + + """ + ComputerSystem.check_path_property(env, model, 'System') + + if not isinstance(model['InstalledSoftware'], pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Expected object path for InstalledSoftware!") + + model["System"] = model.path["System"] = ComputerSystem.get_path() + pkg_info = Identity.object_path2pkg( + model['InstalledSoftware'], kind='installed') + model['InstalledSoftware'] = Identity.pkg2model(pkg_info) + return model + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'InstalledSoftware': None, 'System': None}) + + model['System'] = ComputerSystem.get_path() + inst_model = pywbem.CIMInstanceName( + classname="LMI_SoftwareIdentity", + namespace="root/cimv2") + with YumDB.get_instance() as yb: + pl = yb.get_package_list('installed', sort=True) + for pkg in pl: + model['InstalledSoftware'] = Identity.pkg2model( + pkg, model=inst_model) + yield model + + @cmpi_logging.trace_method + 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) + """ + + if modify_existing: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED, + "Installed package can not be modified.") + + if not "InstalledSoftware" in instance: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Missing InstalledSoftware property.") + if not "System" in instance: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Missing System property.") + + ComputerSystem.check_path_property(env, instance, 'System') + with YumDB.get_instance() as ydb: + pkg_info = Identity.object_path2pkg( + instance["InstalledSoftware"], kind="all") + if pkg_info.installed: + raise pywbem.CIMError(pywbem.CIM_ERR_ALREADY_EXISTS, + 'Package "%s" is already installed.' % pkg_info) + cmpi_logging.logger.info('installing package "%s"' % pkg_info) + installed = ydb.install_package(pkg_info) + cmpi_logging.logger.info('package "%s" installed' % pkg_info) + instance["InstalledSoftware"] = Identity.pkg2model(installed) + return instance + + @cmpi_logging.trace_method + 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) + """ + if not "InstalledSoftware" in instance_name: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Missing InstalledSoftware property.") + if not "System" in instance_name: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Missing System property.") + ComputerSystem.check_path_property(env, instance_name, 'System') + + with YumDB.get_instance() as ydb: + pkg_info = Identity.object_path2pkg( + instance_name["InstalledSoftware"], kind="installed") + cmpi_logging.logger.info('removing package "%s"' % pkg_info) + ydb.remove_package(pkg_info) + cmpi_logging.logger.info('package "%s" removed' % pkg_info) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + + """ + handlers = [ + ("System", "CIM_ComputerSystem", generate_system_referents), + ("InstalledSoftware", "LMI_SoftwareIdentity", + generate_package_referents) + ] + + for ref in generate_references(env, object_name, model, + result_class_name, role, result_role, keys_only, handlers): + yield ref + diff --git a/src/software/lmi/software/LMI_MemberOfSoftwareCollection.py b/src/software/lmi/software/LMI_MemberOfSoftwareCollection.py new file mode 100644 index 0000000..f66f788 --- /dev/null +++ b/src/software/lmi/software/LMI_MemberOfSoftwareCollection.py @@ -0,0 +1,280 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 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_MemberOfSoftwareCollection + +Instruments the CIM class LMI_MemberOfSoftwareCollection + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import generate_references +from lmi.software.core import SystemCollection +from lmi.software.core import Identity +from lmi.software.yumdb import YumDB + +@cmpi_logging.trace_function +def generate_collection_referents(env, object_name, model, _keys_only): + """ + Handler for referents enumeration request. + """ + SystemCollection.check_path(env, object_name, "collection") + pkg_model = pywbem.CIMInstanceName( + classname='LMI_SoftwareIdentity', + namespace="root/cimv2", + host=model.path.host) + model["Collection"] = SystemCollection.get_path() + with YumDB.get_instance() as ydb: + for pkg_info in ydb.get_package_list('available', + allow_duplicates=True, sort=True): + model["Member"] = Identity.pkg2model(pkg_info, model=pkg_model) + yield model + +@cmpi_logging.trace_function +def generate_member_referents(_env, object_name, model, _keys_only): + """ + Handler for referents enumeration request. + """ + try: + pkg_info = Identity.object_path2pkg(object_name, kind="available") + model['Member'] = Identity.pkg2model(pkg_info) + model["Collection"] = SystemCollection.get_path() + except pywbem.CIMError as exc: + if exc.args[0] == pywbem.CIM_ERR_NOT_FOUND: + msg = "Could not find requested package%s." + if "InstanceID" in object_name: + msg = msg % (' with InstanceID="%s"'%object_name["InstanceID"]) + else: + msg = msg % "" + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, msg) + else: + raise + yield model + +class LMI_MemberOfSoftwareCollection(CIMProvider2): + """Instrument the CIM class LMI_MemberOfSoftwareCollection + + LMI_MemberOfSoftwareCollection is an aggregation used to establish + membership of ManagedElements in a Collection. + """ + + def __init__(self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + + """ + SystemCollection.check_path_property(env, model, "Collection") + + if not 'Member' in model: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + 'Missing "Member" key property!') + if not isinstance(model['Member'], pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Expected object path for Member!") + + model['Collection'] = SystemCollection.get_path() + with YumDB.get_instance(): + pkg_info = Identity.object_path2pkg(model['Member'], 'available') + model['Member'] = Identity.pkg2model(pkg_info) + return model + + @cmpi_logging.trace_method + 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) + """ + # 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({'Member': None, 'Collection': None}) + + model['Collection'] = SystemCollection.get_path() + member_model = pywbem.CIMInstanceName( + classname="LMI_SoftwareIdentity", + namespace="root/cimv2") + with YumDB.get_instance() as yb: + pl = yb.get_package_list('available', + allow_duplicates=True, + sort=True) + for pkg in pl: + model['Member'] = Identity.pkg2model(pkg, model=member_model) + yield model + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + + """ + handlers = [ + ("Collection", "LMI_SystemSoftwareCollection", + generate_collection_referents), + ("Member", "LMI_SoftwareIdentity", generate_member_referents) + ] + + for ref in generate_references(env, object_name, model, + result_class_name, role, result_role, keys_only, handlers): + yield ref + diff --git a/src/software/lmi/software/LMI_OwningSoftwareJobElement.py b/src/software/lmi/software/LMI_OwningSoftwareJobElement.py new file mode 100644 index 0000000..5f8b3a8 --- /dev/null +++ b/src/software/lmi/software/LMI_OwningSoftwareJobElement.py @@ -0,0 +1,223 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 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_OwningSoftwareJobElement + +Instruments the CIM class LMI_OwningSoftwareJobElement +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import InstallationService +from lmi.software.core import Job +from lmi.software.yumdb import YumDB + +class LMI_OwningSoftwareJobElement(CIMProvider2): + """Instrument the CIM class LMI_OwningSoftwareJobElement + + OwningJobElement represents an association between a Job and the + ManagedElement responsible for the creation of the Job. This + association may not be possible, given that the execution of jobs can + move between systems and that the lifecycle of the creating entity may + not persist for the total duration of the job. However, this can be + very useful information when available. This association defines a + more specific 'owner' than is provided by the CIM_Job.Owner string. + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + """ + InstallationService.check_path_property(env, model, "OwningElement") + model['OwningElement'] = InstallationService.get_path() + job = Job.object_path2job(model['OwnedElement']) + model['OwnedElement'] = Job.job2model(job) + return model + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'OwningElement': None, 'OwnedElement': None}) + model['OwningElement'] = InstallationService.get_path() + for job in YumDB.get_instance().get_job_list(): + model['OwnedElement'] = Job.job2model(job) + yield model + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + """ + ch = env.get_cimom_handle() + + if ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='LMI_SoftwareInstallationService') or \ + ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super='LMI_SoftwareJob'): + return self.simple_refs(env, object_name, model, + result_class_name, role, result_role, keys_only) diff --git a/src/software/lmi/software/LMI_ResourceForSoftwareIdentity.py b/src/software/lmi/software/LMI_ResourceForSoftwareIdentity.py new file mode 100644 index 0000000..ca22337 --- /dev/null +++ b/src/software/lmi/software/LMI_ResourceForSoftwareIdentity.py @@ -0,0 +1,317 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 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_ResourceForSoftwareIdentity + +Instruments the CIM class LMI_ResourceForSoftwareIdentity +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import generate_references +from lmi.software.core import Identity +from lmi.software.core import IdentityResource +from lmi.software.yumdb import YumDB + +@cmpi_logging.trace_function +def generate_package_referents(_env, object_name, model, _keys_only): + """ + Generates models of repositories holding package represented + by object_name. + """ + with YumDB.get_instance() as ydb: + pkg_infos = Identity.object_path2pkg( + object_name, 'available', return_all=True) + for pkg_info in pkg_infos: + repos = ydb.filter_repositories('enabled', repoid=pkg_info.repoid) + model['ManagedElement'] = Identity.pkg2model(pkg_info) + for repo in repos: + model["AvailableSAP"] = \ + IdentityResource.repo2model(repo) + yield model + +@cmpi_logging.trace_function +def generate_repository_referents(env, object_name, model, _keys_only): + """ + Generates models of software identities contained in repository + represented by object_name. + """ + with YumDB.get_instance() as ydb: + repo = IdentityResource.object_path2repo( + env, object_name, 'all') + pkglist = ydb.get_package_list('available', + allow_duplicates=True, sort=True, + include_repos=repo.repoid, + exclude_repos='*') + model['AvailableSAP'] = IdentityResource.repo2model(repo) + pkg_model = pywbem.CIMInstanceName( + classname="LMI_SoftwareIdentity", + namespace="root/cimv2") + for pkg_info in pkglist: + model["ManagedElement"] = Identity.pkg2model( + pkg_info, model=pkg_model) + yield model + +class LMI_ResourceForSoftwareIdentity(CIMProvider2): + """Instrument the CIM class LMI_ResourceForSoftwareIdentity + + CIM_SAPAvailableForElement conveys the semantics of a Service Access + Point that is available for a ManagedElement. When + CIM_SAPAvailableForElement is not instantiated, then the SAP is + assumed to be generally available. If instantiated, the SAP is + available only for the associated ManagedElements. For example, a + device might provide management access through a URL. This association + allows the URL to be advertised for the device. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + """ + if not isinstance(model['ManagedElement'], pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Expected object path for ManagedElement!") + if not isinstance(model['AvailableSAP'], pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Expected object path for AvailableSAP!") + if not "Name" in model['AvailableSAP']: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + 'Missing key property "Name" in AvailableSAP!') + + with YumDB.get_instance() as ydb: + repoid = model["AvailableSAP"]["Name"] + pkg_info = Identity.object_path2pkg( + model["ManagedElement"], + kind='available', + include_repos=repoid, + exclude_repos='*', + repoid=repoid) + repos = ydb.filter_repositories('all', repoid=repoid) + if len(repos) < 1: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'Unknown repository "%s".' % repoid) + repo = repos[0] + repo = IdentityResource.object_path2repo( + env, model["AvailableSAP"], 'all') + + model["AvailableSAP"] = IdentityResource.repo2model(repo) + model["ManagedElement"] = Identity.pkg2model(pkg_info) + + return model + + @cmpi_logging.trace_method + 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) + + """ + # 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({'ManagedElement': None, 'AvailableSAP': None}) + + elem_model = pywbem.CIMInstanceName( + classname="LMI_SoftwareIdentity", + namespace="root/cimv2") + sap_model = pywbem.CIMInstanceName( + classname="LMI_SoftwareIdentityResource", + namespace="root/cimv2") + # maps repoid to instance name + with YumDB.get_instance() as ydb: + pl = ydb.get_package_list('available', + allow_duplicates=True, + sort=True) + repos = dict( (r.repoid, r) + for r in ydb.get_repository_list('enabled')) + for pkg in pl: + try: + repo = repos[pkg.repoid] + except KeyError: + cmpi_logging.logger.error('unknown or disabled repository' + ' "%s" for package "%s"', pkg, pkg.repoid) + model["AvailableSAP"] = IdentityResource.repo2model( + repo, model=sap_model) + model["ManagedElement"] = Identity.pkg2model( + pkg, model=elem_model) + yield model + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + + """ + handlers = [ + ("AvailableSAP", "LMI_SoftwareIdentityResource", + generate_repository_referents), + ("ManagedElement", "LMI_SoftwareIdentity", + generate_package_referents) + ] + + for ref in generate_references(env, object_name, model, + result_class_name, role, result_role, keys_only, handlers): + yield ref + diff --git a/src/software/lmi/software/LMI_SoftwareIdentity.py b/src/software/lmi/software/LMI_SoftwareIdentity.py new file mode 100644 index 0000000..722f86c --- /dev/null +++ b/src/software/lmi/software/LMI_SoftwareIdentity.py @@ -0,0 +1,188 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 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_SoftwareIdentity + +Instruments the CIM class LMI_SoftwareIdentity + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import Identity +from lmi.software.yumdb import YumDB + +class LMI_SoftwareIdentity(CIMProvider2): + """Instrument the CIM class LMI_SoftwareIdentity + + SoftwareIdentity provides descriptive information about a software + component for asset tracking and/or installation dependency + management. When the IsEntity property has the value TRUE, the + instance of SoftwareIdentity represents an individually identifiable + entity similar to Physical Element. SoftwareIdentity does NOT indicate + whether the software is installed, executing, etc. This extra + information may be provided through specialized associations to + Software Identity. For instance, both InstalledSoftwareIdentity and + ElementSoftwareIdentity may be used to indicate that the software + identified by this class is installed. SoftwareIdentity is used when + managing the software components of a ManagedElement that is the + management focus. Since software may be acquired, SoftwareIdentity can + be associated with a Product using the ProductSoftwareComponent + relationship. The Application Model manages the deployment and + installation of software via the classes, SoftwareFeatures and + SoftwareElements. SoftwareFeature and SoftwareElement are used when + the software component is the management focus. The + deployment/installation concepts are related to the asset/identity + one. In fact, a SoftwareIdentity may correspond to a Product, or to + one or more SoftwareFeatures or SoftwareElements - depending on the + granularity of these classes and the deployment model. The + correspondence of Software Identity to Product, SoftwareFeature or + SoftwareElement is indicated using the ConcreteIdentity association. + Note that there may not be sufficient detail or instrumentation to + instantiate ConcreteIdentity. And, if the association is instantiated, + some duplication of information may result. For example, the Vendor + described in the instances of Product and SoftwareIdentity MAY be the + same. However, this is not necessarily true, and it is why vendor and + similar information are duplicated in this class. Note that + ConcreteIdentity can also be used to describe the relationship of the + software to any LogicalFiles that result from installing it. As above, + there may not be sufficient detail or instrumentation to instantiate + this association. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + + """ + with YumDB.get_instance(): + pkg_info = Identity.object_path2pkg(model.path, 'all') + return Identity.pkg2model(pkg_info, keys_only=False, model=model) + + @cmpi_logging.trace_method + 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) + + """ + # 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({'InstanceID': None}) + + with YumDB.get_instance() as ydb: + pkglist = ydb.get_package_list( + 'all', allow_duplicates=True, sort=True) + for pkg_info in pkglist: + yield Identity.pkg2model(pkg_info, keys_only=keys_only, model=model) + + @cmpi_logging.trace_method + def set_instance(self, env, instance, modify_existing): + """Return a newly created or modified instance. + + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + diff --git a/src/software/lmi/software/LMI_SoftwareIdentityChecks.py b/src/software/lmi/software/LMI_SoftwareIdentityChecks.py new file mode 100644 index 0000000..47ae3f4 --- /dev/null +++ b/src/software/lmi/software/LMI_SoftwareIdentityChecks.py @@ -0,0 +1,256 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +"""Python Provider for LMI_SoftwareIdentityChecks + +Instruments the CIM class LMI_SoftwareIdentityChecks + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import generate_references +from lmi.software.core import Identity, IdentityFileCheck +from lmi.software.yumdb import YumDB + +def generate_identity_referents(_env, object_name, model, _keys_only): + """ + Handler for referents enumeration request enumerating file checks + associated to software identity. + """ + filecheck_model = pywbem.CIMInstanceName( + classname='LMI_SoftwareIdentityFileCheck', + namespace="root/cimv2", + host=model.path.host) + pkg_info = Identity.object_path2pkg( + object_name, kind="installed") + model['Element'] = Identity.pkg2model(pkg_info) + pkg_info, pkg_check = YumDB.get_instance().check_package(pkg_info) + for file_name in pkg_check: + model['Check'] = IdentityFileCheck.file_check2model( + IdentityFileCheck.FileCheck(pkg_info, pkg_check[file_name], + pkg_check.file_checksum_type), + model=filecheck_model) + yield model + +def generate_check_referents(_env, object_name, model, _keys_only): + """ + Handler for referents enumeration request enumerating software + identities associated to file check. + """ + file_check = IdentityFileCheck.object_path2file_check(object_name) + model['Check'] = IdentityFileCheck.file_check2model(file_check) + model['Element'] = Identity.pkg2model(file_check.pkg_info) + yield model + +class LMI_SoftwareIdentityChecks(CIMProvider2): + """Instrument the CIM class LMI_SoftwareIdentityChecks + + 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): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + + """ + file_check = IdentityFileCheck.object_path2file_check(model['Check']) + model['Check'] = IdentityFileCheck.file_check2model(file_check) + model['Element'] = Identity.pkg2model(file_check.pkg_info) + return model + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED, + "Enumeration of instances is not supported.") + + @cmpi_logging.trace_method + def set_instance(self, env, instance, modify_existing): + """Return a newly created or modified instance. + + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + + """ + handlers = [ + ("Element", "LMI_SoftwareIdentity", generate_identity_referents), + ("Check", "LMI_SoftwareIdentityFileCheck", + generate_check_referents) + ] + + for ref in generate_references(env, object_name, model, + result_class_name, role, result_role, keys_only, handlers): + yield ref + diff --git a/src/software/lmi/software/LMI_SoftwareIdentityFileCheck.py b/src/software/lmi/software/LMI_SoftwareIdentityFileCheck.py new file mode 100644 index 0000000..fe6f101 --- /dev/null +++ b/src/software/lmi/software/LMI_SoftwareIdentityFileCheck.py @@ -0,0 +1,244 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +"""Python Provider for LMI_SoftwareIdentityFileCheck + +Instruments the CIM class LMI_SoftwareIdentityFileCheck + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import IdentityFileCheck +from lmi.software.core import ComputerSystem + +class LMI_SoftwareIdentityFileCheck(CIMProvider2): + """Instrument the CIM class LMI_SoftwareIdentityFileCheck + + 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): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + self.values = IdentityFileCheck.Values + + @cmpi_logging.trace_method + 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) + + """ + file_check = IdentityFileCheck.object_path2file_check(model.path) + return IdentityFileCheck.file_check2model(file_check, keys_only=False, + model=model) + + @cmpi_logging.trace_method + 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) + + """ + # this won't be supported because of enormous amount of data + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def set_instance(self, env, instance, modify_existing): + """Return a newly created or modified instance. + + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def cim_method_invoke(self, env, object_name): + """Implements LMI_SoftwareIdentityFileCheck.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) + + """ + file_check = IdentityFileCheck.object_path2file_check(object_name) + out_params = [] + return ( self.values.Invoke.Satisfied + if IdentityFileCheck.file_check_passed(file_check) + else self.values.Invoke.Not_Satisfied, + out_params) + + @cmpi_logging.trace_method + def cim_method_invokeonsystem(self, env, object_name, + param_targetsystem=None): + """Implements LMI_SoftwareIdentityFileCheck.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) + """ + if param_targetsystem is not None: + ComputerSystem.check_path(env, param_targetsystem, "TargetSystem") + return self.cim_method_invoke(env, object_name) + diff --git a/src/software/lmi/software/LMI_SoftwareIdentityResource.py b/src/software/lmi/software/LMI_SoftwareIdentityResource.py new file mode 100644 index 0000000..ff359b7 --- /dev/null +++ b/src/software/lmi/software/LMI_SoftwareIdentityResource.py @@ -0,0 +1,270 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +"""Python Provider for LMI_SoftwareIdentityResource + +Instruments the CIM class LMI_SoftwareIdentityResource + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import IdentityResource +from lmi.software.yumdb import YumDB, errors + +class LMI_SoftwareIdentityResource(CIMProvider2): + """Instrument the CIM class LMI_SoftwareIdentityResource + + SoftwareIdentityResource describes the URL of a file or other resource + that contains all or part of of a SoftwareIdentity for use by the + SoftwareInstallationService. For example, a CIM_SoftwareIdentity might + consist of a meta data file, a binary executable file, and a + installability checker file for some software on a system. This class + allows a management client to selectively access the constituents of + the install package to perform a check function, or retrieve some meta + data information for the install package represented by the + SoftwareIdentity class without downloading the entire package. + SoftwareIdentityResources will be related to the SoftwareIdentity + using the SAPAvailableForElement association. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + self.values = IdentityResource.Values + + @cmpi_logging.trace_method + 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) + """ + repo = IdentityResource.object_path2repo(env, model.path, kind='all') + return IdentityResource.repo2model(repo, keys_only=False, model=model) + + @cmpi_logging.trace_method + 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) + + """ + # 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({'CreationClassName': None, 'SystemName': None, + 'Name': None, 'SystemCreationClassName': None}) + + repolist = YumDB.get_instance().get_repository_list('all') + for repo in repolist: + yield IdentityResource.repo2model( + repo, keys_only=keys_only, model=model) + + @cmpi_logging.trace_method + def set_instance(self, env, instance, modify_existing): + """Return a newly created or modified instance. + + 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) + + """ + # TODO creation and modification should be supported + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + # TODO removal should also be supported + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def cim_method_requeststatechange(self, env, object_name, + param_requestedstate=None, + param_timeoutperiod=None): + """Implements LMI_SoftwareIdentityResource.RequestStateChange() + + Requests that the state of the element be changed to the value + specified in the RequestedState parameter. When the requested + state change takes place, the EnabledState and RequestedState of + the element will be the same. Invoking the RequestStateChange + method multiple times could result in earlier requests being + overwritten or lost. A return code of 0 shall indicate the state + change was successfully initiated. A return code of 3 shall + indicate that the state transition cannot complete within the + interval specified by the TimeoutPeriod parameter. A return code + of 4096 (0x1000) shall indicate the state change was successfully + initiated, a ConcreteJob has been created, and its reference + returned in the output parameter Job. Any other return code + indicates an error condition. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method RequestStateChange() + should be invoked. + param_requestedstate -- The input parameter RequestedState ( + type pywbem.Uint16 Values.RequestStateChange.RequestedState) + The state requested for the element. This information will be + placed into the RequestedState property of the instance if the + return code of the RequestStateChange method is 0 ('Completed + with No Error'), or 4096 (0x1000) ('Job Started'). Refer to + the description of the EnabledState and RequestedState + properties for the detailed explanations of the RequestedState + values. + + param_timeoutperiod -- The input parameter TimeoutPeriod ( + type pywbem.CIMDateTime) + A timeout period that specifies the maximum amount of time that + the client expects the transition to the new state to take. + The interval format must be used to specify the TimeoutPeriod. + A value of 0 or a null parameter indicates that the client has + no time requirements for the transition. If this property + does not contain 0 or null and the implementation does not + support this parameter, a return code of 'Use Of Timeout + Parameter Not Supported' shall be returned. + + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 Values.RequestStateChange) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Job -- (type REF (pywbem.CIMInstanceName( + classname='CIM_ConcreteJob', ...)) + May contain a reference to the ConcreteJob created to track the + state transition initiated by the method invocation. + + 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) + """ + out_params = [] + if param_timeoutperiod is not None: + return ( self.values.RequestStateChange. \ + Use_of_Timeout_Parameter_Not_Supported + , out_params) + if param_requestedstate not in { + self.values.RequestStateChange.RequestedState.Enabled, + self.values.RequestStateChange.RequestedState.Disabled }: + return ( self.values.RequestStateChange.Invalid_State_Transition + , out_params) + + with YumDB.get_instance() as ydb: + repo = IdentityResource.object_path2repo(env, + object_name, 'all') + enable = param_requestedstate == \ + self.values.RequestStateChange.RequestedState.Enabled + cmpi_logging.logger.info("%s repository %s" % ("enabling" + if enable else "disabling", repo)) + try: + prev = ydb.set_repository_enabled( + repo, enable) + except errors.RepositoryChangeError as exc: + msg = "failed to modify repository %s: %s" % (repo, str(exc)) + cmpi_logging.logger.error(msg) + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, msg) + msg = ( "repository %s already %s" + if prev == enable else "repository %s %s") + cmpi_logging.logger.info(msg % (repo, + "enabled" if enable else "disabled")) + + rval = self.values.RequestStateChange.Completed_with_No_Error + return (rval, out_params) + diff --git a/src/software/lmi/software/LMI_SoftwareInstallationService.py b/src/software/lmi/software/LMI_SoftwareInstallationService.py new file mode 100644 index 0000000..7b2b73e --- /dev/null +++ b/src/software/lmi/software/LMI_SoftwareInstallationService.py @@ -0,0 +1,862 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 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_SoftwareInstallationService + +Instruments the CIM class LMI_SoftwareInstallationService + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import Job +from lmi.software.core import InstallationService + +class LMI_SoftwareInstallationService(CIMProvider2): + """Instrument the CIM class LMI_SoftwareInstallationService + + A subclass of service which provides methods to install (or update) + Software Identities in ManagedElements. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + self.values = InstallationService.Values + + @cmpi_logging.trace_method + 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) + + """ + InstallationService.check_path(env, model.path, "path") + + objpath = InstallationService.get_path() + + for key, value in objpath.keybindings.items(): + model[key] = value + + model['Caption'] = 'Software installation service for this system.' + model['CommunicationStatus'] = self.values. \ + CommunicationStatus.Not_Available + model['Description'] = \ + 'Software installation service using YUM packake manager.' + model['DetailedStatus'] = self.values.DetailedStatus.Not_Available + model['EnabledDefault'] = self.values.EnabledDefault.Not_Applicable + model['EnabledState'] = self.values.EnabledState.Not_Applicable + model['HealthState'] = self.values.HealthState.OK + model['InstanceID'] = 'LMI:LMI_InstallationService' + model['OperatingStatus'] = self.values.OperatingStatus.Servicing + model['OperationalStatus'] = [self.values.OperationalStatus.OK] + model['PrimaryStatus'] = self.values.PrimaryStatus.OK + model['RequestedState'] = self.values.RequestedState.Not_Applicable + model['Started'] = True + model['TransitioningToState'] = self.values. \ + TransitioningToState.Not_Applicable + return model + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'CreationClassName': None, 'SystemName': None, + 'Name': None, 'SystemCreationClassName': None}) + + objpath = InstallationService.get_path() + for key, value in objpath.keybindings.items(): + model[key] = value + if not keys_only: + model = self.get_instance(env, model) + yield model + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def cim_method_requeststatechange(self, env, object_name, + param_requestedstate=None, + param_timeoutperiod=None): + """Implements LMI_SoftwareInstallationService.RequestStateChange() + + Requests that the state of the element be changed to the value + specified in the RequestedState parameter. When the requested + state change takes place, the EnabledState and RequestedState of + the element will be the same. Invoking the RequestStateChange + method multiple times could result in earlier requests being + overwritten or lost. A return code of 0 shall indicate the state + change was successfully initiated. A return code of 3 shall + indicate that the state transition cannot complete within the + interval specified by the TimeoutPeriod parameter. A return code + of 4096 (0x1000) shall indicate the state change was successfully + initiated, a ConcreteJob has been created, and its reference + returned in the output parameter Job. Any other return code + indicates an error condition. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method RequestStateChange() + should be invoked. + param_requestedstate -- The input parameter RequestedState ( + type pywbem.Uint16 self.Values.RequestStateChange.RequestedState) + The state requested for the element. This information will be + placed into the RequestedState property of the instance if the + return code of the RequestStateChange method is 0 ('Completed + with No Error'), or 4096 (0x1000) ('Job Started'). Refer to + the description of the EnabledState and RequestedState + properties for the detailed explanations of the RequestedState + values. + + param_timeoutperiod -- The input parameter TimeoutPeriod ( + type pywbem.CIMDateTime) + A timeout period that specifies the maximum amount of time that + the client expects the transition to the new state to take. + The interval format must be used to specify the TimeoutPeriod. + A value of 0 or a null parameter indicates that the client has + no time requirements for the transition. If this property + does not contain 0 or null and the implementation does not + support this parameter, a return code of 'Use Of Timeout + Parameter Not Supported' shall be returned. + + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.RequestStateChange) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Job -- (type REF (pywbem.CIMInstanceName( + classname='CIM_ConcreteJob', ...)) + May contain a reference to the ConcreteJob created to track the + state transition initiated by the method invocation. + + + 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) + """ + out_params = [] + out_params += [pywbem.CIMParameter('Job', type='reference', value=None)] + return ( self.values.RequestStateChange.Not_Supported + , out_params) + + @cmpi_logging.trace_method + def cim_method_stopservice(self, env, object_name): + """Implements LMI_SoftwareInstallationService.StopService() + + The StopService method places the Service in the stopped state. + Note that the function of this method overlaps with the + RequestedState property. RequestedState was added to the model to + maintain a record (such as a persisted value) of the last state + request. Invoking the StopService method should set the + RequestedState property appropriately. The method returns an + integer value of 0 if the Service was successfully stopped, 1 if + the request is not supported, and any other number to indicate an + error. In a subclass, the set of possible return codes could be + specified using a ValueMap qualifier on the method. The strings to + which the ValueMap contents are translated can also be specified + in the subclass as a Values array qualifier. Note: The semantics + of this method overlap with the RequestStateChange method that is + inherited from EnabledLogicalElement. This method is maintained + because it has been widely implemented, and its simple "stop" + semantics are convenient to use. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method StopService() + 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) + + """ + rval = pywbem.Uint32(1) # Not Supported + return (rval, []) + + @cmpi_logging.trace_method + def cim_method_installfromuri(self, env, object_name, + param_installoptionsvalues=None, + param_uri=None, + param_installoptions=None, + param_target=None): + """Implements LMI_SoftwareInstallationService.InstallFromURI() + + Start a job to install software from a specific URI in a + ManagedElement. Note that this method is provided to support + existing, alternative download mechanisms (such as used for + firmware download). The 'normal' mechanism will be to use the + InstallFromSoftwareIdentity method. If 0 is returned, the function + completed successfully and no ConcreteJob instance was required. + If 4096/0x1000 is returned, a ConcreteJob will be started to to + perform the install. The Job's reference will be returned in the + output parameter Job. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method InstallFromURI() + should be invoked. + param_installoptionsvalues -- The input parameter \ + InstallOptionsValues (type [unicode,]) + InstallOptionsValues is an array of strings providing + additionalinformation to InstallOptions for the method to + install the software. Each entry of this array is related to + the entry in InstallOptions that is located at the same index + providing additional information for InstallOptions. For + further information on the use of InstallOptionsValues + parameter, see the description of the InstallOptionsValues + parameter of the + InstallationService.InstallFromSoftwareIdentity + method. + + param_uri -- The input parameter URI (type unicode) + A URI for the software based on RFC 2079. + + param_installoptions -- The input parameter InstallOptions ( + type [pywbem.Uint16,] self.Values.InstallFromURI.InstallOptions) + Options to control the install process. See the InstallOptions + parameter of the + InstallationService.InstallFromSoftwareIdentity method + for the description of these values. + + param_target -- The input parameter Target (type REF ( + pywbem.CIMInstanceName(classname='CIM_ManagedElement', ...)) + The installation target. + + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.InstallFromURI) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Job -- (type REF (pywbem.CIMInstanceName( + classname='CIM_ConcreteJob', ...)) + Reference to the job (may be null if job completed). + + + 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) + """ + InstallationService.check_path(env, object_name, "object_name") + out_params = [pywbem.CIMParameter('Job', type='reference', value=None)] + try: + jobid = InstallationService.install_or_remove_package( + env, Job.JOB_METHOD_INSTALL_FROM_URI, + param_uri, param_target, None, param_installoptions, + param_installoptionsvalues) + rval = self.values.InstallFromURI. \ + Method_Parameters_Checked___Job_Started + out_params[0].value = Job.job2model(jobid, + class_name="LMI_SoftwareInstallationJob") + except InstallationService.InstallationError as exc: + cmpi_logging.logger.error( + "installation failed: %s", exc.description) + rval = exc.return_code + return (rval, out_params) + + @cmpi_logging.trace_method + def cim_method_checksoftwareidentity(self, env, object_name, + param_source=None, + param_target=None, + param_collection=None): + """Implements LMI_SoftwareInstallationService.CheckSoftwareIdentity() + + This method allows a client application to determine whether a + specific SoftwareIdentity can be installed (or updated) on a + ManagedElement. It also allows other characteristics to be + determined such as whether install will require a reboot. In + addition a client can check whether the SoftwareIdentity can be + added simulataneously to a specified SofwareIndentityCollection. A + client MAY specify either or both of the Collection and Target + parameters. The Collection parameter is only supported if + SoftwareInstallationServiceCapabilities.CanAddToCollection is + TRUE. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method CheckSoftwareIdentity() + should be invoked. + param_source -- The input parameter Source (type REF ( + pywbem.CIMInstanceName(classname='CIM_SoftwareIdentity', ...)) + Reference to the SoftwareIdentity to be checked. + + param_target -- The input parameter Target (type REF ( + pywbem.CIMInstanceName(classname='CIM_ManagedElement', ...)) + Reference to the ManagedElement that the Software Identity is + going to be installed in (or updated). + + param_collection -- The input parameter Collection (type REF ( + pywbem.CIMInstanceName(classname='CIM_Collection', ...)) + Reference to the Collection to which the Software Identity will + be added. + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.CheckSoftwareIdentity) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + InstallCharacteristics -- (type [pywbem.Uint16,] + self.Values.CheckSoftwareIdentity.InstallCharacteristics) + The parameter describes the characteristics of the + installation/update that will take place if the Source + Software Identity is installed: Target automatic reset: The + target element will automatically reset once the installation + is complete. System automatic reset: The containing system of + the target ManagedElement (normally a logical device or the + system itself) will automatically reset/reboot once the + installation is complete. Separate target reset required: + EnabledLogicalElement.RequestStateChange MUST be used to reset + the target element after the SoftwareIdentity is installed. + Separate system reset required: + EnabledLogicalElement.RequestStateChange MUST be used to + reset/reboot the containing system of the target + ManagedElement after the SoftwareIdentity is installed. + Manual Reboot Required: The system MUST be manually rebooted + by the user. No reboot required : No reboot is required after + installation. User Intervention Recomended : It is + recommended that a user confirm installation of this + SoftwareIdentity. Inappropriate application MAY have serious + consequences. MAY be added to specified collection : The + SoftwareIndentity MAY be added to specified Collection. + + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_METHOD_NOT_AVAILABLE) + + @cmpi_logging.trace_method + def cim_method_changeaffectedelementsassignedsequence(self, + env, object_name, + param_managedelements, + param_assignedsequence): + """Implements LMI_SoftwareInstallationService. \ + ChangeAffectedElementsAssignedSequence() + + This method is called to change relative sequence in which order + the ManagedElements associated to the Service through + CIM_ServiceAffectsElement association are affected. In the case + when the Service represents an interface for client to execute + extrinsic methods and when it is used for grouping of the managed + elements that could be affected, the ordering represents the + relevant priority of the affected managed elements with respect to + each other. An ordered array of ManagedElement instances is + passed to this method, where each ManagedElement instance shall be + already be associated with this Service instance via + CIM_ServiceAffectsElement association. If one of the + ManagedElements is not associated to the Service through + CIM_ServiceAffectsElement association, the implementation shall + return a value of 2 ("Error Occured"). Upon successful execution + of this method, if the AssignedSequence parameter is NULL, the + value of the AssignedSequence property on each instance of + CIM_ServiceAffectsElement shall be updated such that the values of + AssignedSequence properties shall be monotonically increasing in + correlation with the position of the referenced ManagedElement + instance in the ManagedElements input parameter. That is, the + first position in the array shall have the lowest value for + AssignedSequence. The second position shall have the second lowest + value, and so on. Upon successful execution, if the + AssignedSequence parameter is not NULL, the value of the + AssignedSequence property of each instance of + CIM_ServiceAffectsElement referencing the ManagedElement instance + in the ManagedElements array shall be assigned the value of the + corresponding index of the AssignedSequence parameter array. For + ManagedElements instances which are associated with the Service + instance via CIM_ServiceAffectsElement and are not present in the + ManagedElements parameter array, the AssignedSequence property on + the CIM_ServiceAffects association shall be assigned a value of 0. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method + ChangeAffectedElementsAssignedSequence() should be invoked. + param_managedelements -- The input parameter ManagedElements ( + type REF (pywbem.CIMInstanceName( + classname='CIM_ManagedElement', ...)) (Required) + An array of ManagedElements. + + param_assignedsequence -- The input parameter AssignedSequence ( + type [pywbem.Uint16,]) (Required) + An array of integers representing AssignedSequence for the + ManagedElement in the corresponding index of the + ManagedElements parameter. + + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values. \ + ChangeAffectedElementsAssignedSequence) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Job -- (type REF (pywbem.CIMInstanceName( + classname='CIM_ConcreteJob', ...)) + Reference to the job spawned if the operation continues after + the method returns. (May be null if the task is completed). + + + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_METHOD_NOT_AVAILABLE) + + @cmpi_logging.trace_method + def cim_method_installfromsoftwareidentity( + self, env, object_name, + param_installoptions=None, + param_target=None, + param_collection=None, + param_source=None, + param_installoptionsvalues=None): + """Implements LMI_SoftwareInstallationService. \ + InstallFromSoftwareIdentity() + + Start a job to install or update a SoftwareIdentity (Source) on a + ManagedElement (Target). In addition the method can be used to + add the SoftwareIdentity simulataneously to a specified + SofwareIndentityCollection. A client MAY specify either or both of + the Collection and Target parameters. The Collection parameter is + only supported if InstallationService.CanAddToCollection + is TRUE. If 0 is returned, the function completed successfully + and no ConcreteJob instance was required. If 4096/0x1000 is + returned, a ConcreteJob will be started to perform the install. + The Job's reference will be returned in the output parameter Job. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method + InstallFromSoftwareIdentity() + should be invoked. + param_installoptions -- The input parameter InstallOptions ( + type [pywbem.Uint16,] self.Values.InstallFromSoftwareIdentity.\ + InstallOptions) + Options to control the install process. Defer target/system + reset : do not automatically reset the target/system. Force + installation : Force the installation of the same or an older + SoftwareIdentity. Install: Perform an installation of this + software on the managed element. Update: Perform an update of + this software on the managed element. Repair: Perform a repair + of the installation of this software on the managed element by + forcing all the files required for installing the software to + be reinstalled. Reboot: Reboot or reset the system immediately + after the install or update of this software, if the install + or the update requires a reboot or reset. Password: Password + will be specified as clear text without any encryption for + performing the install or update. Uninstall: Uninstall the + software on the managed element. Log: Create a log for the + install or update of the software. SilentMode: Perform the + install or update without displaying any user interface. + AdministrativeMode: Perform the install or update of the + software in the administrative mode. ScheduleInstallAt: + Indicates the time at which theinstall or update of the + software will occur. + + param_target -- The input parameter Target (type REF ( + pywbem.CIMInstanceName(classname='CIM_ManagedElement', ...)) + The installation target. If NULL then the SOftwareIdentity will + be added to Collection only. The underlying implementation is + expected to be able to obtain any necessary metadata from the + Software Identity. + + param_collection -- The input parameter Collection (type REF ( + pywbem.CIMInstanceName(classname='CIM_Collection', ...)) + Reference to the Collection to which the Software Identity + SHALL be added. If NULL then the SOftware Identity will not be + added to a Collection. + + param_source -- The input parameter Source (type REF ( + pywbem.CIMInstanceName(classname='CIM_SoftwareIdentity', ...)) + Reference to the source of the install. + + param_installoptionsvalues -- The input parameter + InstallOptionsValues (type [unicode,]) + InstallOptionsValues is an array of strings providing + additional information to InstallOptions for the method to + install the software. Each entry of this array is related to + the entry in InstallOptions that is located at the same index + providing additional information for InstallOptions. If the + index in InstallOptions has the value "Password " then a value + at the corresponding index of InstallOptionValues shall not be + NULL. If the index in InstallOptions has the value + "ScheduleInstallAt" then the value at the corresponding index + of InstallOptionValues shall not be NULL and shall be in the + datetime type format. If the index in InstallOptions has the + value "Log " then a value at the corresponding index of + InstallOptionValues may be NULL. If the index in + InstallOptions has the value "Defer target/system reset", + "Force installation","Install", "Update", "Repair" or "Reboot" + then a value at the corresponding index of InstallOptionValues + shall be NULL. + + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.InstallFromSoftwareIdentity) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Job -- (type REF (pywbem.CIMInstanceName( + classname='CIM_ConcreteJob', ...)) + Reference to the job (may be null if job completed). + + 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) + """ + InstallationService.check_path(env, object_name, "object_name") + out_params = [pywbem.CIMParameter('Job', type='reference', value=None)] + try: + jobid = InstallationService.install_or_remove_package( + env, Job.JOB_METHOD_INSTALL_FROM_SOFTWARE_IDENTITY, + param_source, param_target, param_collection, + param_installoptions, + param_installoptionsvalues) + rval = self.values.InstallFromSoftwareIdentity. \ + Method_Parameters_Checked___Job_Started + out_params[0].value = Job.job2model(jobid, + class_name="LMI_SoftwareInstallationJob") + except InstallationService.InstallationError as exc: + cmpi_logging.logger.error( + "installation failed: %s", exc.description) + rval = exc.return_code + return (rval, out_params) + + @cmpi_logging.trace_method + def cim_method_startservice(self, env, object_name): + """Implements LMI_SoftwareInstallationService.StartService() + + The StartService method places the Service in the started state. + Note that the function of this method overlaps with the + RequestedState property. RequestedState was added to the model to + maintain a record (such as a persisted value) of the last state + request. Invoking the StartService method should set the + RequestedState property appropriately. The method returns an + integer value of 0 if the Service was successfully started, 1 if + the request is not supported, and any other number to indicate an + error. In a subclass, the set of possible return codes could be + specified using a ValueMap qualifier on the method. The strings to + which the ValueMap contents are translated can also be specified + in the subclass as a Values array qualifier. Note: The semantics + of this method overlap with the RequestStateChange method that is + inherited from EnabledLogicalElement. This method is maintained + because it has been widely implemented, and its simple "start" + semantics are convenient to use. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method StartService() + 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) + """ + rval = pywbem.Uint32(1) # Not Supported + return (rval, []) + + @cmpi_logging.trace_method + def cim_method_installfrombytestream(self, env, object_name, + param_installoptionsvalues=None, + param_image=None, + param_installoptions=None, + param_target=None): + """Implements LMI_SoftwareInstallationService.InstallFromByteStream() + + Start a job to download a series of bytes containing a software + image to a ManagedElement. Note that this method is provided to + support existing, alternative download mechanisms (such as used + for firmware download). The 'normal' mechanism will be to use the + InstallFromSoftwareIdentity method. If 0 is returned, the + function completed successfully and no ConcreteJob instance was + required. If 4096/0x1000 is returned, a ConcreteJob will be + started to to perform the install. The Job's reference will be + returned in the output parameter Job. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method InstallFromByteStream() + should be invoked. + param_installoptionsvalues -- The input parameter InstallOptionsValues (type [unicode,]) + InstallOptionsValues is an array of strings providing + additional information to InstallOptions for the method to + install the software. Each entry of this array is related to + the entry in InstallOptions that is located at the same index + providing additional information for InstallOptions. For + further information on the use of InstallOptionsValues + parameter, see the description of the InstallOptionsValues + parameter of the + InstallationService.InstallFromSoftwareIdentity + method. + + param_image -- The input parameter Image (type [pywbem.Uint8,]) + A array of bytes containing the install image. + + param_installoptions -- The input parameter InstallOptions (type [pywbem.Uint16,] self.Values.InstallFromByteStream.InstallOptions) + Options to control the install process. See the InstallOptions + parameter of the + InstallationService.InstallFromSoftwareIdentity method + for the description of these values. + + param_target -- The input parameter Target (type REF (pywbem.CIMInstanceName(classname='CIM_ManagedElement', ...)) + The installation target. + + + Returns a two-tuple containing the return value (type pywbem.Uint32 self.Values.InstallFromByteStream) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Job -- (type REF (pywbem.CIMInstanceName(classname='CIM_ConcreteJob', ...)) + Reference to the job (may be null if job completed). + + + 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) + + """ + out_params = [pywbem.CIMParameter('Job', type='reference', value=None)] + return ( self.values.InstallFromByteStream.Not_Supported + , out_params) + + @cmpi_logging.trace_method + def cim_method_verifyinstalledidentity(self, env, object_name, + param_source=None, + param_target=None): + """Implements LMI_SoftwareInstallationService. \ + VerifyInstalledIdentity() + + Start a job to verify installed package represented by + SoftwareIdentity (source) (Source) on a ManagedElement (Target). + If 0 is returned, the function completed successfully and no + ConcreteJob instance was required. If 4096/0x1000 is returned, a + ConcreteJob will be started to perform the verification. The Job's + reference will be returned in the output parameter Job. In former + case, the Failed parameterwill contain all associated file checks, + that did not pass. In the latter case this property will be NULL. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method VerifyInstalledIdentity() + should be invoked. + param_source -- The input parameter Source ( + type REF (pywbem.CIMInstanceName( + classname='LMI_SoftwareIdentity', ...)) + Reference to the installed SoftwareIdentity to be verified. + + param_target -- The input parameter Target ( + type REF (pywbem.CIMInstanceName( + classname='CIM_ManagedElement', ...)) + Reference to the ManagedElement that the Software Identity is + installed on. + + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.VerifyInstalledIdentity) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Job -- (type REF (pywbem.CIMInstanceName( + classname='LMI_SoftwareVerificationJob', ...)) + Reference to the job (may be null if job completed). + + Failed -- (type REF (pywbem.CIMInstanceName( + classname='LMI_SoftwareIdentityFileCheck', ...)) + Array of file checks that did not pass verification. This is + NULL in case that asynchronous job has been started. + + + 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) + """ + InstallationService.check_path(env, object_name, "object_name") + out_params = [pywbem.CIMParameter('Job', type='reference', value=None)] + try: + jobid = InstallationService.verify_package( + env, Job.JOB_METHOD_VERIFY_INSTALLED_IDENTITY, + param_source, param_target) + rval = self.values.VerifyInstalledIdentity. \ + Method_Parameters_Checked___Job_Started + out_params[0].value = Job.job2model(jobid, + class_name="LMI_SoftwareVerificationJob") + except InstallationService.InstallationError as exc: + cmpi_logging.logger.error( + "failed to launch verification job: %s", exc.description) + rval = exc.return_code + return (rval, out_params) + diff --git a/src/software/lmi/software/LMI_SoftwareInstallationServiceAffectsElement.py b/src/software/lmi/software/LMI_SoftwareInstallationServiceAffectsElement.py new file mode 100644 index 0000000..92d5c26 --- /dev/null +++ b/src/software/lmi/software/LMI_SoftwareInstallationServiceAffectsElement.py @@ -0,0 +1,299 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 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_SoftwareInstallationServiceAffectsElement + +Instruments the CIM class LMI_SoftwareInstallationServiceAffectsElement +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import ComputerSystem +from lmi.software.core import Identity +from lmi.software.core import InstallationService +from lmi.software.core import InstallationServiceAffectsElement +from lmi.software.yumdb import YumDB + +def generate_service_referents(env, model, object_name, keys_only): + """ + Used in references method. + """ + InstallationService.check_path(env, object_name, "object_name") + InstallationServiceAffectsElement.fill_model_computer_system( + model, keys_only=keys_only) + yield model + + avail_model = pywbem.CIMInstanceName( + classname="LMI_SoftwareIdentity", + namespace="root/cimv2") + for pkg_info in YumDB.get_instance().get_package_list('available'): + model["AffectedElement"] = InstallationServiceAffectsElement. \ + fill_model_identity(model, pkg_info, + keys_only=keys_only, + identity_model=avail_model) + yield model + +class LMI_SoftwareInstallationServiceAffectsElement(CIMProvider2): + """Instrument the CIM class LMI_SoftwareInstallationServiceAffectsElement + + ServiceAffectsElement represents an association between a Service and + the ManagedElements that might be affected by its execution. + Instantiating this association indicates that running the service may + change, manage, provide functionality for,or pose some burden on the + ManagedElement. This burden might affect performance, throughput, + availability, and so on. + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + self.values = InstallationServiceAffectsElement.Values + + @cmpi_logging.trace_method + 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) + """ + InstallationService.check_path_property( + env, model, "AffectingElement") + + ch = env.get_cimom_handle() + + affected = model["AffectedElement"] + if ch.is_subclass(affected.namespace, + sub=affected.classname, super='LMI_SoftwareIdentity'): + pkg_info = Identity.object_path2pkg(affected, kind='available') + InstallationServiceAffectsElement.fill_model_identity( + model, pkg_info, keys_only=False) + elif ch.is_subclass(affected.namespace, + sub=affected.classname, super='Linux_ComputerSystem'): + InstallationServiceAffectsElement.fill_model_computer_system( + model, keys_only=False) + else: + cmpi_logging.logger.error("Unhandled classname: %s", + affected.classname) + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Provided AffectedElement not affected by this service.") + + return model + + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'AffectedElement': None, 'AffectingElement': None}) + + model['AffectingElement'] = InstallationService.get_path() + InstallationServiceAffectsElement.fill_model_computer_system( + model, keys_only=keys_only) + yield model + + avail_model = pywbem.CIMInstanceName( + classname="LMI_SoftwareIdentity", + namespace="root/cimv2") + for pkg_info in YumDB.get_instance().get_package_list('available'): + model["AffectedElement"] = InstallationServiceAffectsElement. \ + fill_model_identity(model, pkg_info, keys_only=keys_only, + identity_model=avail_model) + yield model + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def references(self, env, object_name, model, result_class_name, role, + result_role, keys_only): + """Instrument Associations. + + 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) + """ + ch = env.get_cimom_handle() + + model.path.update({'AffectedElement': None, 'AffectingElement': None}) + try: + if ( (not role or role.lower() == 'affectingelement') + and ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super="LMI_SoftwareInstallationService")): + for ref in generate_service_referents( + env, model, object_name, keys_only): + yield ref + + elif ( (not role or role.lower() == 'affectedelement') + and ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super="CIM_ComputerSystem")): + ComputerSystem.check_path(env, object_name, "object_name") + model = InstallationServiceAffectsElement. \ + fill_model_computer_system(model, keys_only=keys_only) + model["AffectingElement"] = InstallationService.get_path() + yield model + + elif ( (not role or role.lower() == 'affectedelement') + and ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super="LMI_SoftwareIdentity")): + pkg_info = Identity.object_path2pkg(object_name, "available") + model = InstallationServiceAffectsElement. \ + fill_model_identity(model, pkg_info, + keys_only=keys_only) + model["AffectingElement"] = InstallationService.get_path() + yield model + + except pywbem.CIMError as exc: + if exc.args[0] != pywbem.CIM_ERR_NOT_FOUND: + cmpi_logging.logger.exception( + 'failed to generate referents of "%s"', object_name) + raise diff --git a/src/software/lmi/software/LMI_SoftwareInstallationServiceCapabilities.py b/src/software/lmi/software/LMI_SoftwareInstallationServiceCapabilities.py new file mode 100644 index 0000000..739d391 --- /dev/null +++ b/src/software/lmi/software/LMI_SoftwareInstallationServiceCapabilities.py @@ -0,0 +1,292 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 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_SoftwareInstallationServiceCapabilities + +Instruments the CIM class LMI_SoftwareInstallationServiceCapabilities + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import InstallationServiceCapabilities + +class LMI_SoftwareInstallationServiceCapabilities(CIMProvider2): + """Instrument the CIM class LMI_SoftwareInstallationServiceCapabilities + + A subclass of capabilities that defines the capabilities of a + SoftwareInstallationService. A single instance of + SoftwareInstallationServiceCapabilities is associated with a + SoftwareInstallationService using ElementCapabilities. + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + self.values = InstallationServiceCapabilities.Values + + @cmpi_logging.trace_method + 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) + """ + InstallationServiceCapabilities.check_path(env, + model.path, "path") + return InstallationServiceCapabilities.get_instance(model) + + @cmpi_logging.trace_method + 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) + """ + model.path.update({'InstanceID': None}) + + if keys_only: + model['InstanceID'] = \ + InstallationServiceCapabilities.get_path()['InstanceID'] + else: + model = InstallationServiceCapabilities.get_instance(model) + yield model + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + def cim_method_creategoalsettings(self, env, object_name, + param_supportedgoalsettings=None, + param_templategoalsettings=None): + """Implements LMI_SoftwareInstallationServiceCapabilities. \ + CreateGoalSettings() + + Method to create a set of supported SettingData elements, from two + sets of SettingData elements, provided by the caller. CreateGoal + should be used when the SettingData instances that represents the + goal will not persist beyond the execution of the client and where + those instances are not intended to be shared with other, + non-cooperating clients. Both TemplateGoalSettings and + SupportedGoalSettings are represented as strings containing + EmbeddedInstances of a CIM_SettingData subclass. These embedded + instances do not exist in the infrastructure supporting this + method but are maintained by the caller/client. This method + should return CIM_Error(s) representing that a single named + property of a setting (or other) parameter (either reference or + embedded object) has an invalid value or that an invalid + combination of named properties of a setting (or other) parameter + (either reference or embedded object) has been requested. If the + input TemplateGoalSettings is NULL or the empty string, this + method returns a default SettingData element that is supported by + this Capabilities element. If the TemplateGoalSettings specifies + values that cannot be supported, this method shall return an + appropriate CIM_Error and should return a best match for a + SupportedGoalSettings. The client proposes a goal using the + TemplateGoalSettings parameter and gets back Success if the + TemplateGoalSettings is exactly supportable. It gets back + "Alternative Proposed" if the output SupportedGoalSettings + represents a supported alternative. This alternative should be a + best match, as defined by the implementation. If the + implementation is conformant to a RegisteredProfile, then that + profile may specify the algorithms used to determine best match. A + client may compare the returned value of each property against the + requested value to determine if it is left unchanged, degraded or + upgraded. Otherwise, if the TemplateGoalSettings is not + applicable an "Invalid Parameter" error is returned. When a + mutually acceptable SupportedGoalSettings has been achieved, the + client may use the contained SettingData instances as input to + methods for creating a new object ormodifying an existing object. + Also the embedded SettingData instances returned in the + SupportedGoalSettings may be instantiated via CreateInstance, + either by a client or as a side-effect of the execution of an + extrinsic method for which the returned SupportedGoalSettings is + passed as an embedded instance. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method CreateGoalSettings() + should be invoked. + param_supportedgoalsettings -- The input parameter + SupportedGoalSettings (type pywbem.CIMInstance( + classname='CIM_SettingData', ...)) + SupportedGoalSettings are elements of class CIM_SettingData, or + a derived class. At most, one instance of each SettingData + subclass may be supplied. All SettingData instances provided + by this property are interpreted as a set, relative to this + Capabilities instance. To enable a client to provide + additional information towards achieving the + TemplateGoalSettings, an input set of SettingData instances + may be provided. If not provided, this property shall be set + to NULL on input.. Note that when provided, what property + values are changed, and how, is implementation dependent and + may be the subject of other standards. If provided, the input + SettingData instances must be ones that the implementation is + able to support relative to the ManagedElement associated via + ElementCapabilities. Typically, the input SettingData + instances are created by a previous instantiation of + CreateGoalSettings. If the input SupportedGoalSettings is not + supported by the implementation, then an "Invalid Parameter" + (5) error is returned by this call. In this case, a + corresponding CIM_ERROR should also be returned. On output, + this property is used to return the best supported match to + the TemplateGoalSettings. If the output SupportedGoalSettings + matches the input SupportedGoalSettings, then the + implementation is unable to improve further towards meeting + the TemplateGoalSettings. + + param_templategoalsettings -- The input parameter + TemplateGoalSettings (type pywbem.CIMInstance( + classname='CIM_SettingData', ...)) + If provided, TemplateGoalSettings are elements of class + CIM_SettingData, or a derived class, that is used as the + template to be matched. . At most, one instance of each + SettingData subclass may be supplied. All SettingData + instances provided by this property are interpreted as a set, + relative to this Capabilities instance. SettingData instances + that are not relevant to this instance are ignored. If not + provided, it shall be set to NULL. In that case, a SettingData + instance representing the default settings of the associated + ManagedElement is used. + + + Returns a two-tuple containing the return value (type pywbem.Uint16 + self.Values.CreateGoalSettings) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + SupportedGoalSettings -- (type + pywbem.CIMInstance(classname='CIM_SettingData', ...)) + SupportedGoalSettings are elements of class CIM_SettingData, or + a derived class. At most, one instance of each SettingData + subclass may be supplied. All SettingData instances provided + by this property are interpreted as a set, relative to this + Capabilities instance. To enable a client to provide + additional information towards achieving the + TemplateGoalSettings, an input set of SettingData instances + may be provided. If not provided, this property shall be set + to NULL on input.. Note that when provided, what property + values are changed, and how, is implementation dependent and + may be the subject of other standards. If provided, the input + SettingData instances must be ones that the implementation is + able to support relative to the ManagedElement associated via + ElementCapabilities. Typically, the input SettingData + instances are created by a previous instantiation of + CreateGoalSettings. If the input SupportedGoalSettings is not + supported by the implementation, then an "Invalid Parameter" + (5) error is returned by this call. In this case, a + corresponding CIM_ERROR should also be returned. On output, + this property is used to return the best supported match to + the TemplateGoalSettings. If the output SupportedGoalSettings + matches the input SupportedGoalSettings, then the + implementation is unable to improve further towards meeting + the TemplateGoalSettings. + + + 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) + """ + return (self.values.CreateGoalSettings.Not_Supported, []) + diff --git a/src/software/lmi/software/LMI_SoftwareJob.py b/src/software/lmi/software/LMI_SoftwareJob.py new file mode 100644 index 0000000..79275bf --- /dev/null +++ b/src/software/lmi/software/LMI_SoftwareJob.py @@ -0,0 +1,385 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 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_SoftwareInstallationJob and +LMI_SoftwareVerificationJob. They will be referred to as LMI_SoftwareJob, +which is their base class. + +Instruments the CIM class LMI_SoftwareJob +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import Job +from lmi.software.yumdb import errors, YumDB + +class LMI_SoftwareJob(CIMProvider2): + """Instrument the CIM class LMI_SoftwareJob + + A concrete version of Job. This class represents a generic and + instantiable unit of work, such as a batch or a print job. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + self.values = Job.Values + + @cmpi_logging.trace_method + 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) + """ + job = Job.object_path2job(model.path) + return Job.job2model(job, keys_only=False, model=model) + + @cmpi_logging.trace_method + 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) + """ + # 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({'InstanceID': None}) + + ch = env.get_cimom_handle() + for job in YumDB.get_instance().get_job_list(): + if ch.is_subclass(model.path.namespace, + sub=model.path.classname, + super=Job.job_class2cim_class_name(job.__class__)): + yield Job.job2model(job, keys_only=keys_only, model=model) + + @cmpi_logging.trace_method + def set_instance(self, env, instance, modify_existing): + """Return a newly created or modified instance. + + 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) + + """ + if not modify_existing: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED, + "Can not create new instance.") + return Job.modify_instance(instance) + + @cmpi_logging.trace_method + 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) + """ + with YumDB.get_instance() as ydb: + job = Job.object_path2job(instance_name) + try: + cmpi_logging.logger.info('deleting job "%s"' % job) + ydb.delete_job(job.jobid) + cmpi_logging.logger.info('job "%s" removed' % job) + except errors.JobNotFound as exc: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + getattr(exc, 'message', str(exc))) + except errors.JobControlError as exc: + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, + getattr(exc, 'message', str(exc))) + + @cmpi_logging.trace_method + def cim_method_requeststatechange(self, env, object_name, + param_requestedstate=None, + param_timeoutperiod=None): + """Implements LMI_SoftwareJob.RequestStateChange() + + Requests that the state of the job be changed to the value + specified in the RequestedState parameter. Invoking the + RequestStateChange method multiple times could result in earlier + requests being overwritten or lost. If 0 is returned, then the + task completed successfully. Any other return code indicates an + error condition. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method RequestStateChange() + should be invoked. + param_requestedstate -- The input parameter RequestedState ( + type pywbem.Uint16 + self.Values.RequestStateChange.RequestedState) + RequestStateChange changes the state of a job. The possible + values are as follows: Start (2) changes the state to + 'Running'. Suspend (3) stops the job temporarily. The + intention is to subsequently restart the job with 'Start'. It + might be possible to enter the 'Service' state while + suspended. (This is job-specific.) Terminate (4) stops the + job cleanly, saving data, preserving the state, and shutting + down all underlying processes in an orderly manner. Kill (5) + terminates the job immediately with no requirement to save + data or preserve the state. Service (6) puts the job into a + vendor-specific service state. It might be possible to restart + the job. + + param_timeoutperiod -- The input parameter TimeoutPeriod ( + type pywbem.CIMDateTime) + A timeout period that specifies the maximum amount of time that + the client expects the transition to the new state to take. + The interval format must be used to specify the TimeoutPeriod. + A value of 0 or a null parameter indicates that the client has + no time requirements for the transition. If this property + does not contain 0 or null and the implementation does not + support this parameter, a return code of 'Use Of Timeout + Parameter Not Supported' must be returned. + + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.RequestStateChange) + 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) + """ + job = Job.object_path2job(object_name) + try: + if param_requestedstate not in { + self.values.RequestStateChange.RequestedState.Terminate }: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Valid RequestedState can by only Terminate (%d)" % + self.values.RequestStateChange.RequestedState.Terminate) + if param_timeoutperiod: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Timeout period is not supported.") + YumDB.get_instance().terminate_job(job.jobid) + except errors.JobNotFound as exc: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + getattr(exc, 'message', str(exc))) + except errors.JobControlError as exc: + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, + getattr(exc, 'message', str(exc))) + return (self.values.GetErrors.Success, []) + + @cmpi_logging.trace_method + def cim_method_geterrors(self, env, object_name): + """Implements LMI_SoftwareJob.GetErrors() + + If JobState is "Completed" and Operational Status is "Completed" + then no instance of CIM_Error is returned. If JobState is + "Exception" then GetErrors may return intances of CIM_Error + related to the execution of the procedure or method invoked by the + job. If Operatational Status is not "OK" or "Completed"then + GetErrors may return CIM_Error instances related to the running of + the job. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method GetErrors() + should be invoked. + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.GetErrors) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Errors -- (type pywbem.CIMInstance(classname='CIM_Error', ...)) + If the OperationalStatus on the Job is not "OK", then this + method will return one or more CIM Error instance(s). + Otherwise, when the Job is "OK", null is returned. + + + 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) + """ + job = Job.object_path2job(object_name) + error = Job.job2error(job) + out_params = [] + if error is not None: + param = pywbem.CIMParameter('Errors', type='instance', + is_array=True, array_size=1, value=[error]) + out_params.append(param) + return (self.values.GetErrors.Success, out_params) + + @cmpi_logging.trace_method + def cim_method_killjob(self, env, object_name, + param_deleteonkill=None): + """Implements LMI_SoftwareJob.KillJob() + + KillJob is being deprecated because there is no distinction made + between an orderly shutdown and an immediate kill. + CIM_ConcreteJob.RequestStateChange() provides 'Terminate' and + 'Kill' options to allow this distinction. A method to kill this + job and any underlying processes, and to remove any 'dangling' + associations. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method KillJob() + should be invoked. + param_deleteonkill -- The input parameter DeleteOnKill (type bool) + Indicates whether or not the Job should be automatically + deleted upon termination. This parameter takes precedence over + the property, DeleteOnCompletion. + + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.KillJob) + 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) + """ + raise pywbem.CIMError(pywbem.CIM_ERR_METHOD_NOT_AVAILABLE) + + @cmpi_logging.trace_method + def cim_method_geterror(self, env, object_name): + """Implements LMI_SoftwareJob.GetError() + + GetError is deprecated because Error should be an array,not a + scalar. When the job is executing or has terminated without error, + then this method returns no CIM_Error instance. However, if the + job has failed because of some internal problem or because the job + has been terminated by a client, then a CIM_Error instance is + returned. + + Keyword arguments: + env -- Provider Environment (pycimmb.ProviderEnvironment) + object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName + specifying the object on which the method GetError() + should be invoked. + + Returns a two-tuple containing the return value ( + type pywbem.Uint32 self.Values.GetError) + and a list of CIMParameter objects representing the output parameters + + Output parameters: + Error -- (type pywbem.CIMInstance(classname='CIM_Error', ...)) + If the OperationalStatus on the Job is not "OK", then this + method will return a CIM Error instance. Otherwise, when the + Job is "OK", null is returned. + + + 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) + """ + job = Job.object_path2job(object_name) + error = Job.job2error(job) + out_params = [] + if error is not None: + param = pywbem.CIMParameter('Error', type='instance', value=error) + out_params.append(param) + return (self.values.GetErrors.Success, out_params) diff --git a/src/software/lmi/software/LMI_SoftwareMethodResult.py b/src/software/lmi/software/LMI_SoftwareMethodResult.py new file mode 100644 index 0000000..b4d5a21 --- /dev/null +++ b/src/software/lmi/software/LMI_SoftwareMethodResult.py @@ -0,0 +1,163 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 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_SoftwareMethodResult + +Instruments the CIM class LMI_SoftwareMethodResult + +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import MethodResult +from lmi.software.yumdb import YumDB + +class LMI_SoftwareMethodResult(CIMProvider2): + """Instrument the CIM class LMI_SoftwareMethodResult + + Jobs are sometimes used to represent extrinsic method invocations that + execute for times longer than the length of time is reasonable to + require a client to wait. The method executing continues beyond the + method return to the client. The class provides the result of the + execution of a Job that was itself started by the side-effect of this + extrinsic method invocation. The indication instances embedded an + instance of this class shall be the same indications delivered to + listening clients or recorded, all or in part, to logs. Basically, + this approach is a corollary to the functionality provided by an + instance of ListenerDestinationLog (as defined in the Interop Model). + The latter provides a comprehensive, persistent mechanism for + recording Job results, but is also more resource-intensive and + requires supporting logging functionality. Both the extra resources + and logging may not be available in all environments (for example, + embedded environments). Therefore, this instance-based approach is + also provided. The MethodResult instances shall not exist after the + associated ConcreteJob is deleted. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.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) + + """ + job = MethodResult.object_path2job(model.path) + return MethodResult.job2model(job, 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) + + """ + # 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({'InstanceID': None}) + + for job in YumDB.get_instance().get_job_list(): + yield MethodResult.job2model(job, keys_only=keys_only, model=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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) diff --git a/src/software/lmi/software/LMI_SystemSoftwareCollection.py b/src/software/lmi/software/LMI_SystemSoftwareCollection.py new file mode 100644 index 0000000..6aea1d9 --- /dev/null +++ b/src/software/lmi/software/LMI_SystemSoftwareCollection.py @@ -0,0 +1,173 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 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_SystemSoftwareCollection + +Instruments the CIM class LMI_SystemSoftwareCollection +""" + +import pywbem +from pywbem.cim_provider2 import CIMProvider2 + +from lmi.common import cmpi_logging +from lmi.software.core import SystemCollection + +class LMI_SystemSoftwareCollection(CIMProvider2): + """Instrument the CIM class LMI_SystemSoftwareCollection + + SystemSoftwareCollection represents the general concept of a collection + that is scoped (or contained) by a System. It represents a Collection + that has meaning only in the context of a System, a Collection whose + elements are restricted by the definition of the System, or both of + these types of Collections. This meaning is explicitly described by + the (required) association, HostedCollection. An example of a + SystemSoftwareCollection is a Fibre Channel zone that collects network + ports, port groupings, and aliases (as required by a customer) in the + context of an AdminDomain. The Collection is not a part of the domain, + but merely an arbitrary grouping of the devices and other Collections + in the domain. In other words, the context of the Collection is + restricted to the domain, and its members are also limited by the + domain. + + """ + + def __init__ (self, _env): + cmpi_logging.logger.debug('Initializing provider %s from %s' \ + % (self.__class__.__name__, __file__)) + + @cmpi_logging.trace_method + def get_instance(self, env, model): + """Return an instance. + + 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) + + """ + if not 'InstanceID' in model: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Missing InstanceID key property") + if model['InstanceID'] != SystemCollection.get_path()['InstanceID']: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No such instance.") + + model['Caption'] = "System RPM Package Collection" + #model['Description'] = '' # TODO + #model['ElementName'] = '' # TODO + return model + + @cmpi_logging.trace_method + 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) + + """ + # 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({'InstanceID': None}) + + model['InstanceID'] = SystemCollection.get_path()["InstanceID"] + if keys_only is False: + yield self.get_instance(env, model) + else: + yield model + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + + @cmpi_logging.trace_method + 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) + + """ + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + diff --git a/src/software/lmi/software/__init__.py b/src/software/lmi/software/__init__.py new file mode 100644 index 0000000..ffdae3f --- /dev/null +++ b/src/software/lmi/software/__init__.py @@ -0,0 +1,25 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +""" +CIM providers for software management. +Part of OpenLMI project. +""" diff --git a/src/software/lmi/software/cimom_entry.py b/src/software/lmi/software/cimom_entry.py new file mode 100644 index 0000000..89ad17f --- /dev/null +++ b/src/software/lmi/software/cimom_entry.py @@ -0,0 +1,212 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +""" +Entry module for OpenLMI Software providers. +""" + +from multiprocessing import Queue +from lmi.common import cmpi_logging +from lmi.common import JobManager +from lmi.common.IndicationManager import IndicationManager +from lmi.software.core import Job +from lmi.software.LMI_SoftwareIdentity import LMI_SoftwareIdentity +from lmi.software.LMI_SystemSoftwareCollection import \ + LMI_SystemSoftwareCollection +from lmi.software.LMI_HostedSoftwareCollection import \ + LMI_HostedSoftwareCollection +from lmi.software.LMI_MemberOfSoftwareCollection import \ + LMI_MemberOfSoftwareCollection +from lmi.software.LMI_InstalledSoftwareIdentity import \ + LMI_InstalledSoftwareIdentity +from lmi.software.LMI_SoftwareIdentityResource import \ + LMI_SoftwareIdentityResource +from lmi.software.LMI_ResourceForSoftwareIdentity import \ + LMI_ResourceForSoftwareIdentity +from lmi.software.LMI_HostedSoftwareIdentityResource import \ + LMI_HostedSoftwareIdentityResource +from lmi.software.LMI_SoftwareInstallationService import \ + LMI_SoftwareInstallationService +from lmi.software.LMI_SoftwareInstallationServiceCapabilities import \ + LMI_SoftwareInstallationServiceCapabilities +from lmi.software.LMI_AssociatedSoftwareInstallationServiceCapabilities \ + import LMI_AssociatedSoftwareInstallationServiceCapabilities +from lmi.software.LMI_HostedSoftwareInstallationService import \ + LMI_HostedSoftwareInstallationService +from lmi.software.LMI_SoftwareInstallationServiceAffectsElement import \ + LMI_SoftwareInstallationServiceAffectsElement +from lmi.software.LMI_SoftwareJob import LMI_SoftwareJob +from lmi.software.LMI_SoftwareMethodResult import LMI_SoftwareMethodResult +from lmi.software.LMI_AffectedSoftwareJobElement import \ + LMI_AffectedSoftwareJobElement +from lmi.software.LMI_AssociatedSoftwareJobMethodResult import \ + LMI_AssociatedSoftwareJobMethodResult +from lmi.software.LMI_OwningSoftwareJobElement import \ + LMI_OwningSoftwareJobElement +from lmi.software.LMI_SoftwareIdentityFileCheck import \ + LMI_SoftwareIdentityFileCheck +from lmi.software.LMI_SoftwareIdentityChecks import LMI_SoftwareIdentityChecks +from lmi.software.yumdb import jobmanager, YumDB + +def get_providers(env): + """ + @return mapping of provider names to corresponding provider instances. + """ + cmpi_logging.LogManager.LOGGER_NAME = 'lmi.software' + cmpi_logging.LogManager(env) + + # jobmanager does not understand CIM models, give it a way to transform + # job to CIMIndication instance + jobmanager.JOB_TO_MODEL = lambda job: Job.job2model(job, keys_only=False) + + providers = { + "LMI_SoftwareIdentity" : LMI_SoftwareIdentity(env), + "LMI_SystemSoftwareCollection" : LMI_SystemSoftwareCollection(env), + "LMI_HostedSoftwareCollection" : LMI_HostedSoftwareCollection(env), + "LMI_MemberOfSoftwareCollection" : LMI_MemberOfSoftwareCollection(env), + "LMI_InstalledSoftwareIdentity" : LMI_InstalledSoftwareIdentity(env), + "LMI_SoftwareIdentityResource" : LMI_SoftwareIdentityResource(env), + "LMI_ResourceForSoftwareIdentity" : + LMI_ResourceForSoftwareIdentity(env), + "LMI_HostedSoftwareIdentityResource" : + LMI_HostedSoftwareIdentityResource(env), + "LMI_SoftwareInstallationService" : \ + LMI_SoftwareInstallationService(env), + "LMI_SoftwareInstallationServiceCapabilities" : \ + LMI_SoftwareInstallationServiceCapabilities(env), + "LMI_AssociatedSoftwareInstallationServiceCapabilities" : \ + LMI_AssociatedSoftwareInstallationServiceCapabilities(env), + "LMI_HostedSoftwareInstallationService" : \ + LMI_HostedSoftwareInstallationService(env), + "LMI_SoftwareInstallationServiceAffectsElement" : \ + LMI_SoftwareInstallationServiceAffectsElement(env), + "LMI_SoftwareJob" : LMI_SoftwareJob(env), + "LMI_SoftwareInstallationJob" : LMI_SoftwareJob(env), + "LMI_SoftwareVerificationJob" : LMI_SoftwareJob(env), + "LMI_SoftwareInstCreation" : LMI_SoftwareJob(env), + "LMI_SoftwareInstModification" : LMI_SoftwareJob(env), + "LMI_SoftwareMethodResult" : LMI_SoftwareMethodResult(env), + "LMI_AffectedSoftwareJobElement" : LMI_AffectedSoftwareJobElement(env), + "LMI_AssociatedSoftwareJobMethodResult" : \ + LMI_AssociatedSoftwareJobMethodResult(env), + "LMI_OwningSoftwareJobElement" : LMI_OwningSoftwareJobElement(env), + "LMI_SoftwareIdentityFileCheck" : LMI_SoftwareIdentityFileCheck(env), + "LMI_SoftwareIdentityChecks" : LMI_SoftwareIdentityChecks(env) + } + + # Initialization of indication manager -- running in separate thread as + # daemon. That means it does not have to be cleaned up. + im = IndicationManager.get_instance( + env, "Software", "root/cimv2", queue=Queue()) + JobManager.register_filters("LMI_SoftwareInstallationJob", im) + JobManager.register_filters("LMI_SoftwareVerificationJob", im) + + return providers + +def authorize_filter(env, fltr, class_name, op, owner): + """ + CIMOM callback. + + It asks us to verify whether this filter is allowed. + + :param fltr: (``String``) Contains the filter that must be authorized. + :param class_name: (``String``) Contains the class name extracted + from the filter FROM clause. + :param op: The name of the class for which monitoring is required. + Only the namespace part is set if className is a process indication. + :param owner The owner argument is the destination owner. + """ + IndicationManager.get_instance().authorize_filter( + env, fltr, class_name, op, owner) + +def activate_filter(env, fltr, class_name, class_path, first_activation): + """ + CIMOM callback. + + It ask us to begin monitoring a resource. The function shall begin + monitoring the resource according to the filter express only. + + :param fltr: (``String``) The filter argument contains the filter + specification for this subscription to become active. + :param class_name: (``String``) The class name extracted from the filter + FROM clause. + :param class_path: (``CIMInstanceName``) The name of the class for which + monitoring is required. Only the namespace part is set if eventType + is a process indication. + :param first_activation: (``bool``) Set to true if this is the first + filter for className. + """ + IndicationManager.get_instance().activate_filter( + env, fltr, class_name, class_path, first_activation) + +def deactivate_filter(env, fltr, class_name, class_path, last_activation): + """ + CIMOM callback. + + Informs us that monitoring using this filter should stop. + + :param fltr: (``String``) The filter argument contains the filter + specification for this subscription to become active. + :param class_name: (``String``) The class name extracted from the filter + FROM clause. + :param class_path: (``CIMInstanceName``) class_path The name of the class + for which monitoring is required. Only the namespace part is set + if className is a process indication. + :last_activation: (``bool``) Set to true if this is the last filter for + className. + """ + IndicationManager.get_instance().deactivate_filter( + env, fltr, class_name, class_path, last_activation) + +def enable_indications(env): + """ + CIMOM callback. + + Tells us that indications can now be generated. The MB is now prepared + to process indications. The function is normally called by the MB after + having done its intialization and processing of persistent subscription + requests. + """ + IndicationManager.get_instance().enable_indications(env) + +def disable_indications(env): + """ + CIMOM callback. + + Tells us that we should stop generating indications. MB will not accept any + indications until enabled again. The function is normally called when the + MB is shutting down indication services either temporarily or permanently. + """ + IndicationManager.get_instance().disable_indications(env) + +def can_unload(_env): + """ + Says, whether providers can be unloaded. + """ + return True + +def shutdown(_env): + """ + Release resources upon cleanup. + """ + if YumDB.isInstantiated(): + YumDB.get_instance().clean_up() diff --git a/src/software/lmi/software/core/AffectedSoftwareJobElement.py b/src/software/lmi/software/core/AffectedSoftwareJobElement.py new file mode 100644 index 0000000..90014eb --- /dev/null +++ b/src/software/lmi/software/core/AffectedSoftwareJobElement.py @@ -0,0 +1,264 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to AffectedSoftwareJobElement provider. +""" + +import pywbem + +from lmi.common import cmpi_logging +from lmi.software import util +from lmi.software.core import ComputerSystem +from lmi.software.core import Identity +from lmi.software.core import IdentityFileCheck +from lmi.software.core import Job +from lmi.software.core import SystemCollection +from lmi.software.yumdb import jobs +from lmi.software.yumdb import PackageInfo + +class Values(object): + class ElementEffects(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Exclusive_Use = pywbem.Uint16(2) + Performance_Impact = pywbem.Uint16(3) + Element_Integrity = pywbem.Uint16(4) + Create = pywbem.Uint16(5) + _reverse_map = { + 0: 'Unknown', + 1: 'Other', + 2: 'Exclusive Use', + 3: 'Performance Impact', + 4: 'Element Integrity', + 5: 'Create' + } + +@cmpi_logging.trace_function +def check_path(env, op): + """ + Checks, whether object path is valid. + + Return internal object representing job and object path of affected element + as a pair: ``(job, affected)``. + """ + if not isinstance(op, pywbem.CIMInstanceName): + raise TypeError("op must be a CIMInstanceName") + ch = env.get_cimom_handle() + + job = op['AffectingElement'] = Job.object_path2job(op['AffectingElement']) + affected = op['AffectedElement'] + if ch.is_subclass(affected.namespace, sub=affected.classname, + super='LMI_SoftwareIdentity'): + pkg_info = Identity.object_path2pkg(affected, kind='all') + if isinstance(job, jobs.YumSpecificPackageJob): + if isinstance(job.pkg, PackageInfo): + if pkg_info != job.pkg: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "AffectedElement does not match job's package:" + " \"%s\" != \"%s\"." % (pkg_info, job.pkg)) + else: + flt = pkg_info.key_props + flt.pop('repoid', None) + if util.nevra2filter(job.pkg) != flt: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "AffectedElement does not match job's package:" + " \"%s\" != \"%s\"." % (pkg_info, job.pkg)) + affected = Identity.pkg2model(pkg_info) + elif isinstance(job, jobs.YumInstallPackageFromURI): + if job.state == job.COMPLETED: + affected = Identity.pkg2model(job.result_data) + else: + # TODO: this should be somehow obtained from downloaded + # package before installation + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No SoftwareIdentity is associated to given job.") + else: + cmpi_logging.logger.error("Unsupported async job: %s", job) + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No associated SoftwareIdentity.") + elif ch.is_subclass(affected.namespace, sub=affected.classname, + super='LMI_SystemSoftwareCollection'): + SystemCollection.check_path(env, affected, "AffectedElement") + affected = SystemCollection.get_path() + elif ch.is_subclass(affected.namespace, sub=affected.classname, + super='Linux_ComputerSystem'): + ComputerSystem.check_path(env, affected, "AffectedElement") + affected = ComputerSystem.get_path() + elif ch.is_subclass(affected.namespace, sub=affected.classname, + super='LMI_SoftwareIdentityFileCheck'): + if not isinstance(job, jobs.YumCheckPackage): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Job must point to verification job, not to: \"%s\"" % job) + if job.state != job.COMPLETED: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Associations to failed file checks for verification job" + " \"%s\" are not yet known, since job has not completed" + " yet." % op["AffectingElement"]["InstanceID"]) + pkg_info, _ = job.result_data + file_check = IdentityFileCheck.object_path2file_check(affected) + if file_check.pkg_info.nevra != pkg_info.nevra: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Affected element associated to job for another package:" + " \"%s\" != \"%s\"." % (file_check.pkg_info, pkg_info)) + if IdentityFileCheck.file_check_passed(file_check): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Given file check reference passed the verification.") + else: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Expected an instance of LMI_SoftwareIdentity," + " LMI_SystemSoftwareCollection or Linux_ComputerSystem.") + return (job, affected) + +@cmpi_logging.trace_function +def job2affected_software_identity(job): + """ + @return (path of SoftwareIdentity, ElementEffects array, + OtherElementEffectsDescriptions array) + """ + effects = [Values.ElementEffects.Other] + descriptions = [] + if isinstance(job, jobs.YumSpecificPackageJob): + if job.state == job.COMPLETED and job.result_data: + if isinstance(job, jobs.YumCheckPackage): + # get the first item out of (pkg_info, pkg_check) + affected = Identity.pkg2model(job.result_data[0]) + else: + affected = Identity.pkg2model(job.result_data) + else: + affected = Identity.pkg2model(job.pkg) + if isinstance(job, jobs.YumInstallPackage): + descriptions.append("Installing") + elif isinstance(job, jobs.YumRemovePackage): + descriptions.append("Removing") + elif isinstance(job, + (jobs.YumUpdatePackage, jobs.YumUpdateToPackage)): + descriptions.append("Updating") + elif isinstance(job, jobs.YumCheckPackage): + descriptions.append("Verifying") + else: + descriptions.append("Modifying") + cmpi_logging.logger.error("Unhandled job: %s", job) + elif isinstance(job, jobs.YumInstallPackageFromURI): + if job.state == job.COMPLETED: + affected = Identity.pkg2model(job.result_data) + descriptions.append("Installing") + else: + # TODO: this should be somehow obtained from from downloaded + # package before installation + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No SoftwareIdentity is associated to given job.") + else: + cmpi_logging.logger.error("Unsupported async job: %s", job) + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "No associated SoftwareIdentity.") + return (affected, effects, descriptions) + +@cmpi_logging.trace_function +def fill_model_computer_system(model, job, keys_only=True): + """ + Fills model's AffectedElement and all non-key properties. + """ + model["AffectedElement"] = ComputerSystem.get_path() + if not keys_only: + model["ElementEffects"] = [Values.ElementEffects.Other] + description = "Modifying software collection." + if isinstance(job, (jobs.YumInstallPackage, + jobs.YumInstallPackageFromURI)): + description = "Installing software package to collection." + elif isinstance(job, jobs.YumRemovePackage): + description = "Removing package from software collection." + elif isinstance(job, (jobs.YumUpdatePackage, jobs.YumUpdateToPackage)): + description = "Updating software package." + model["OtherElementEffectsDescriptions"] = [description] + return model + +@cmpi_logging.trace_function +def fill_model_system_collection(model, keys_only=True): + """ + Fills model's AffectedElement and all non-key properties. + """ + model["AffectedElement"] = SystemCollection.get_path() + if not keys_only: + model["ElementEffects"] = [Values.ElementEffects.Exclusive_Use] + model["OtherElementEffectsDescriptions"] = [ + "Package database is locked." + ] + return model + +@cmpi_logging.trace_function +def fill_model_failed_check(model, failed_check, keys_only=True): + """ + Fills model's AffectedElement and all non-key properties. + + :param failed_check: (``CIMInstanceName``) Is on object path of failed + file check. + """ + if not isinstance(failed_check, pywbem.CIMInstanceName): + raise TypeError("failed_check must be a CIMInstanceName") + model["AffectedElement"] = failed_check + if not keys_only: + model["ElementEffects"] = [Values.ElementEffects.Other] + model["OtherElementEffectsDescriptions"] = [ + "File did not pass the verification." + ] + return model + +@cmpi_logging.trace_function +def generate_failed_checks(model, job, keys_only=True): + """ + Generates associations between LMI_SoftwareVerificationJob and + LMI_SoftwareIdentityFileCheck for files that did not pass the check. + """ + out_params = Job.get_verification_out_params(job) + if not "Failed" in out_params: + return + for failed in out_params["Failed"].value: + yield fill_model_failed_check(model, failed, keys_only) + +@cmpi_logging.trace_function +def generate_models_from_job(job, keys_only=True, model=None): + """ + Generates all associations between job and affected elements. + """ + if not isinstance(job, jobs.YumJob): + raise TypeError("pkg must be an instance of PackageInfo or nevra") + if model is None: + model = pywbem.CIMInstanceName("LMI_AffectedSoftwareJobElement", + namespace="root/cimv2") + if not keys_only: + model = pywbem.CIMInstance("LMI_AffectedSoftwareJobElement", + path=model) + model["AffectingElement"] = Job.job2model(job) + (si, element_effects, element_effects_descriptions) = \ + job2affected_software_identity(job) + model["AffectedElement"] = si + if not keys_only: + model["ElementEffects"] = element_effects + model["OtherElementEffectsDescriptions"] = \ + element_effects_descriptions + yield model + if not isinstance(job, jobs.YumCheckPackage): + fill_model_system_collection(model, keys_only=keys_only) + yield model + else: # package verification - associate to failed file checks + for model in generate_failed_checks(model, job, keys_only=keys_only): + yield model + fill_model_computer_system(model, job, keys_only=keys_only) + yield model + diff --git a/src/software/lmi/software/core/AssociatedInstallationServiceCapabilities.py b/src/software/lmi/software/core/AssociatedInstallationServiceCapabilities.py new file mode 100644 index 0000000..cb1b24e --- /dev/null +++ b/src/software/lmi/software/core/AssociatedInstallationServiceCapabilities.py @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to associated class +LMI_AssociatedInstallationServiceCapabilities. +""" + +import pywbem + +class Values(object): + class Characteristics(object): + Default = pywbem.Uint16(2) + Current = pywbem.Uint16(3) + # DMTF_Reserved = .. + # Vendor_Specific = 32768..65535 + _reverse_map = { + 2: 'Default', + 3: 'Current' + } diff --git a/src/software/lmi/software/core/ComputerSystem.py b/src/software/lmi/software/core/ComputerSystem.py new file mode 100644 index 0000000..53c0738 --- /dev/null +++ b/src/software/lmi/software/core/ComputerSystem.py @@ -0,0 +1,87 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to associated class Linux_ComputerSystem. +""" + +import pywbem +import socket + +from lmi.common import cmpi_logging + +def get_path(prefix='Linux'): + """ + @return object path of Linux_ComputerSystem + """ + op = pywbem.CIMInstanceName( + classname='%s_ComputerSystem' % prefix, + namespace="root/cimv2") + op["CreationClassName"] = "Linux_ComputerSystem" + op["Name"] = socket.gethostname() + return op + +@cmpi_logging.trace_function +def check_path(env, system, prop_name): + """ + Checks instance name of ComputerSystem. + @param system instance name + @param prop_name name of object path + """ + if not isinstance(system, pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "\"%s\" must be a CIMInstanceName" % prop_name) + our_system = get_path(prefix='CIM') + ch = env.get_cimom_handle() + if system.namespace != our_system.namespace: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'Namespace of "%s" does not match "%s"' % ( + prop_name, our_system.namespace)) + if not ch.is_subclass(our_system.namespace, + sub=system.classname, + super=our_system.classname): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Class of \"%s\" must be a sublass of %s" % ( + prop_name, our_system.classname)) + if not 'CreationClassName' in system or not 'Name' in system: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "\"%s\" is missing one of keys", prop_name) + if not ch.is_subclass(our_system.namespace, + sub=system['CreationClassName'], + super=our_system.classname): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "CreationClassName of \"%s\" must be a sublass of %s" % ( + prop_name, our_system.classname)) + if system['Name'] != our_system['Name']: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Name of \"%s\" does not match \"%s\"" % + prop_name, our_system['Name']) + return True + +@cmpi_logging.trace_function +def check_path_property(env, op, prop_name): + """ + Checks, whether object path contains correct instance name of + Linux_ComputerSystem corresponding to this system. + If not, an exception is raised. + """ + if not prop_name in op: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Missing %s key property!" % prop_name) + return check_path(env, op[prop_name], prop_name) + diff --git a/src/software/lmi/software/core/Error.py b/src/software/lmi/software/core/Error.py new file mode 100644 index 0000000..d36ebb0 --- /dev/null +++ b/src/software/lmi/software/core/Error.py @@ -0,0 +1,457 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to class CIM_Error. +""" + +import pywbem + +from lmi.common import cmpi_logging + +class Values(object): + class ErrorSourceFormat(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + CIMObjectPath = pywbem.Uint16(2) + # DMTF_Reserved = .. + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'CIMObjectPath' + } + + class ErrorType(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Communications_Error = pywbem.Uint16(2) + Quality_of_Service_Error = pywbem.Uint16(3) + Software_Error = pywbem.Uint16(4) + Hardware_Error = pywbem.Uint16(5) + Environmental_Error = pywbem.Uint16(6) + Security_Error = pywbem.Uint16(7) + Oversubscription_Error = pywbem.Uint16(8) + Unavailable_Resource_Error = pywbem.Uint16(9) + Unsupported_Operation_Error = pywbem.Uint16(10) + # DMTF_Reserved = .. + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'Communications Error', + 3 : 'Quality of Service Error', + 4 : 'Software Error', + 5 : 'Hardware Error', + 6 : 'Environmental Error', + 7 : 'Security Error', + 8 : 'Oversubscription Error', + 9 : 'Unavailable Resource Error', + 10 : 'Unsupported Operation Error' + } + + class CIMStatusCode(object): + CIM_ERR_FAILED = pywbem.Uint32(1) + CIM_ERR_ACCESS_DENIED = pywbem.Uint32(2) + CIM_ERR_INVALID_NAMESPACE = pywbem.Uint32(3) + CIM_ERR_INVALID_PARAMETER = pywbem.Uint32(4) + CIM_ERR_INVALID_CLASS = pywbem.Uint32(5) + CIM_ERR_NOT_FOUND = pywbem.Uint32(6) + CIM_ERR_NOT_SUPPORTED = pywbem.Uint32(7) + CIM_ERR_CLASS_HAS_CHILDREN = pywbem.Uint32(8) + CIM_ERR_CLASS_HAS_INSTANCES = pywbem.Uint32(9) + CIM_ERR_INVALID_SUPERCLASS = pywbem.Uint32(10) + CIM_ERR_ALREADY_EXISTS = pywbem.Uint32(11) + CIM_ERR_NO_SUCH_PROPERTY = pywbem.Uint32(12) + CIM_ERR_TYPE_MISMATCH = pywbem.Uint32(13) + CIM_ERR_QUERY_LANGUAGE_NOT_SUPPORTED = pywbem.Uint32(14) + CIM_ERR_INVALID_QUERY = pywbem.Uint32(15) + CIM_ERR_METHOD_NOT_AVAILABLE = pywbem.Uint32(16) + CIM_ERR_METHOD_NOT_FOUND = pywbem.Uint32(17) + CIM_ERR_UNEXPECTED_RESPONSE = pywbem.Uint32(18) + CIM_ERR_INVALID_RESPONSE_DESTINATION = pywbem.Uint32(19) + CIM_ERR_NAMESPACE_NOT_EMPTY = pywbem.Uint32(20) + CIM_ERR_INVALID_ENUMERATION_CONTEXT = pywbem.Uint32(21) + CIM_ERR_INVALID_OPERATION_TIMEOUT = pywbem.Uint32(22) + CIM_ERR_PULL_HAS_BEEN_ABANDONED = pywbem.Uint32(23) + CIM_ERR_PULL_CANNOT_BE_ABANDONED = pywbem.Uint32(24) + CIM_ERR_FILTERED_ENUMERATION_NOT_SUPPORTED = pywbem.Uint32(25) + CIM_ERR_CONTINUATION_ON_ERROR_NOT_SUPPORTED = pywbem.Uint32(26) + CIM_ERR_SERVER_LIMITS_EXCEEDED = pywbem.Uint32(27) + CIM_ERR_SERVER_IS_SHUTTING_DOWN = pywbem.Uint32(28) + CIM_ERR_QUERY_FEATURE_NOT_SUPPORTED = pywbem.Uint32(29) + # DMTF_Reserved = .. + _reverse_map = { + 1 : 'CIM_ERR_FAILED', + 2 : 'CIM_ERR_ACCESS_DENIED', + 3 : 'CIM_ERR_INVALID_NAMESPACE', + 4 : 'CIM_ERR_INVALID_PARAMETER', + 5 : 'CIM_ERR_INVALID_CLASS', + 6 : 'CIM_ERR_NOT_FOUND', + 7 : 'CIM_ERR_NOT_SUPPORTED', + 8 : 'CIM_ERR_CLASS_HAS_CHILDREN', + 9 : 'CIM_ERR_CLASS_HAS_INSTANCES', + 10 : 'CIM_ERR_INVALID_SUPERCLASS', + 11 : 'CIM_ERR_ALREADY_EXISTS', + 12 : 'CIM_ERR_NO_SUCH_PROPERTY', + 13 : 'CIM_ERR_TYPE_MISMATCH', + 14 : 'CIM_ERR_QUERY_LANGUAGE_NOT_SUPPORTED', + 15 : 'CIM_ERR_INVALID_QUERY', + 16 : 'CIM_ERR_METHOD_NOT_AVAILABLE', + 17 : 'CIM_ERR_METHOD_NOT_FOUND', + 18 : 'CIM_ERR_UNEXPECTED_RESPONSE', + 19 : 'CIM_ERR_INVALID_RESPONSE_DESTINATION', + 20 : 'CIM_ERR_NAMESPACE_NOT_EMPTY', + 21 : 'CIM_ERR_INVALID_ENUMERATION_CONTEXT', + 22 : 'CIM_ERR_INVALID_OPERATION_TIMEOUT', + 23 : 'CIM_ERR_PULL_HAS_BEEN_ABANDONED', + 24 : 'CIM_ERR_PULL_CANNOT_BE_ABANDONED', + 25 : 'CIM_ERR_FILTERED_ENUMERATION_NOT_SUPPORTED', + 26 : 'CIM_ERR_CONTINUATION_ON_ERROR_NOT_SUPPORTED', + 27 : 'CIM_ERR_SERVER_LIMITS_EXCEEDED', + 28 : 'CIM_ERR_SERVER_IS_SHUTTING_DOWN', + 29 : 'CIM_ERR_QUERY_FEATURE_NOT_SUPPORTED' + } + + class PerceivedSeverity(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Information = pywbem.Uint16(2) + Degraded_Warning = pywbem.Uint16(3) + Minor = pywbem.Uint16(4) + Major = pywbem.Uint16(5) + Critical = pywbem.Uint16(6) + Fatal_NonRecoverable = pywbem.Uint16(7) + # DMTF_Reserved = .. + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'Information', + 3 : 'Degraded/Warning', + 4 : 'Minor', + 5 : 'Major', + 6 : 'Critical', + 7 : 'Fatal/NonRecoverable' + } + + class ProbableCause(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Adapter_Card_Error = pywbem.Uint16(2) + Application_Subsystem_Failure = pywbem.Uint16(3) + Bandwidth_Reduced = pywbem.Uint16(4) + Connection_Establishment_Error = pywbem.Uint16(5) + Communications_Protocol_Error = pywbem.Uint16(6) + Communications_Subsystem_Failure = pywbem.Uint16(7) + Configuration_Customization_Error = pywbem.Uint16(8) + Congestion = pywbem.Uint16(9) + Corrupt_Data = pywbem.Uint16(10) + CPU_Cycles_Limit_Exceeded = pywbem.Uint16(11) + Dataset_Modem_Error = pywbem.Uint16(12) + Degraded_Signal = pywbem.Uint16(13) + DTE_DCE_Interface_Error = pywbem.Uint16(14) + Enclosure_Door_Open = pywbem.Uint16(15) + Equipment_Malfunction = pywbem.Uint16(16) + Excessive_Vibration = pywbem.Uint16(17) + File_Format_Error = pywbem.Uint16(18) + Fire_Detected = pywbem.Uint16(19) + Flood_Detected = pywbem.Uint16(20) + Framing_Error = pywbem.Uint16(21) + HVAC_Problem = pywbem.Uint16(22) + Humidity_Unacceptable = pywbem.Uint16(23) + I_O_Device_Error = pywbem.Uint16(24) + Input_Device_Error = pywbem.Uint16(25) + LAN_Error = pywbem.Uint16(26) + Non_Toxic_Leak_Detected = pywbem.Uint16(27) + Local_Node_Transmission_Error = pywbem.Uint16(28) + Loss_of_Frame = pywbem.Uint16(29) + Loss_of_Signal = pywbem.Uint16(30) + Material_Supply_Exhausted = pywbem.Uint16(31) + Multiplexer_Problem = pywbem.Uint16(32) + Out_of_Memory = pywbem.Uint16(33) + Output_Device_Error = pywbem.Uint16(34) + Performance_Degraded = pywbem.Uint16(35) + Power_Problem = pywbem.Uint16(36) + Pressure_Unacceptable = pywbem.Uint16(37) + Processor_Problem__Internal_Machine_Error_ = pywbem.Uint16(38) + Pump_Failure = pywbem.Uint16(39) + Queue_Size_Exceeded = pywbem.Uint16(40) + Receive_Failure = pywbem.Uint16(41) + Receiver_Failure = pywbem.Uint16(42) + Remote_Node_Transmission_Error = pywbem.Uint16(43) + Resource_at_or_Nearing_Capacity = pywbem.Uint16(44) + Response_Time_Excessive = pywbem.Uint16(45) + Retransmission_Rate_Excessive = pywbem.Uint16(46) + Software_Error = pywbem.Uint16(47) + Software_Program_Abnormally_Terminated = pywbem.Uint16(48) + Software_Program_Error__Incorrect_Results_ = pywbem.Uint16(49) + Storage_Capacity_Problem = pywbem.Uint16(50) + Temperature_Unacceptable = pywbem.Uint16(51) + Threshold_Crossed = pywbem.Uint16(52) + Timing_Problem = pywbem.Uint16(53) + Toxic_Leak_Detected = pywbem.Uint16(54) + Transmit_Failure = pywbem.Uint16(55) + Transmitter_Failure = pywbem.Uint16(56) + Underlying_Resource_Unavailable = pywbem.Uint16(57) + Version_Mismatch = pywbem.Uint16(58) + Previous_Alert_Cleared = pywbem.Uint16(59) + Login_Attempts_Failed = pywbem.Uint16(60) + Software_Virus_Detected = pywbem.Uint16(61) + Hardware_Security_Breached = pywbem.Uint16(62) + Denial_of_Service_Detected = pywbem.Uint16(63) + Security_Credential_Mismatch = pywbem.Uint16(64) + Unauthorized_Access = pywbem.Uint16(65) + Alarm_Received = pywbem.Uint16(66) + Loss_of_Pointer = pywbem.Uint16(67) + Payload_Mismatch = pywbem.Uint16(68) + Transmission_Error = pywbem.Uint16(69) + Excessive_Error_Rate = pywbem.Uint16(70) + Trace_Problem = pywbem.Uint16(71) + Element_Unavailable = pywbem.Uint16(72) + Element_Missing = pywbem.Uint16(73) + Loss_of_Multi_Frame = pywbem.Uint16(74) + Broadcast_Channel_Failure = pywbem.Uint16(75) + Invalid_Message_Received = pywbem.Uint16(76) + Routing_Failure = pywbem.Uint16(77) + Backplane_Failure = pywbem.Uint16(78) + Identifier_Duplication = pywbem.Uint16(79) + Protection_Path_Failure = pywbem.Uint16(80) + Sync_Loss_or_Mismatch = pywbem.Uint16(81) + Terminal_Problem = pywbem.Uint16(82) + Real_Time_Clock_Failure = pywbem.Uint16(83) + Antenna_Failure = pywbem.Uint16(84) + Battery_Charging_Failure = pywbem.Uint16(85) + Disk_Failure = pywbem.Uint16(86) + Frequency_Hopping_Failure = pywbem.Uint16(87) + Loss_of_Redundancy = pywbem.Uint16(88) + Power_Supply_Failure = pywbem.Uint16(89) + Signal_Quality_Problem = pywbem.Uint16(90) + Battery_Discharging = pywbem.Uint16(91) + Battery_Failure = pywbem.Uint16(92) + Commercial_Power_Problem = pywbem.Uint16(93) + Fan_Failure = pywbem.Uint16(94) + Engine_Failure = pywbem.Uint16(95) + Sensor_Failure = pywbem.Uint16(96) + Fuse_Failure = pywbem.Uint16(97) + Generator_Failure = pywbem.Uint16(98) + Low_Battery = pywbem.Uint16(99) + Low_Fuel = pywbem.Uint16(100) + Low_Water = pywbem.Uint16(101) + Explosive_Gas = pywbem.Uint16(102) + High_Winds = pywbem.Uint16(103) + Ice_Buildup = pywbem.Uint16(104) + Smoke = pywbem.Uint16(105) + Memory_Mismatch = pywbem.Uint16(106) + Out_of_CPU_Cycles = pywbem.Uint16(107) + Software_Environment_Problem = pywbem.Uint16(108) + Software_Download_Failure = pywbem.Uint16(109) + Element_Reinitialized = pywbem.Uint16(110) + Timeout = pywbem.Uint16(111) + Logging_Problems = pywbem.Uint16(112) + Leak_Detected = pywbem.Uint16(113) + Protection_Mechanism_Failure = pywbem.Uint16(114) + Protecting_Resource_Failure = pywbem.Uint16(115) + Database_Inconsistency = pywbem.Uint16(116) + Authentication_Failure = pywbem.Uint16(117) + Breach_of_Confidentiality = pywbem.Uint16(118) + Cable_Tamper = pywbem.Uint16(119) + Delayed_Information = pywbem.Uint16(120) + Duplicate_Information = pywbem.Uint16(121) + Information_Missing = pywbem.Uint16(122) + Information_Modification = pywbem.Uint16(123) + Information_Out_of_Sequence = pywbem.Uint16(124) + Key_Expired = pywbem.Uint16(125) + Non_Repudiation_Failure = pywbem.Uint16(126) + Out_of_Hours_Activity = pywbem.Uint16(127) + Out_of_Service = pywbem.Uint16(128) + Procedural_Error = pywbem.Uint16(129) + Unexpected_Information = pywbem.Uint16(130) + # DMTF_Reserved = .. + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'Adapter/Card Error', + 3 : 'Application Subsystem Failure', + 4 : 'Bandwidth Reduced', + 5 : 'Connection Establishment Error', + 6 : 'Communications Protocol Error', + 7 : 'Communications Subsystem Failure', + 8 : 'Configuration/Customization Error', + 9 : 'Congestion', + 10 : 'Corrupt Data', + 11 : 'CPU Cycles Limit Exceeded', + 12 : 'Dataset/Modem Error', + 13 : 'Degraded Signal', + 14 : 'DTE-DCE Interface Error', + 15 : 'Enclosure Door Open', + 16 : 'Equipment Malfunction', + 17 : 'Excessive Vibration', + 18 : 'File Format Error', + 19 : 'Fire Detected', + 20 : 'Flood Detected', + 21 : 'Framing Error', + 22 : 'HVAC Problem', + 23 : 'Humidity Unacceptable', + 24 : 'I/O Device Error', + 25 : 'Input Device Error', + 26 : 'LAN Error', + 27 : 'Non-Toxic Leak Detected', + 28 : 'Local Node Transmission Error', + 29 : 'Loss of Frame', + 30 : 'Loss of Signal', + 31 : 'Material Supply Exhausted', + 32 : 'Multiplexer Problem', + 33 : 'Out of Memory', + 34 : 'Output Device Error', + 35 : 'Performance Degraded', + 36 : 'Power Problem', + 37 : 'Pressure Unacceptable', + 38 : 'Processor Problem (Internal Machine Error)', + 39 : 'Pump Failure', + 40 : 'Queue Size Exceeded', + 41 : 'Receive Failure', + 42 : 'Receiver Failure', + 43 : 'Remote Node Transmission Error', + 44 : 'Resource at or Nearing Capacity', + 45 : 'Response Time Excessive', + 46 : 'Retransmission Rate Excessive', + 47 : 'Software Error', + 48 : 'Software Program Abnormally Terminated', + 49 : 'Software Program Error (Incorrect Results)', + 50 : 'Storage Capacity Problem', + 51 : 'Temperature Unacceptable', + 52 : 'Threshold Crossed', + 53 : 'Timing Problem', + 54 : 'Toxic Leak Detected', + 55 : 'Transmit Failure', + 56 : 'Transmitter Failure', + 57 : 'Underlying Resource Unavailable', + 58 : 'Version Mismatch', + 59 : 'Previous Alert Cleared', + 60 : 'Login Attempts Failed', + 61 : 'Software Virus Detected', + 62 : 'Hardware Security Breached', + 63 : 'Denial of Service Detected', + 64 : 'Security Credential Mismatch', + 65 : 'Unauthorized Access', + 66 : 'Alarm Received', + 67 : 'Loss of Pointer', + 68 : 'Payload Mismatch', + 69 : 'Transmission Error', + 70 : 'Excessive Error Rate', + 71 : 'Trace Problem', + 72 : 'Element Unavailable', + 73 : 'Element Missing', + 74 : 'Loss of Multi Frame', + 75 : 'Broadcast Channel Failure', + 76 : 'Invalid Message Received', + 77 : 'Routing Failure', + 78 : 'Backplane Failure', + 79 : 'Identifier Duplication', + 80 : 'Protection Path Failure', + 81 : 'Sync Loss or Mismatch', + 82 : 'Terminal Problem', + 83 : 'Real Time Clock Failure', + 84 : 'Antenna Failure', + 85 : 'Battery Charging Failure', + 86 : 'Disk Failure', + 87 : 'Frequency Hopping Failure', + 88 : 'Loss of Redundancy', + 89 : 'Power Supply Failure', + 90 : 'Signal Quality Problem', + 91 : 'Battery Discharging', + 92 : 'Battery Failure', + 93 : 'Commercial Power Problem', + 94 : 'Fan Failure', + 95 : 'Engine Failure', + 96 : 'Sensor Failure', + 97 : 'Fuse Failure', + 98 : 'Generator Failure', + 99 : 'Low Battery', + 100 : 'Low Fuel', + 101 : 'Low Water', + 102 : 'Explosive Gas', + 103 : 'High Winds', + 104 : 'Ice Buildup', + 105 : 'Smoke', + 106 : 'Memory Mismatch', + 107 : 'Out of CPU Cycles', + 108 : 'Software Environment Problem', + 109 : 'Software Download Failure', + 110 : 'Element Reinitialized', + 111 : 'Timeout', + 112 : 'Logging Problems', + 113 : 'Leak Detected', + 114 : 'Protection Mechanism Failure', + 115 : 'Protecting Resource Failure', + 116 : 'Database Inconsistency', + 117 : 'Authentication Failure', + 118 : 'Breach of Confidentiality', + 119 : 'Cable Tamper', + 120 : 'Delayed Information', + 121 : 'Duplicate Information', + 122 : 'Information Missing', + 123 : 'Information Modification', + 124 : 'Information Out of Sequence', + 125 : 'Key Expired', + 126 : 'Non-Repudiation Failure', + 127 : 'Out of Hours Activity', + 128 : 'Out of Service', + 129 : 'Procedural Error', + 130 : 'Unexpected Information' + } + +@cmpi_logging.trace_function +def make_instance( + status_code=Values.CIMStatusCode.CIM_ERR_FAILED, + error_type=Values.ErrorType.Software_Error, + probable_cause=Values.ErrorType.Unknown, + error_source=None, + status_code_description=None, + message=None, + message_arguments=None, + probable_cause_description=None, + recommended_actions=None): + for param in ('status_code', 'probable_cause', 'error_type'): + if not isinstance(locals()[param], (int, long)): + raise TypeError('%s must be integer'%param) + if error_source is None: + # this is a cyclic dependency + from lmi.software.core import InstallationService + error_source = InstallationService.get_path() + if not isinstance(error_source, pywbem.CIMInstanceName): + raise TypeError('error_source must be a CIMInstanceName') + + inst = pywbem.CIMInstance(classname="CIM_Error", + path=pywbem.CIMInstanceName(classname="CIM_Error", + namespace="root/cimv2")) + inst['CIMStatusCode'] = pywbem.Uint32(status_code) + if status_code_description is not None: + inst['CIMStatusCodeDescription'] = status_code_description + inst['ErrorType'] = pywbem.Uint16(error_type) + inst['ErrorSource'] = str(error_source) + inst['ErrorSourceFormat'] = Values.ErrorSourceFormat.CIMObjectPath + if message is not None: + inst['Message'] = message + if message_arguments is not None: + inst['MessageArguments'] = message_arguments + inst['ProbableCause'] = pywbem.Uint16(probable_cause) + if probable_cause_description is not None: + inst['ProbableCauseDescription'] = probable_cause_description + if recommended_actions is not None: + inst['RecommendedActions'] = recommended_actions + return inst + diff --git a/src/software/lmi/software/core/Identity.py b/src/software/lmi/software/core/Identity.py new file mode 100644 index 0000000..2df43a9 --- /dev/null +++ b/src/software/lmi/software/core/Identity.py @@ -0,0 +1,270 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to LMI_SoftwareIdentity provider. +""" + +import pywbem + +from lmi.common import cmpi_logging +from lmi.software import util +from lmi.software.yumdb import PackageInfo, YumDB + +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 Classifications(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Driver = pywbem.Uint16(2) + Configuration_Software = pywbem.Uint16(3) + Application_Software = pywbem.Uint16(4) + Instrumentation = pywbem.Uint16(5) + Firmware_BIOS = pywbem.Uint16(6) + Diagnostic_Software = pywbem.Uint16(7) + Operating_System = pywbem.Uint16(8) + Middleware = pywbem.Uint16(9) + Firmware = pywbem.Uint16(10) + BIOS_FCode = pywbem.Uint16(11) + Support_Service_Pack = pywbem.Uint16(12) + Software_Bundle = pywbem.Uint16(13) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000..0xFFFF + + class ExtendedResourceType(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Not_Applicable = pywbem.Uint16(2) + Linux_RPM = pywbem.Uint16(3) + HP_UX_Depot = pywbem.Uint16(4) + Windows_MSI = pywbem.Uint16(5) + Solaris_Package = pywbem.Uint16(6) + Macintosh_Disk_Image = pywbem.Uint16(7) + Debian_linux_Package = pywbem.Uint16(8) + VMware_vSphere_Installation_Bundle = pywbem.Uint16(9) + VMware_Software_Bulletin = pywbem.Uint16(10) + HP_Smart_Component = pywbem.Uint16(11) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + + 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 PrimaryStatus(object): + Unknown = pywbem.Uint16(0) + OK = pywbem.Uint16(1) + Degraded = pywbem.Uint16(2) + Error = pywbem.Uint16(3) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + + +@cmpi_logging.trace_function +def object_path2nevra(op, with_epoch='NOT_ZERO'): + """Get nevra out of object path. Also checks for validity.""" + if not isinstance(op, pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "op must be an instance of CIMInstanceName") + + if (not "InstanceID" in op or not op['InstanceID']): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, "Wrong keys.") + instid = op['InstanceID'] + if not instid.lower().startswith("lmi:lmi_softwareidentity:"): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "InstanceID must start with LMI:LMI_SoftwareIdentity: prefix.") + instid = instid[len("LMI:LMI_SoftwareIdentity:"):] + match = util.RE_NEVRA_OPT_EPOCH.match(instid) + if not match: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Wrong InstanceID. Expected valid nevra" + ' (name-[epoch:]version-release.arch): "%s".' % + instid) + epoch = match.group('epoch') + if not epoch: + epoch = "0" + return util.make_nevra( + match.group('name'), + epoch, + match.group('version'), + match.group('release'), + match.group('arch'), with_epoch=with_epoch) + +@cmpi_logging.trace_function +def object_path2pkg(op, + kind='installed', + include_repos=None, + exclude_repos=None, + repoid=None, + return_all=False): + """ + @param op must contain precise information of package, + otherwise an error is raised + @param kind one of yumdb.jobs.YumGetPackageList.SUPPORTED_KINDS + says, where to look for given package + @param repoid if not None, specifies repoid filter on package; + note, that this does not make sure, that repoid will be enabled. + @param return_all if True, return list of matching packages as returned + by YumDB.filter_packages(), otherwise single package is returned + """ + if not isinstance(kind, basestring): + raise TypeError("kind must be a string") + + pkglist = YumDB.get_instance().filter_packages(kind, + allow_duplicates=kind not in ('installed', 'avail_reinst'), + include_repos=include_repos, + exclude_repos=exclude_repos, + repoid=repoid, + nevra=object_path2nevra(op, 'ALWAYS')) + if return_all is True: + return pkglist + if len(pkglist) > 0: + return pkglist[0] + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'No matching package found for InstanceID=\"%s\".' % + op["InstanceID"]) + +@cmpi_logging.trace_function +def pkg2model(pkg, keys_only=True, model=None): + """ + @param pkg can either be an instance of PackageInfo or nevra as string + @param model if not None, will be filled with data, otherwise + a new instance of CIMInstance or CIMObjectPath is created + """ + if not isinstance(pkg, (basestring, PackageInfo)): + raise TypeError("pkg must be an instance of PackageInfo or nevra") + if isinstance(pkg, basestring) and not keys_only: + raise ValueError("can not create instance out of nevra") + if model is None: + model = pywbem.CIMInstanceName("LMI_SoftwareIdentity", + namespace="root/cimv2") + if not keys_only: + model = pywbem.CIMInstance("LMI_SoftwareIdentity", path=model) + nevra = pkg if isinstance(pkg, basestring) else pkg.nevra + model['InstanceID'] = 'LMI:LMI_SoftwareIdentity:'+nevra + if not keys_only: + model.path['InstanceID'] = model['InstanceID'] #pylint: disable=E1103 + model['Caption'] = pkg.summary + model['Classifications'] = [pywbem.Uint16(0)] + model['Description'] = pkg.description + model['ElementName'] = pkg.nevra + if pkg.installed: + model['InstallDate'] = pywbem.CIMDateTime(pkg.install_time) + else: + model['InstallDate'] = pywbem.CIMProperty( + 'InstallDate', None, type='datetime') + model['IsEntity'] = True + model['Name'] = pkg.name + try: + model["Epoch"] = pywbem.Uint32(int(pkg.epoch)) + except ValueError: + cmpi_logging.logger.error('Could not convert epoch "%s"' + ' to integer for package \"%s\"!' % (pkg.epoch, pkg)) + model["Epoch"] = pywbem.CIMProperty('Epoch', None, type='uint32') + model['Version'] = pkg.version + model['Release'] = pkg.release + model['Architecture'] = pkg.arch + model['TargetTypes'] = ['rpm', 'yum'] + model['VersionString'] = pkg.evra + return model + diff --git a/src/software/lmi/software/core/IdentityFileCheck.py b/src/software/lmi/software/core/IdentityFileCheck.py new file mode 100644 index 0000000..665d822 --- /dev/null +++ b/src/software/lmi/software/core/IdentityFileCheck.py @@ -0,0 +1,965 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +""" +Just a common functionality related to SoftwareIdentityFileCheck provider. +""" + +import hashlib +import os +import pywbem +import stat +import yum + +from lmi.common import cmpi_logging +from lmi.software import util +from lmi.software.yumdb import errors +from lmi.software.yumdb import jobs +from lmi.software.yumdb import packageinfo +from lmi.software.yumdb import packagecheck +from lmi.software.yumdb import YumDB + +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) + Microsoft_Windows_Server_2011 = pywbem.Uint16(111) + Microsoft_Windows_Server_2012 = pywbem.Uint16(113) + Microsoft_Windows_8 = pywbem.Uint16(114) + Microsoft_Windows_8_64_bit = pywbem.Uint16(115) + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'MACOS', + 3 : 'ATTUNIX', + 4 : 'DGUX', + 5 : 'DECNT', + 6 : 'Tru64 UNIX', + 7 : 'OpenVMS', + 8 : 'HPUX', + 9 : 'AIX', + 10 : 'MVS', + 11 : 'OS400', + 12 : 'OS/2', + 13 : 'JavaVM', + 14 : 'MSDOS', + 15 : 'WIN3x', + 16 : 'WIN95', + 17 : 'WIN98', + 18 : 'WINNT', + 19 : 'WINCE', + 20 : 'NCR3000', + 21 : 'NetWare', + 22 : 'OSF', + 23 : 'DC/OS', + 24 : 'Reliant UNIX', + 25 : 'SCO UnixWare', + 26 : 'SCO OpenServer', + 27 : 'Sequent', + 28 : 'IRIX', + 29 : 'Solaris', + 30 : 'SunOS', + 31 : 'U6000', + 32 : 'ASERIES', + 33 : 'HP NonStop OS', + 34 : 'HP NonStop OSS', + 35 : 'BS2000', + 36 : 'LINUX', + 37 : 'Lynx', + 38 : 'XENIX', + 39 : 'VM', + 40 : 'Interactive UNIX', + 41 : 'BSDUNIX', + 42 : 'FreeBSD', + 43 : 'NetBSD', + 44 : 'GNU Hurd', + 45 : 'OS9', + 46 : 'MACH Kernel', + 47 : 'Inferno', + 48 : 'QNX', + 49 : 'EPOC', + 50 : 'IxWorks', + 51 : 'VxWorks', + 52 : 'MiNT', + 53 : 'BeOS', + 54 : 'HP MPE', + 55 : 'NextStep', + 56 : 'PalmPilot', + 57 : 'Rhapsody', + 58 : 'Windows 2000', + 59 : 'Dedicated', + 60 : 'OS/390', + 61 : 'VSE', + 62 : 'TPF', + 63 : 'Windows (R) Me', + 64 : 'Caldera Open UNIX', + 65 : 'OpenBSD', + 66 : 'Not Applicable', + 67 : 'Windows XP', + 68 : 'z/OS', + 69 : 'Microsoft Windows Server 2003', + 70 : 'Microsoft Windows Server 2003 64-Bit', + 71 : 'Windows XP 64-Bit', + 72 : 'Windows XP Embedded', + 73 : 'Windows Vista', + 74 : 'Windows Vista 64-Bit', + 75 : 'Windows Embedded for Point of Service', + 76 : 'Microsoft Windows Server 2008', + 77 : 'Microsoft Windows Server 2008 64-Bit', + 78 : 'FreeBSD 64-Bit', + 79 : 'RedHat Enterprise Linux', + 80 : 'RedHat Enterprise Linux 64-Bit', + 81 : 'Solaris 64-Bit', + 82 : 'SUSE', + 83 : 'SUSE 64-Bit', + 84 : 'SLES', + 85 : 'SLES 64-Bit', + 86 : 'Novell OES', + 87 : 'Novell Linux Desktop', + 88 : 'Sun Java Desktop System', + 89 : 'Mandriva', + 90 : 'Mandriva 64-Bit', + 91 : 'TurboLinux', + 92 : 'TurboLinux 64-Bit', + 93 : 'Ubuntu', + 94 : 'Ubuntu 64-Bit', + 95 : 'Debian', + 96 : 'Debian 64-Bit', + 97 : 'Linux 2.4.x', + 98 : 'Linux 2.4.x 64-Bit', + 99 : 'Linux 2.6.x', + 100 : 'Linux 2.6.x 64-Bit', + 101 : 'Linux 64-Bit', + 102 : 'Other 64-Bit', + 103 : 'Microsoft Windows Server 2008 R2', + 104 : 'VMware ESXi', + 105 : 'Microsoft Windows 7', + 106 : 'CentOS 32-bit', + 107 : 'CentOS 64-bit', + 108 : 'Oracle Enterprise Linux 32-bit', + 109 : 'Oracle Enterprise Linux 64-bit', + 110 : 'eComStation 32-bitx', + 111 : 'Microsoft Windows Server 2011', + 113 : 'Microsoft Windows Server 2012', + 114 : 'Microsoft Windows 8', + 115 : 'Microsoft Windows 8 64-bit' + } + + class ChecksumType(object): + UNKNOWN = pywbem.Uint16(0) + MD5 = pywbem.Uint16(1) + SHA_1 = pywbem.Uint16(2) + RIPE_MD_160 = pywbem.Uint16(3) + SHA256 = pywbem.Uint16(8) + SHA384 = pywbem.Uint16(9) + SHA512 = pywbem.Uint16(10) + SHA224 = pywbem.Uint16(11) + _reverse_map = { + 0 : 'UNKNOWN', + 1 : 'MD5', + 2 : 'SHA-1', + 3 : 'RIPE-MD/160', + 8 : 'SHA256', + 9 : 'SHA384', + 10 : 'SHA512', + 11 : 'SHA224' + } + + 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) + _reverse_map = { + 0 : 'Unknown', + 1 : 'File', + 2 : 'Directory', + 3 : 'Symlink', + 4 : 'FIFO', + 5 : 'Character Device', + 6 : 'Block Device' + } + + class FileModeFlags(object): + Execute_Other = pywbem.Uint8(0) + Write_Other = pywbem.Uint8(1) + Read_Other = pywbem.Uint8(2) + Execute_Group = pywbem.Uint8(3) + Write_Group = pywbem.Uint8(4) + Read_Group = pywbem.Uint8(5) + Execute_User = pywbem.Uint8(6) + Write_User = pywbem.Uint8(7) + Read_User = pywbem.Uint8(8) + Sticky = pywbem.Uint8(9) + SGID = pywbem.Uint8(10) + SUID = pywbem.Uint8(11) + _reverse_map = { + 0 : 'Execute Other', + 1 : 'Write Other', + 2 : 'Read Other', + 3 : 'Execute Group', + 4 : 'Write Group', + 5 : 'Read Group', + 6 : 'Execute User', + 7 : 'Write User', + 8 : 'Read User', + 9 : 'Sticky', + 10 : 'SGID', + 11 : 'SUID' + } + + class FileModeFlagsOriginal(object): + Execute_Other = pywbem.Uint8(0) + Write_Other = pywbem.Uint8(1) + Read_Other = pywbem.Uint8(2) + Execute_Group = pywbem.Uint8(3) + Write_Group = pywbem.Uint8(4) + Read_Group = pywbem.Uint8(5) + Execute_User = pywbem.Uint8(6) + Write_User = pywbem.Uint8(7) + Read_User = pywbem.Uint8(8) + Sticky = pywbem.Uint8(9) + SGID = pywbem.Uint8(10) + SUID = pywbem.Uint8(11) + _reverse_map = { + 0 : 'Execute Other', + 1 : 'Write Other', + 2 : 'Read Other', + 3 : 'Execute Group', + 4 : 'Write Group', + 5 : 'Read Group', + 6 : 'Execute User', + 7 : 'Write User', + 8 : 'Read User', + 9 : 'Sticky', + 10 : 'SGID', + 11 : 'SUID' + } + + class FailedFlags(object): + Existence = pywbem.Uint16(0) + FileSize = pywbem.Uint16(1) + FileMode = pywbem.Uint16(2) + Checksum = pywbem.Uint16(3) + Device_Number = pywbem.Uint16(4) + LinkTarget = pywbem.Uint16(5) + UserID = pywbem.Uint16(6) + GroupID = pywbem.Uint16(7) + Last_Modification_Time = pywbem.Uint16(8) + _reverse_map = { + 0 : 'Existence', + 1 : 'FileSize', + 2 : 'FileMode', + 3 : 'Checksum', + 4 : 'Device Number', + 5 : 'LinkTarget', + 6 : 'UserID', + 7 : 'GroupID', + 8 : 'Last Modification Time' + } + + class SoftwareElementState(object): + Deployable = pywbem.Uint16(0) + Installable = pywbem.Uint16(1) + Executable = pywbem.Uint16(2) + Running = pywbem.Uint16(3) + _reverse_map = { + 0 : 'Deployable', + 1 : 'Installable', + 2 : 'Executable', + 3 : 'Running' + } + + class FileTypeOriginal(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) + _reverse_map = { + 0 : 'Unknown', + 1 : 'File', + 2 : 'Directory', + 3 : 'Symlink', + 4 : 'FIFO', + 5 : 'Character Device', + 6 : 'Block Device' + } + + class Invoke(object): + """ + This is not an enumeration from mof file, it just gives human + names to numeric values. + """ + Satisfied = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Not_Satisfied = pywbem.Uint32(2) + +class FileCheck(object): + """ + File attribute storage keeping both RPM database metadata and info + from local filesystem for single file. Most attributes are loaded at the + time of first request and cached for later use. + + Most of properties return tuple: + ``(installed, original)`` + where + ``installed`` is a state of property of installed file, while + ``original`` is the value stored in RPM database. + + If any of those values are None, it means that property could not be + retrieved or it's not applicable to file's type. + """ + + def __init__(self, pkg_info, pkg_file, checksum_type): + """ + :param pkg_file: (``yumdb.packagecheck.PackageFile``) Metadata for + file loaded from rpm database. + :param checksum_type: (``int``) Indentificator of checksum algorithm + corresponding to values in yum.constants.RPM_CHECKSUM_TYPES. + """ + if not isinstance(pkg_info, packageinfo.PackageInfo): + raise TypeError("pkg_info must be an instance of PackageInfo") + if not isinstance(pkg_file, packagecheck.PackageFile): + raise TypeError("pkg_file must be an instance of PackageFile") + self._pkg_info = pkg_info + self._pkg_file = pkg_file + self._exists = None + # (md5 checksum, rpm-type checksum) - this is made on first request + self._checksums = None + self._checksum_type = checksum_type + self._link_target = None + # stores result of os.stat() call + self._stat = None + + @property + def pkg_info(self): + """:rtype: (``PackageInfo``)""" + return self._pkg_info + + @property + def pkg_file(self): + """:rtype: (``PackageFile``)""" + return self._pkg_file + + @property + def path(self): + """Return absolute file path.""" + return self._pkg_file.path + + @property + def exists(self): + """Return true if file exists on local file system.""" + if self._exists is None: + self._exists = os.path.lexists(self._pkg_file.path) + return self._exists + + @property + def stat(self): + """ + Return cached stat object - result of ``os.lstat()``. + None is returned if file does not exist. + """ + if self._stat is None and self.exists: + self._stat = os.lstat(self.path) + return self._stat + + @property + def file_type(self): + """ + Return identifiers of file type for installed and rpm file. + If the installed file does not exist, None is returned on + its position. + :rtype: (``tuple``) Pair of (installed, original). + """ + fm = self.file_mode[0] + if fm is None: + ft = None + elif stat.S_ISLNK(fm): + ft = packagecheck.FILE_TYPE_SYMLINK + elif stat.S_ISDIR(fm): + ft = packagecheck.FILE_TYPE_DIRECTORY + elif stat.S_ISFIFO(fm): + ft = packagecheck.FILE_TYPE_FIFO + elif stat.S_ISCHR(fm): + ft = packagecheck.FILE_TYPE_CHARACTER_DEVICE + elif stat.S_ISBLK(fm): + ft = packagecheck.FILE_TYPE_BLOCK_DEVICE + elif stat.S_ISREG(fm): + ft = packagecheck.FILE_TYPE_FILE + else: + ft = packagecheck.FILE_TYPE_UNKNOWN + return (ft, self._pkg_file.file_type) + + @property + def md5_checksum(self): + """ + Return md5 checksum string of installed file. + This is computed on first access either to this or ``file_checksum`` + property and cached for later access. + :rtype: (``str``) + """ + if self._checksums is None: + self.make_checksums() + return self._checksums[0] if self._checksums else None + + @property + def file_checksum_type(self): + """ + Return identifier of hash algorithm used in rpm package. + :rtype: (``int``) + """ + return self._checksum_type + + @property + def file_checksum(self): + """ + Return checksum strings for installed and rpm file. + This is computed on first access either to this or ``md5_checksum`` + property and cached for later access. + :rtype: (``tuple``) Pair of (installed, original). + """ + if self._checksums is None: + self.make_checksums() + return ( self._checksums[1] if self._checksums else None + , self._pkg_file.checksum) + + @property + def file_size(self): + """:rtype: (``tuple``) Pair of (installed, original).""" + return (self.getstat('size'), self._pkg_file.size) + + @property + def file_mode(self): + """:rtype: (``tuple``) Pair of (installed, original).""" + return (self.getstat('mode'), self._pkg_file.mode) + + @property + def device(self): + """:rtype: (``tuple``) Pair of (installed, original).""" + return (self.getstat('dev'), self._pkg_file.device) + + @property + def link_target(self): + """:rtype: (``tuple``) Pair of (installed, original).""" + if ( self.file_type[0] == packagecheck.FILE_TYPE_SYMLINK + and self._link_target is None): + self._link_target = os.readlink(self.path) + return (self._link_target, self._pkg_file.link_target) + + @property + def user_id(self): + """:rtype: (``tuple``) Pair of (installed, original).""" + return (self.getstat('uid'), self._pkg_file.uid) + + @property + def group_id(self): + """:rtype: (``tuple``) Pair of (installed, original).""" + return (self.getstat('gid'), self._pkg_file.gid) + + @property + def last_modification_time(self): + """:rtype: (``tuple``) Pair of (installed, original).""" + return (self.getstat('mtime'), self._pkg_file.mtime) + + def getstat(self, attr): + """ + Get attribute from stat object for given file. + :param attr: (``str``) Will be prefixed with "st_". + """ + if self.stat is not None: + return getattr(self.stat, "st_"+attr) + + def make_checksums(self): + """ + Compute checksums for installed file and cache them. + :rtype: (``tuple``) MD5 and rpm-type checksums for given file + as a pair. + """ + if self.file_type[0] == packagecheck.FILE_TYPE_FILE: + self._checksums = compute_checksums( + self._checksum_type, self.path) + else: + self._checksums = None + return self._checksums + + +@cmpi_logging.trace_function +def hashfile(afile, hashers, blocksize=65536): + """ + Generic function computing multiple hashes from single file at once. + + :param hashers: (``list``) A list of hash objects. + :rtype: (``list``) List of digest strings (in hex format) for each + given hash object in the same order. + """ + if not isinstance(hashers, (tuple, list, set, frozenset)): + hashers = (hashers, ) + buf = afile.read(blocksize) + while len(buf) > 0: + for hashfunc in hashers: + hashfunc.update(buf) + buf = afile.read(blocksize) + return [ hashfunc.hexdigest() for hashfunc in hashers ] + +@cmpi_logging.trace_function +def compute_checksums(checksum_type, file_path): + """ + Compute file checksums in one go. + + :param checksum_type: (``int`) Selected hash algorithm to compute second + checksum (the one used for rpm package). Correct value can be + obtained from ``yum.constants.RPM_CHECKSUM_TYPES``. + :param file_path: (``str``) Absolute path of regular file to hash. + :rtype (md5sum, checksum) Tuple of strings containing hex represention + of computed checksums. + + 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. + """ + cmpi_logging.logger.debug( + "checksuming file %s", file_path) + hashers = [hashlib.md5()] #pylint: disable=E1101 + if checksum_type != packagecheck.CHECKSUMTYPE_STR2NUM["md5"]: + hashers.append(checksumtype_num2hash(checksum_type)()) + try: + with open(file_path, 'rb') as fobj: + rslts = hashfile(fobj, hashers) + except (OSError, IOError) as exc: + cmpi_logging.logger.error("could not open file \"%s\"" + " for reading: %s", file_path, exc) + return None, None + return (rslts[0], rslts[1] if len(rslts) > 1 else rslts[0]*2) + +@cmpi_logging.trace_function +def checksumtype_num2hash(csumt): + """ + Get checksum function for rpm hash identifier. + + :param csumt: (``int``) Checksum type as a number obtained from package. + :rtype: (``function``) Hash function object corresponding to csumt. + """ + return getattr(hashlib, yum.constants.RPM_CHECKSUM_TYPES[csumt]) + +@cmpi_logging.trace_function +def mode2pywbem_flags(mode): + """ + Utility for creation of value of ``FileModeFlags`` property out of file's + raw mode. + + :param mode: (``int``) File's mode. Retrieved from os.lstat(). + If None, file does not exist. + :rtype: (``list``) Of integer flags describing file's access permissions. + """ + if mode is None: + return None + flags = [] + for i, flag in enumerate(( + stat.S_IXOTH, + stat.S_IWOTH, + stat.S_IROTH, + stat.S_IXGRP, + stat.S_IWGRP, + stat.S_IRGRP, + stat.S_IXUSR, + stat.S_IWUSR, + stat.S_IRUSR, + stat.S_ISVTX, + stat.S_ISGID, + stat.S_ISUID)): + if flag & mode: + flags.append(pywbem.Uint8(i)) + return flags + +def get_existing_check_from_job(check_id): + """ + ``CheckID`` of ``LMI_SoftwareIdentityFileCheck`` refers to asynchronous job + that caused creation of this file check. Let's parse it, get the job out + of YumWorker and ask it for results so they don't have to be computed + again. + + :param check_id: (``str``) Value of ``CheckID`` property. + :rtype: (``tuple``) Pair of (pkg_info, pkg_check). + ``None`` if job does not exists anymore. + """ + try: + match = util.RE_INSTANCE_ID.match(check_id) + if not match: + raise ValueError(check_id) + if ( not match.group('clsname').lower() + == "LMI_SoftwareVerificationJob".lower()): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "CheckID does not contain supported job class name: " + " %s != %s" % (match.group('clsname'), + "LMI_SoftwareVerificationJob".lower())) + job_id = int(match.group('id')) + job = YumDB.get_instance().get_job(check_id) + if not isinstance(job, + (jobs.YumCheckPackage, jobs.YumCheckPackageFile)): + cmpi_logging.logger.error( + 'CheckID="%s" of LMI_SoftwareIdentityFileCheck refers' + ' to job %s.', check_id, job) + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "CheckID refers to wrong job.") + return job.result_data + + except errors.JobNotFound: + # allow this - job could already be deleted + cmpi_logging.logger.warn("could not find YumCheckPackage or" + " YumCheckPackageFile job with id \"%d\"" % job_id) + except ValueError: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Could not parse \"CheckID\" key property: \"%s\"." % check_id) + +@cmpi_logging.trace_function +def object_path2file_check(objpath): + """ + @return (package_info, package_check, file_name) + """ + if not isinstance(objpath, pywbem.CIMInstanceName): + raise TypeError("objpath must be instance of CIMInstanceName, " + "not \"%s\"" % objpath.__class__.__name__) + + for key in ('Name', 'SoftwareElementID', 'Version', + 'SoftwareElementState', 'TargetOperatingSystem'): + if not key in objpath or not objpath[key]: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Missing key property \"%s\"." % key) + version_match = util.RE_EVRA.match(objpath['Version']) + if not version_match: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + 'Failed to parse \"Version\" property for valid EVRA: "%s".' % + objpath['Version']) + pkg_fltr = util.nevra2filter(objpath["SoftwareElementID"]) + if any( pkg_fltr[attr] != version_match.group(attr) + for attr in ('epoch', 'version', 'release', 'arch')): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + 'Inconsistent \"Version\" and \"SoftwareElementID\" key' + ' properties.') + if ( objpath['SoftwareElementState'] + != Values.SoftwareElementState.Executable): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Only \"Executable\" software element state supported") + if not util.check_target_operating_system( + objpath['TargetOperatingSystem']): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Wrong target operating system.") + + ydb = YumDB.get_instance() + pkg_check = None + if ( objpath["CheckID"].lower() + != "LMI:LMI_SoftwareIdentityFileCheck".lower()): + check = get_existing_check_from_job(objpath["CheckID"]) + if check is not None: + pkg_info, pkg_check = check + # else - job does not exist (could be deleted) + + if pkg_check is None: + # let the YumWorker check the rest + try: + pkg_info, pkg_check = ydb.check_package_file( + objpath["SoftwareElementID"], objpath["Name"]) + except errors.PackageNotFound: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Could not find package matching SoftwareElementID \"%s\"" % + objpath["SoftwareElementID"]) + except errors.FileNotFound as exc: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, exc.args[1]) + + # last check for file path + if objpath["Name"] not in pkg_check: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'File \"%s\" not found in package \"%s\".' % + objpath["SoftwareElementID"]) + return FileCheck(pkg_info, pkg_check[objpath["Name"]], + pkg_check.file_checksum_type) + +@cmpi_logging.trace_function +def test_file(pkg_info, checksum_type, pkg_file): + """ + :param checksum_type: (``int``) Identifier of checksum algorithm used + in rpm package. It's one of keys of + ``yum.constants.RPM_CHECKSUM_TYPES`` dictionary. + :rtype: (``FileCheck``) + """ + for var, cls in ( + ("pkg_info", packageinfo.PackageInfo), + ("pkg_file", packagecheck.PackageFile)): + if not isinstance(locals()[var], cls): + raise TypeError("%s must be an instance of %s, not %s" % ( + var, cls.__name__, pkg_file.__class__.__name__)) + return FileCheck(pkg_info, pkg_file, checksum_type) + +@cmpi_logging.trace_function +def _file_check2failed_flags(file_check): + """ + Makes value of ``FailedFlags`` property from ``FileCheck`` instance. + + :param file_check: (``FileCheck``) + :rtype: (``pywbem.CIMProperty``) Property ``FailedFlags`` value. + """ + if not isinstance(file_check, FileCheck): + raise TypeError("file_check must be an instance of FileCheck") + + if not file_check.exists: + return [Values.FailedFlags.Existence] + flags = set() + fails_on = lambda attr: ( + getattr(file_check, attr)[0] != getattr(file_check, attr)[1]) + if fails_on('file_type'): + flags.add(Values.FailedFlags.FileMode) + + # file type specific checks + if file_check.file_type[1] == packagecheck.FILE_TYPE_FILE: + if fails_on('file_size'): + flags.add(Values.FailedFlags.FileSize) + if fails_on('last_modification_time'): + flags.add(Values.FailedFlags.Last_Modification_Time) + if fails_on('file_checksum'): + flags.add(Values.FailedFlags.Checksum) + elif file_check.file_type[1] in ( + packagecheck.FILE_TYPE_CHARACTER_DEVICE, + packagecheck.FILE_TYPE_BLOCK_DEVICE): + if fails_on('device'): + flags.add(Values.FailedFlags.Device_Number) + elif file_check.file_type[1] == packagecheck.FILE_TYPE_SYMLINK: + if fails_on('link_target'): + flags.add(Values.FailedFlags.LinkTarget) + + # generic checks + if ( fails_on('file_mode') + # do not check permissions for symlinks + and file_check.file_type[1] != packagecheck.FILE_TYPE_SYMLINK): + flags.add(Values.FailedFlags.FileMode) + if fails_on('user_id'): + flags.add(Values.FailedFlags.UserID) + if fails_on('group_id'): + flags.add(Values.FailedFlags.GroupID) + + return list(flags) + +@cmpi_logging.trace_function +def file_check_passed(file_check): + """ + Verify single file and return true if it passes. + """ + return len(_file_check2failed_flags(file_check)) == 0 + +@cmpi_logging.trace_function +def _fill_non_key_values(model, file_check): + """ + Fills a non key values into instance of SoftwareIdentityFileCheck. + + :param model: (``CIMInstance``) Model, that will be filled with + all supported non-key properties. + """ + set_prop = lambda name, kwargs: \ + model.__setitem__(name, pywbem.CIMProperty(name, **kwargs)) + + set_prop("CheckMode", dict(type='boolean', value=True)) + model["ChecksumType"] = pywbem.Uint16(file_check.file_checksum_type) + + set_prop("FailedFlags", dict(type='uint16', is_array=True, + value=_file_check2failed_flags(file_check))) + for mattr, fattr, kwargs, convert in ( + ('LastModificationTime', 'last_modification_time', + {'type':'uint64'}, pywbem.Uint64), + ('FileType' , 'file_type' ,{'type':'uint16'}, pywbem.Uint16), + ('UserID' , 'user_id' ,{'type':'uint32'}, pywbem.Uint32), + ('GroupID' , 'group_id' ,{'type':'uint32'}, pywbem.Uint32), + ('FileMode' , 'file_mode' ,{'type':'uint32'}, pywbem.Uint32), + ('FileSize' , 'file_size' ,{'type':'uint64'}, pywbem.Uint64), + ('LinkTarget' , 'link_target' ,{'type':'string'}, str), + ('FileChecksum', 'file_checksum',{'type':'string'}, str)): + installed, orig = getattr(file_check, fattr) + kwargs['value'] = None if orig is None else convert(orig) + set_prop(mattr+"Original", kwargs) + kwargs['value'] = None if installed is None else convert(installed) + set_prop(mattr, kwargs) + model['FileName'] = os.path.basename(file_check.path) + for suf in ('', 'Original'): + set_prop('FileModeFlags'+suf, dict(type='uint8', is_array=True, + value=mode2pywbem_flags(file_check.file_mode[1]))) + set_prop("FileExists", dict(type='boolean', value=file_check.exists)) + set_prop('MD5Checksum', dict(type='string', + value=file_check.md5_checksum)) + +@cmpi_logging.trace_function +def file_check2model(file_check, keys_only=True, model=None, job=None): + """ + :param file_check: (``FileCheck``) File check information to transform + to cim instance. + :param keys_only: (``bool``) Whether to fill non-key properties. + :param model: (``CIMInstance`` | ``CIMInstanceName``) If given, + it will be used as a template. Only property values will be set. + This instance will be returned on completion. + :param job: (``YumAsyncJob``) If this is a consequence of asynchronous + job operation, the instance of job should be supplied so that + ``CheckID`` property can be filled. + :rtype: (``CIMInstance`` | ``CIMInstanceName``) Object of + LMI_SoftwareIdentityFileCheck with filled desired values. + """ + if job is not None and not isinstance(job, jobs.YumCheckPackage): + raise TypeError("job must be instance of jobs.YumCheckPackage") + if not isinstance(file_check, FileCheck): + raise TypeError("file_check must be an instance of FileCheck") + + if model is None: + model = pywbem.CIMInstanceName("LMI_SoftwareIdentityFileCheck", + namespace="root/cimv2") + if not keys_only: + model = pywbem.CIMInstance("LMI_SoftwareIdentityFileCheck", + path=model) + + if not keys_only: + model.path.update( #pylint: disable=E1103 + {k: None for k in ("Name", "SoftwareElementID", + "SoftwareElementState", "TargetOperatingSystem", "Version", + "CheckID")}) + + model['Name'] = file_check.path + model['SoftwareElementID'] = file_check.pkg_info.nevra + model['SoftwareElementState'] = Values.SoftwareElementState.Executable + model['TargetOperatingSystem'] = pywbem.Uint16( + util.get_target_operating_system()[0]) + model['Version'] = file_check.pkg_info.evra + model['CheckID'] = ("LMI:LMI_SoftwareVerificationJob:%d" % job.jobid + if job is not None else "LMI:%s" % model.classname) + if not keys_only: + _fill_non_key_values(model, file_check) + return model + diff --git a/src/software/lmi/software/core/IdentityResource.py b/src/software/lmi/software/core/IdentityResource.py new file mode 100644 index 0000000..228015b --- /dev/null +++ b/src/software/lmi/software/core/IdentityResource.py @@ -0,0 +1,650 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Just a common functionality related to LMI_SoftwareIdentityResource provider. +""" + +import pywbem +import socket + +from lmi.common import cmpi_logging +from lmi.software.core import ComputerSystem +from lmi.software.yumdb import YumDB +from lmi.software.yumdb.repository import Repository + +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.. + _reverse_map = { + 0 : 'Not Available', + 1 : 'No Additional Information', + 2 : 'Stressed', + 3 : 'Predictive Failure', + 4 : 'Non-Recoverable Error', + 5 : 'Supporting Entity in Error' + } + + class RequestedState(object): + Unknown = pywbem.Uint16(0) + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shut_Down = pywbem.Uint16(4) + No_Change = pywbem.Uint16(5) + Offline = pywbem.Uint16(6) + Test = pywbem.Uint16(7) + Deferred = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Reboot = pywbem.Uint16(10) + Reset = pywbem.Uint16(11) + Not_Applicable = pywbem.Uint16(12) + # DMTF_Reserved = .. + # Vendor_Reserved = 32768..65535 + _reverse_map = { + 0 : 'Unknown', + 2 : 'Enabled', + 3 : 'Disabled', + 4 : 'Shut Down', + 5 : 'No Change', + 6 : 'Offline', + 7 : 'Test', + 8 : 'Deferred', + 9 : 'Quiesce', + 10 : 'Reboot', + 11 : 'Reset', + 12 : 'Not Applicable' + } + + 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 + _reverse_map = { + 0 : 'Unknown', + 5 : 'OK', + 10 : 'Degraded/Warning', + 15 : 'Minor failure', + 20 : 'Major failure', + 25 : 'Critical failure', + 30 : 'Non-recoverable error' + } + + class TransitioningToState(object): + Unknown = pywbem.Uint16(0) + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shut_Down = pywbem.Uint16(4) + No_Change = pywbem.Uint16(5) + Offline = pywbem.Uint16(6) + Test = pywbem.Uint16(7) + Defer = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Reboot = pywbem.Uint16(10) + Reset = pywbem.Uint16(11) + Not_Applicable = pywbem.Uint16(12) + # DMTF_Reserved = .. + _reverse_map = { + 0 : 'Unknown', + 2 : 'Enabled', + 3 : 'Disabled', + 4 : 'Shut Down', + 5 : 'No Change', + 6 : 'Offline', + 7 : 'Test', + 8 : 'Defer', + 9 : 'Quiesce', + 10 : 'Reboot', + 11 : 'Reset', + 12 : 'Not Applicable' + } + + class ResourceType(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Installer_and_Payload = pywbem.Uint16(2) + Installer = pywbem.Uint16(3) + Payload = pywbem.Uint16(4) + Installability_checker = pywbem.Uint16(5) + Security_Advisory = pywbem.Uint16(6) + Engineering_Advisory = pywbem.Uint16(7) + Technical_release_notes = pywbem.Uint16(9) + Change_notification = pywbem.Uint16(10) + Whitepaper = pywbem.Uint16(11) + Marketing_Documentation = pywbem.Uint16(12) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000..0xFFFF + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'Installer and Payload', + 3 : 'Installer', + 4 : 'Payload', + 5 : 'Installability checker', + 6 : 'Security Advisory', + 7 : 'Engineering Advisory', + 9 : 'Technical release notes', + 10 : 'Change notification', + 11 : 'Whitepaper', + 12 : 'Marketing Documentation' + } + + class EnabledState(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shutting_Down = pywbem.Uint16(4) + Not_Applicable = pywbem.Uint16(5) + Enabled_but_Offline = pywbem.Uint16(6) + In_Test = pywbem.Uint16(7) + Deferred = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Starting = pywbem.Uint16(10) + # DMTF_Reserved = 11..32767 + # Vendor_Reserved = 32768..65535 + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'Enabled', + 3 : 'Disabled', + 4 : 'Shutting Down', + 5 : 'Not Applicable', + 6 : 'Enabled but Offline', + 7 : 'In Test', + 8 : 'Deferred', + 9 : 'Quiesce', + 10 : 'Starting' + } + + class ExtendedResourceType(object): + Unknown = pywbem.Uint16(0) + Not_Applicable = pywbem.Uint16(2) + Linux_RPM = pywbem.Uint16(3) + HP_UX_Depot = pywbem.Uint16(4) + Windows_MSI = pywbem.Uint16(5) + Solaris_Package = pywbem.Uint16(6) + Macintosh_Disk_Image = pywbem.Uint16(7) + Debian_linux_Package = pywbem.Uint16(8) + HP_Smart_Component = pywbem.Uint16(11) + # Vendor_Reserved = 101..200 + HTML = pywbem.Uint16(201) + PDF = pywbem.Uint16(202) + Text_File = pywbem.Uint16(203) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000..0xFFFF + _reverse_map = { + 0 : 'Unknown', + 2 : 'Not Applicable', + 3 : 'Linux RPM', + 4 : 'HP-UX Depot', + 5 : 'Windows MSI', + 6 : 'Solaris Package', + 7 : 'Macintosh Disk Image', + 8 : 'Debian linux Package', + 201 : 'HTML', + 202 : 'PDF', + 11 : 'HP Smart Component', + 203 : 'Text File' + } + + class AccessContext(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Default_Gateway = pywbem.Uint16(2) + DNS_Server = pywbem.Uint16(3) + SNMP_Trap_Destination = pywbem.Uint16(4) + MPLS_Tunnel_Destination = pywbem.Uint16(5) + DHCP_Server = pywbem.Uint16(6) + SMTP_Server = pywbem.Uint16(7) + LDAP_Server = pywbem.Uint16(8) + Network_Time_Protocol__NTP__Server = pywbem.Uint16(9) + Management_Service = pywbem.Uint16(10) + internet_Storage_Name_Service__iSNS_ = pywbem.Uint16(11) + # DMTF_Reserved = .. + # Vendor_Reserved = 32768..65535 + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'Default Gateway', + 3 : 'DNS Server', + 4 : 'SNMP Trap Destination', + 5 : 'MPLS Tunnel Destination', + 6 : 'DHCP Server', + 7 : 'SMTP Server', + 8 : 'LDAP Server', + 9 : 'Network Time Protocol (NTP) Server', + 10 : 'Management Service', + 11 : 'internet Storage Name Service (iSNS)' + } + + class AvailableRequestedStates(object): + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shut_Down = pywbem.Uint16(4) + Offline = pywbem.Uint16(6) + Test = pywbem.Uint16(7) + Defer = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Reboot = pywbem.Uint16(10) + Reset = pywbem.Uint16(11) + # DMTF_Reserved = .. + _reverse_map = { + 2 : 'Enabled', + 3 : 'Disabled', + 4 : 'Shut Down', + 6 : 'Offline', + 7 : 'Test', + 8 : 'Defer', + 9 : 'Quiesce', + 10 : 'Reboot', + 11 : 'Reset' + } + + 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 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.. + _reverse_map = { + 0 : 'Unknown', + 1 : 'Not Available', + 2 : 'Communication OK', + 3 : 'Lost Communication', + 4 : 'No Contact' + } + + 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.. + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'OK', + 3 : 'Degraded', + 4 : 'Stressed', + 5 : 'Predictive Failure', + 6 : 'Error', + 7 : 'Non-Recoverable Error', + 8 : 'Starting', + 9 : 'Stopping', + 10 : 'Stopped', + 11 : 'In Service', + 12 : 'No Contact', + 13 : 'Lost Communication', + 14 : 'Aborted', + 15 : 'Dormant', + 16 : 'Supporting Entity in Error', + 17 : 'Completed', + 18 : 'Power Mode', + 19 : 'Relocating' + } + + 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.. + _reverse_map = { + 0 : 'Unknown', + 1 : 'Not Available', + 2 : 'Servicing', + 3 : 'Starting', + 4 : 'Stopping', + 5 : 'Stopped', + 6 : 'Aborted', + 7 : 'Dormant', + 8 : 'Completed', + 9 : 'Migrating', + 10 : 'Emigrating', + 11 : 'Immigrating', + 12 : 'Snapshotting', + 13 : 'Shutting Down', + 14 : 'In Test', + 15 : 'Transitioning', + 16 : 'In Service' + } + + class RequestStateChange(object): + Completed_with_No_Error = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unknown_or_Unspecified_Error = pywbem.Uint32(2) + Cannot_complete_within_Timeout_Period = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + In_Use = pywbem.Uint32(6) + # DMTF_Reserved = .. + Method_Parameters_Checked___Job_Started = pywbem.Uint32(4096) + Invalid_State_Transition = pywbem.Uint32(4097) + Use_of_Timeout_Parameter_Not_Supported = pywbem.Uint32(4098) + Busy = pywbem.Uint32(4099) + # Method_Reserved = 4100..32767 + # Vendor_Specific = 32768..65535 + class RequestedState(object): + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shut_Down = pywbem.Uint16(4) + Offline = pywbem.Uint16(6) + Test = pywbem.Uint16(7) + Defer = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Reboot = pywbem.Uint16(10) + Reset = pywbem.Uint16(11) + # DMTF_Reserved = .. + # Vendor_Reserved = 32768..65535 + + class EnabledDefault(object): + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Not_Applicable = pywbem.Uint16(5) + Enabled_but_Offline = pywbem.Uint16(6) + No_Default = pywbem.Uint16(7) + Quiesce = pywbem.Uint16(9) + # DMTF_Reserved = .. + # Vendor_Reserved = 32768..65535 + _reverse_map = { + 2 : 'Enabled', + 3 : 'Disabled', + 5 : 'Not Applicable', + 6 : 'Enabled but Offline', + 7 : 'No Default', + 9 : 'Quiesce' + } + + class PrimaryStatus(object): + Unknown = pywbem.Uint16(0) + OK = pywbem.Uint16(1) + Degraded = pywbem.Uint16(2) + Error = pywbem.Uint16(3) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + _reverse_map = { + 0 : 'Unknown', + 1 : 'OK', + 2 : 'Degraded', + 3 : 'Error' + } + + class InfoFormat(object): + Other = pywbem.Uint16(1) + Host_Name = pywbem.Uint16(2) + IPv4_Address = pywbem.Uint16(3) + IPv6_Address = pywbem.Uint16(4) + IPX_Address = pywbem.Uint16(5) + DECnet_Address = pywbem.Uint16(6) + SNA_Address = pywbem.Uint16(7) + Autonomous_System_Number = pywbem.Uint16(8) + MPLS_Label = pywbem.Uint16(9) + IPv4_Subnet_Address = pywbem.Uint16(10) + IPv6_Subnet_Address = pywbem.Uint16(11) + IPv4_Address_Range = pywbem.Uint16(12) + IPv6_Address_Range = pywbem.Uint16(13) + Dial_String = pywbem.Uint16(100) + Ethernet_Address = pywbem.Uint16(101) + Token_Ring_Address = pywbem.Uint16(102) + ATM_Address = pywbem.Uint16(103) + Frame_Relay_Address = pywbem.Uint16(104) + URL = pywbem.Uint16(200) + FQDN = pywbem.Uint16(201) + User_FQDN = pywbem.Uint16(202) + DER_ASN1_DN = pywbem.Uint16(203) + DER_ASN1_GN = pywbem.Uint16(204) + Key_ID = pywbem.Uint16(205) + Parameterized_URL = pywbem.Uint16(206) + # DMTF_Reserved = .. + # Vendor_Reserved = 32768..65535 + _reverse_map = { + 1 : 'Other', + 2 : 'Host Name', + 3 : 'IPv4 Address', + 4 : 'IPv6 Address', + 5 : 'IPX Address', + 6 : 'DECnet Address', + 7 : 'SNA Address', + 8 : 'Autonomous System Number', + 9 : 'MPLS Label', + 10 : 'IPv4 Subnet Address', + 11 : 'IPv6 Subnet Address', + 12 : 'IPv4 Address Range', + 13 : 'IPv6 Address Range', + 200 : 'URL', + 201 : 'FQDN', + 202 : 'User FQDN', + 203 : 'DER ASN1 DN', + 204 : 'DER ASN1 GN', + 205 : 'Key ID', + 206 : 'Parameterized URL', + 100 : 'Dial String', + 101 : 'Ethernet Address', + 102 : 'Token Ring Address', + 103 : 'ATM Address', + 104 : 'Frame Relay Address' + } + +@cmpi_logging.trace_function +def object_path2repo(env, op, kind='enabled'): + """ + @param op must contain precise information of repository, + otherwise an error is raised + """ + if not isinstance(kind, basestring): + raise TypeError("kind must be a string") + if not isinstance(op, pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "op must be an instance of CIMInstanceName") + + if ( (not "CreationClassName" in op or not op['CreationClassName']) + or (not "Name" in op or not op["Name"]) + or ( not "SystemCreationClassName" in op + or not op["SystemCreationClassName"]) + or (not "SystemName" in op or not op["SystemName"])): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, "Wrong keys.") + if op["SystemName"] != socket.gethostname(): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'SystemName "%s" does not match "%s".' % ( + op["SystemName"], socket.gethostname())) + ch = env.get_cimom_handle() + if not ch.is_subclass("root/cimv2", + sub=op["CreationClassName"], + super="CIM_SoftwareIdentityResource"): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + 'CreationClassName \"%s\" must be a subclass of "%s".' % ( + op["CreationClassName"], "CIM_SoftwareIdentityResource")) + if not ch.is_subclass("root/cimv2", + sub=op["SystemCreationClassName"], + super="CIM_ComputerSystem"): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + 'SystemCreationClassName of \"%s\" must be a subclass of "%s".' + % (op["CreationClassName"], "CIM_SoftwareIdentityResource")) + repos = YumDB.get_instance().filter_repositories(kind, repoid=op["Name"]) + if len(repos) < 1: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'No matching repository found for Name=\"%s\".' % op["Name"]) + return repos[0] + +@cmpi_logging.trace_function +def _fill_non_keys(repo, model): + """ + Fills into the model of instance all non-key properties. + """ + model['AccessContext'] = Values.AccessContext.Other + if repo.mirror_list: + access_info = repo.mirror_list + elif repo.base_urls: + if len(repo.base_urls) > 0: + if len(repo.base_urls) > 1: + cmpi_logging.logger.warn( + 'multiple base urls found for repository "%s", selecting' + ' the last one', repo) + access_info = repo.base_urls[-1] + else: + cmpi_logging.logger.error( + 'no base url found for repository "%s"' % repo) + access_info = pywbem.CIMProperty('AccessInfo', + None, type='string') + model["AccessInfo"] = access_info + model['AvailableRequestedStates'] = [ + Values.AvailableRequestedStates.Enabled, + Values.AvailableRequestedStates.Disabled] + model['Caption'] = repo.name + model['Cost'] = pywbem.Sint32(repo.cost) + model['Description'] = "[%s] - %s for %s architecture with cost %d" % ( + repo.repoid, repo.name, repo.basearch, repo.cost) + model['ElementName'] = repo.repoid + model['EnabledDefault'] = Values.EnabledDefault.Not_Applicable + if repo.enabled: + model['EnabledState'] = Values.EnabledState.Enabled + else: + model['EnabledState'] = Values.EnabledState.Disabled + model['ExtendedResourceType'] = Values.ExtendedResourceType.Linux_RPM + model['GPGCheck'] = repo.gpg_check + if repo.ready: + model['HealthState'] = Values.HealthState.OK + else: + model['HealthState'] = Values.HealthState.Major_failure + if repo.revision is not None: + model["Generation"] = pywbem.Uint64(repo.revision) + else: + model['Generation'] = pywbem.CIMProperty('Generation', + None, type='uint64') + model['InfoFormat'] = Values.InfoFormat.URL + model['InstanceID'] = 'LMI:LMI_SoftwareIdentityResource:' + repo.repoid + if repo.mirror_list: + model["MirrorList"] = repo.mirror_list + else: + model['MirrorList'] = pywbem.CIMProperty('MirrorList', + None, type='string') + model['OperationalStatus'] = [ Values.OperationalStatus.OK + if repo.ready else Values.OperationalStatus.Error] + model['OtherAccessContext'] = "YUM package repository" + model['OtherResourceType'] = "RPM Software Package" + # this would need to populateSack, which is expensive + #model["PackageCount"] = pywbem.Uint32(repo.pkg_count) + if repo.ready: + model['PrimaryStatus'] = Values.PrimaryStatus.OK + else: + model['PrimaryStatus'] = Values.PrimaryStatus.Error + model['RepoGPGCheck'] = repo.repo_gpg_check + if repo.enabled: + model['RequestedState'] = Values.RequestedState.Enabled + else: + model['RequestedState'] = Values.RequestedState.Disabled + model['ResourceType'] = Values.ResourceType.Other + model['StatusDescriptions'] = [ "Ready" if repo.ready else "Not Ready" ] + model['TimeOfLastStateChange'] = pywbem.CIMDateTime(repo.last_edit) + if repo.last_update is not None: + model['TimeOfLastUpdate'] = pywbem.CIMDateTime(repo.last_update) + else: + model['TimeOfLastUpdate'] = pywbem.CIMProperty('TimeOfLastUpdate', + None, type='datetime') + model['TransitioningToState'] = Values.TransitioningToState.Not_Applicable + +@cmpi_logging.trace_function +def repo2model(repo, keys_only=True, model=None): + """ + @param model if not None, will be filled with data, otherwise + a new instance of CIMInstance or CIMObjectPath is created + """ + if not isinstance(repo, Repository): + raise TypeError("pkg must be an instance of Repository") + if model is None: + model = pywbem.CIMInstanceName("LMI_SoftwareIdentityResource", + namespace="root/cimv2") + if not keys_only: + model = pywbem.CIMInstance( + "LMI_SoftwareIdentityResource", path=model) + if isinstance(model, pywbem.CIMInstance): + def _set_key(k, value): + """Sets the value of key property of cim instance""" + model[k] = value + model.path[k] = value #pylint: disable=E1103 + else: + _set_key = model.__setitem__ + _set_key('CreationClassName', "LMI_SoftwareIdentityResource") + _set_key("Name", repo.repoid) + _set_key("SystemCreationClassName", "Linux_ComputerSystem") + _set_key("SystemName", ComputerSystem.get_path()["Name"]) + if not keys_only: + _fill_non_keys(repo, model) + + return model + diff --git a/src/software/lmi/software/core/InstMethodCall.py b/src/software/lmi/software/core/InstMethodCall.py new file mode 100644 index 0000000..82b7b1e --- /dev/null +++ b/src/software/lmi/software/core/InstMethodCall.py @@ -0,0 +1,144 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 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/>. + +""" +CIM values for enumeration types of CIM_InstMethodCall indication class. +""" + +import pywbem +import socket + +from lmi.common import cmpi_logging +from lmi.software.core import Job +from lmi.software.core import InstallationService +from lmi.software.yumdb import jobs, errors + +class Values(object): + class ReturnValueType(object): + boolean = pywbem.Uint16(2) + string = pywbem.Uint16(3) + char16 = pywbem.Uint16(4) + uint8 = pywbem.Uint16(5) + sint8 = pywbem.Uint16(6) + uint16 = pywbem.Uint16(7) + sint16 = pywbem.Uint16(8) + uint32 = pywbem.Uint16(9) + sint32 = pywbem.Uint16(10) + uint64 = pywbem.Uint16(11) + sint64 = pywbem.Uint16(12) + datetime = pywbem.Uint16(13) + real32 = pywbem.Uint16(14) + real64 = pywbem.Uint16(15) + reference = pywbem.Uint16(16) + # DMTF_Reserved = .. + _reverse_map = { + 2: 'boolean', + 3: 'string', + 4: 'char16', + 5: 'uint8', + 6: 'sint8', + 7: 'uint16', + 8: 'sint16', + 9: 'uint32', + 10: 'sint32', + 11: 'uint64', + 12: 'sint64', + 13: 'datetime', + 14: 'real32', + 15: 'real64', + 16: 'reference' + } + + class PerceivedSeverity(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Information = pywbem.Uint16(2) + Degraded_Warning = pywbem.Uint16(3) + Minor = pywbem.Uint16(4) + Major = pywbem.Uint16(5) + Critical = pywbem.Uint16(6) + Fatal_NonRecoverable = pywbem.Uint16(7) + # DMTF_Reserved = .. + _reverse_map = { + 0: 'Unknown', + 1: 'Other', + 2: 'Information', + 3: 'Degraded/Warning', + 4: 'Minor', + 5: 'Major', + 6: 'Critical', + 7: 'Fatal/NonRecoverable' + } + +@cmpi_logging.trace_function +def job2model(job, pre=True): + """ + Create post or pre indication instance used by clients to subscribe + to job's state changes. + + :param job: (``YumJob``) Instance of job created as a result of method + invocation. + :param pre: (``bool``) says, whether to make pre or post indication + instance. + :rtype CIMInstance of CIM_InstMethodCall. + """ + if not isinstance(job, jobs.YumJob): + raise TypeError("job must be a YumJob") + if not pre and job.state == job.NEW or job.state == job.RUNNING: + raise ValueError("job must be finished to make a post indication" + " instance") + path = pywbem.CIMInstanceName( + classname="CIM_InstMethodCall", + host=socket.gethostname(), + namespace="root/cimv2") + inst = pywbem.CIMInstance(classname="CIM_InstMethodCall", path=path) + src_instance = Job.job2model(job, keys_only=False) + inst['SourceInstance'] = pywbem.CIMProperty("SourceInstance", + type="instance", value=src_instance) + inst['SourceInstanceModelPath'] = \ + str(src_instance.path) #pylint: disable=E1103 + inst['MethodName'] = Job.JOB_METHOD_NAMES[ + job.metadata["method"]] + inst['MethodParameters'] = Job.make_method_params( + job, '__MethodParameters', True, not pre) + inst['PreCall'] = pre + + if not pre: + inst["Error"] = pywbem.CIMProperty("Error", type="instance", + is_array=True, value=[]) + error = Job.job2error(job) + if error is not None: + inst["Error"].append(error) + inst["ReturnValueType"] = Values.ReturnValueType.uint32 + if job.state == job.COMPLETED: + inst["ReturnValue"] = str(InstallationService. \ + Values.InstallFromURI.Job_Completed_with_No_Error) + elif job.state == job.EXCEPTION: + if issubclass(job.result_data[0], ( + errors.InvalidNevra, errors.InvalidURI, + errors.PackageNotFound)): + inst["ReturnValue"] = str(InstallationService.Values. \ + InstallFromURI.Invalid_Parameter) + else: + inst["ReturnValue"] = str(InstallationService.Values. \ + InstallFromURI.Failed) + else: + inst["ReturnValue"] = str(InstallationService.Values. \ + InstallFromURI.Unspecified_Error) + return inst + diff --git a/src/software/lmi/software/core/InstallationService.py b/src/software/lmi/software/core/InstallationService.py new file mode 100644 index 0000000..557f913 --- /dev/null +++ b/src/software/lmi/software/core/InstallationService.py @@ -0,0 +1,826 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to LMI_SoftwareInstallationService +provider. +""" + +import pywbem + +from lmi.common import cmpi_logging +from lmi.software.core import ComputerSystem +from lmi.software.core import Identity +from lmi.software.core import Job +from lmi.software.core import SystemCollection +from lmi.software.yumdb import errors +from lmi.software.yumdb import YumDB + +JOB_METHOD_SRC_PARAM_NAMES = ["URI", "Source", "Image", "Source"] + +class InstallationError(Exception): + """This exception shall be raised upon any error within + install_or_remove_package() function. + """ + def __init__(self, return_code, description): + Exception.__init__(self, return_code, description) + @property + def return_code(self): + """@return return code of CIM method""" + return self.args[0] + @property + def description(self): + """@return description of error""" + return self.args[1] + +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.. + _reverse_map = { + 0: 'Not Available', + 1: 'No Additional Information', + 2: 'Stressed', + 3: 'Predictive Failure', + 4: 'Non-Recoverable Error', + 5: 'Supporting Entity in Error' + } + + class RequestedState(object): + Unknown = pywbem.Uint16(0) + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shut_Down = pywbem.Uint16(4) + No_Change = pywbem.Uint16(5) + Offline = pywbem.Uint16(6) + Test = pywbem.Uint16(7) + Deferred = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Reboot = pywbem.Uint16(10) + Reset = pywbem.Uint16(11) + Not_Applicable = pywbem.Uint16(12) + # DMTF_Reserved = .. + # Vendor_Reserved = 32768..65535 + _reverse_map = { + 0: 'Unknown', + 2: 'Enabled', + 3: 'Disabled', + 4: 'Shut Down', + 5: 'No Change', + 6: 'Offline', + 7: 'Test', + 8: 'Deferred', + 9: 'Quiesce', + 10: 'Reboot', + 11: 'Reset', + 12: 'Not Applicable' + } + + 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 + _reverse_map = { + 0: 'Unknown', + 5: 'OK', + 10: 'Degraded/Warning', + 15: 'Minor failure', + 20: 'Major failure', + 25: 'Critical failure', + 30: 'Non-recoverable error' + } + + class InstallFromURI(object): + Job_Completed_with_No_Error = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unspecified_Error = pywbem.Uint32(2) + Timeout = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + Target_In_Use = pywbem.Uint32(6) + # DMTF_Reserved = .. + Method_Parameters_Checked___Job_Started = pywbem.Uint32(4096) + Unsupported_TargetType = pywbem.Uint32(4097) + Unattended_silent_installation_not_supported = pywbem.Uint32(4098) + Downgrade_reinstall_not_supported = pywbem.Uint32(4099) + Not_enough_memory = pywbem.Uint32(4100) + Not_enough_swap_space = pywbem.Uint32(4101) + Unsupported_version_transition = pywbem.Uint32(4102) + Not_enough_disk_space = pywbem.Uint32(4103) + Software_and_target_operating_system_mismatch = pywbem.Uint32(4104) + Missing_dependencies = pywbem.Uint32(4105) + Not_applicable_to_target = pywbem.Uint32(4106) + URI_not_accessible = pywbem.Uint32(4107) + # Method_Reserved = 4108..32767 + # Vendor_Specific = 32768..65535 + class InstallOptions(object): + Defer_target_system_reset = pywbem.Uint16(2) + Force_installation = pywbem.Uint16(3) + Install = pywbem.Uint16(4) + Update = pywbem.Uint16(5) + Repair = pywbem.Uint16(6) + Reboot = pywbem.Uint16(7) + Password = pywbem.Uint16(8) + Uninstall = pywbem.Uint16(9) + Log = pywbem.Uint16(10) + SilentMode = pywbem.Uint16(11) + AdministrativeMode = pywbem.Uint16(12) + ScheduleInstallAt = pywbem.Uint16(13) + # DMTF_Reserved = .. + # Vendor_Specific = 32768..65535 + _reverse_map = { + 2 : "Defer Target/System Reset", + 3 : "Force Installation", + 4 : "Install", + 5 : "Update", + 6 : "Repair", + 7 : "Reboot", + 8 : "Password", + 9 : "Uninstall", + 10 : "Log", + 11 : "SilentMode", + 12 : "AdministrativeMode", + 13 : "ScheduleInstallAt", + } + + InstallOptions.supported = { + InstallOptions.Install, + InstallOptions.Update, + InstallOptions.Uninstall, + InstallOptions.Force_installation, + InstallOptions.Repair, + } + + + class CheckSoftwareIdentity(object): + Job_Completed_with_No_Error = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unspecified_Error = pywbem.Uint32(2) + Timeout = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + Target_In_Use = pywbem.Uint32(6) + # DMTF_Reserved = .. + Method_Reserved = pywbem.Uint32(4096) + Unsupported_TargetType = pywbem.Uint32(4097) + Unattended_silent_installation_not_supported = pywbem.Uint32(4098) + Downgrade_reinstall_not_supported = pywbem.Uint32(4099) + Not_enough_memory = pywbem.Uint32(4100) + Not_enough_swap_space = pywbem.Uint32(4101) + Unsupported_version_transition = pywbem.Uint32(4102) + Not_enough_disk_space = pywbem.Uint32(4103) + Software_and_target_operating_system_mismatch = pywbem.Uint32(4104) + Missing_dependencies = pywbem.Uint32(4105) + Not_applicable_to_target = pywbem.Uint32(4106) + No_supported_path_to_image = pywbem.Uint32(4107) + Cannot_add_to_Collection = pywbem.Uint32(4108) + Asynchronous_Job_already_in_progress = pywbem.Uint32(4109) + # Method_Reserved = 4110..32767 + # Vendor_Specific = 32768..65535 + class InstallCharacteristics(object): + Target_automatic_reset = pywbem.Uint16(2) + System_automatic_reset = pywbem.Uint16(3) + Separate_target_reset_Required = pywbem.Uint16(4) + Separate_system_reset_Required = pywbem.Uint16(5) + Manual_Reboot_Required = pywbem.Uint16(6) + No_Reboot_Required = pywbem.Uint16(7) + User_Intervention_recommended = pywbem.Uint16(8) + MAY_be_added_to_specified_Collection = pywbem.Uint16(9) + # DMTF_Reserved = .. + # Vendor_Specific = 0x7FFF..0xFFFF + + class ChangeAffectedElementsAssignedSequence(object): + Completed_with_No_Error = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Error_Occured = pywbem.Uint32(2) + Busy = pywbem.Uint32(3) + Invalid_Reference = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + Access_Denied = pywbem.Uint32(6) + # DMTF_Reserved = 7..32767 + # Vendor_Specified = 32768..65535 + + class TransitioningToState(object): + Unknown = pywbem.Uint16(0) + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shut_Down = pywbem.Uint16(4) + No_Change = pywbem.Uint16(5) + Offline = pywbem.Uint16(6) + Test = pywbem.Uint16(7) + Defer = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Reboot = pywbem.Uint16(10) + Reset = pywbem.Uint16(11) + Not_Applicable = pywbem.Uint16(12) + # DMTF_Reserved = .. + _reverse_map = { + 0: 'Unknown', + 2: 'Enabled', + 3: 'Disabled', + 4: 'Shut Down', + 5: 'No Change', + 6: 'Offline', + 7: 'Test', + 8: 'Defer', + 9: 'Quiesce', + 10: 'Reboot', + 11: 'Reset', + 12: 'Not Applicable' + } + + class EnabledDefault(object): + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Not_Applicable = pywbem.Uint16(5) + Enabled_but_Offline = pywbem.Uint16(6) + No_Default = pywbem.Uint16(7) + Quiesce = pywbem.Uint16(9) + # DMTF_Reserved = .. + # Vendor_Reserved = 32768..65535 + _reverse_map = { + 2: 'Enabled', + 3: 'Disabled', + 5: 'Not Applicable', + 6: 'Enabled but Offline', + 7: 'No Default', + 9: 'Quiesce' + } + + class EnabledState(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shutting_Down = pywbem.Uint16(4) + Not_Applicable = pywbem.Uint16(5) + Enabled_but_Offline = pywbem.Uint16(6) + In_Test = pywbem.Uint16(7) + Deferred = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Starting = pywbem.Uint16(10) + # DMTF_Reserved = 11..32767 + # Vendor_Reserved = 32768..65535 + _reverse_map = { + 0: 'Unknown', + 1: 'Other', + 2: 'Enabled', + 3: 'Disabled', + 4: 'Shutting Down', + 5: 'Not Applicable', + 6: 'Enabled but Offline', + 7: 'In Test', + 8: 'Deferred', + 9: 'Quiesce', + 10: 'Starting' + } + + class VerifyInstalledIdentity(object): + Job_Completed_with_No_Error = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unspecified_Error = pywbem.Uint32(2) + Timeout = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + Target_In_Use = pywbem.Uint32(6) + # DMTF_Reserved = .. + Method_Parameters_Checked___Job_Started = pywbem.Uint32(4096) + Unsupported_TargetType = pywbem.Uint32(4097) + Method_Reserved = pywbem.Uint32(4098) + # Software_Identity_Not_Installed = 4099..32767 + Vendor_Specific = pywbem.Uint32(32768) + + class AvailableRequestedStates(object): + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shut_Down = pywbem.Uint16(4) + Offline = pywbem.Uint16(6) + Test = pywbem.Uint16(7) + Defer = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Reboot = pywbem.Uint16(10) + Reset = pywbem.Uint16(11) + # DMTF_Reserved = .. + _reverse_map = { + 2: 'Enabled', + 3: 'Disabled', + 4: 'Shut Down', + 6: 'Offline', + 7: 'Test', + 8: 'Defer', + 9: 'Quiesce', + 10: 'Reboot', + 11: 'Reset' + } + + 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 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.. + _reverse_map = { + 0: 'Unknown', + 1: 'Not Available', + 2: 'Communication OK', + 3: 'Lost Communication', + 4: 'No Contact' + } + + 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.. + _reverse_map = { + 0: 'Unknown', + 1: 'Other', + 2: 'OK', + 3: 'Degraded', + 4: 'Stressed', + 5: 'Predictive Failure', + 6: 'Error', + 7: 'Non-Recoverable Error', + 8: 'Starting', + 9: 'Stopping', + 10: 'Stopped', + 11: 'In Service', + 12: 'No Contact', + 13: 'Lost Communication', + 14: 'Aborted', + 15: 'Dormant', + 16: 'Supporting Entity in Error', + 17: 'Completed', + 18: 'Power Mode', + 19: 'Relocating' + } + + 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.. + _reverse_map = { + 0: 'Unknown', + 1: 'Not Available', + 2: 'Servicing', + 3: 'Starting', + 4: 'Stopping', + 5: 'Stopped', + 6: 'Aborted', + 7: 'Dormant', + 8: 'Completed', + 9: 'Migrating', + 10: 'Emigrating', + 11: 'Immigrating', + 12: 'Snapshotting', + 13: 'Shutting Down', + 14: 'In Test', + 15: 'Transitioning', + 16: 'In Service' + } + + class RequestStateChange(object): + Completed_with_No_Error = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unknown_or_Unspecified_Error = pywbem.Uint32(2) + Cannot_complete_within_Timeout_Period = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + In_Use = pywbem.Uint32(6) + # DMTF_Reserved = .. + Method_Parameters_Checked___Job_Started = pywbem.Uint32(4096) + Invalid_State_Transition = pywbem.Uint32(4097) + Use_of_Timeout_Parameter_Not_Supported = pywbem.Uint32(4098) + Busy = pywbem.Uint32(4099) + # Method_Reserved = 4100..32767 + # Vendor_Specific = 32768..65535 + class RequestedState(object): + Enabled = pywbem.Uint16(2) + Disabled = pywbem.Uint16(3) + Shut_Down = pywbem.Uint16(4) + Offline = pywbem.Uint16(6) + Test = pywbem.Uint16(7) + Defer = pywbem.Uint16(8) + Quiesce = pywbem.Uint16(9) + Reboot = pywbem.Uint16(10) + Reset = pywbem.Uint16(11) + # DMTF_Reserved = .. + # Vendor_Reserved = 32768..65535 + + class InstallFromByteStream(object): + Job_Completed_with_No_Error = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unspecified_Error = pywbem.Uint32(2) + Timeout = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + Target_In_Use = pywbem.Uint32(6) + # DMTF_Reserved = .. + Method_Parameters_Checked___Job_Started = pywbem.Uint32(4096) + Unsupported_TargetType = pywbem.Uint32(4097) + Unattended_silent_installation_not_supported = pywbem.Uint32(4098) + Downgrade_reinstall_not_supported = pywbem.Uint32(4099) + Not_enough_memory = pywbem.Uint32(4100) + Not_enough_swap_space = pywbem.Uint32(4101) + Unsupported_version_transition = pywbem.Uint32(4102) + Not_enough_disk_space = pywbem.Uint32(4103) + Software_and_target_operating_system_mismatch = pywbem.Uint32(4104) + Missing_dependencies = pywbem.Uint32(4105) + Not_applicable_to_target = pywbem.Uint32(4106) + No_supported_path_to_image = pywbem.Uint32(4107) + # Method_Reserved = 4108..32767 + # Vendor_Specific = 32768..65535 + + InstallFromByteStream.InstallOptions = InstallFromURI.InstallOptions + + class InstallFromSoftwareIdentity(object): + Job_Completed_with_No_Error = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unspecified_Error = pywbem.Uint32(2) + Timeout = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + Target_In_Use = pywbem.Uint32(6) + # DMTF_Reserved = .. + Method_Parameters_Checked___Job_Started = pywbem.Uint32(4096) + Unsupported_TargetType = pywbem.Uint32(4097) + Unattended_silent_installation_not_supported = pywbem.Uint32(4098) + Downgrade_reinstall_not_supported = pywbem.Uint32(4099) + Not_enough_memory = pywbem.Uint32(4100) + Not_enough_swap_space = pywbem.Uint32(4101) + Unsupported_version_transition = pywbem.Uint32(4102) + Not_enough_disk_space = pywbem.Uint32(4103) + Software_and_target_operating_system_mismatch = pywbem.Uint32(4104) + Missing_dependencies = pywbem.Uint32(4105) + Not_applicable_to_target = pywbem.Uint32(4106) + No_supported_path_to_image = pywbem.Uint32(4107) + Cannot_add_to_Collection = pywbem.Uint32(4108) + # Method_Reserved = 4109..32767 + # Vendor_Specific = 32768..65535 + + InstallFromSoftwareIdentity.InstallOptions = InstallFromURI.InstallOptions + + class StartMode(object): + Automatic = 'Automatic' + Manual = 'Manual' + + class PrimaryStatus(object): + Unknown = pywbem.Uint16(0) + OK = pywbem.Uint16(1) + Degraded = pywbem.Uint16(2) + Error = pywbem.Uint16(3) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + _reverse_map = { + 0: 'Unknown', + 1: 'OK', + 2: 'Degraded', + 3: 'Error' + } + +def get_path(): + """@return instance name with prefilled properties""" + op = pywbem.CIMInstanceName( + classname="LMI_SoftwareInstallationService", + namespace="root/cimv2") + op['CreationClassName'] = op.classname + systemop = ComputerSystem.get_path() + op["SystemCreationClassName"] = systemop.classname + op['SystemName'] = systemop["Name"] + op["Name"] = "LMI:LMI_SoftwareInstallationService" + return op + +@cmpi_logging.trace_function +def check_path(env, service, prop_name): + """ + Checks instance name of SoftwareInstallationService. + @param prop_name name of object name; used for error descriptions + """ + if not isinstance(service, pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "\"%s\" must be a CIMInstanceName" % prop_name) + our_service = get_path() + ch = env.get_cimom_handle() + if service.namespace != our_service.namespace: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'Namespace of "%s" does not match "%s"' % ( + prop_name, our_service.namespace)) + if not ch.is_subclass(our_service.namespace, + sub=service.classname, + super=our_service.classname): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Class of \"%s\" must be a subclass of %s" % ( + prop_name, our_service.classname)) + for key in our_service.keybindings.keys(): + if not key in service: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "\"%s\" is missing %s key property" % ( prop_name, key)) + if service[key] != our_service[key]: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "\"%s\" key property %s(%s) does not match \"%s\"" %( + prop_name, key, service[key], our_service[key])) + return True + +@cmpi_logging.trace_function +def check_path_property(env, op, prop_name): + """ + Checks, whether prop_name property of op object path is correct. + If not, an exception will be raised. + """ + if not prop_name in op: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Missing %s key property!" % prop_name) + return check_path(env, op[prop_name], prop_name) + +@cmpi_logging.trace_function +def _check_target_and_collection(env, method, target, collection): + """ + Checks Target and Collection parameters of provider's installation + methods. + """ + values = Values.InstallFromSoftwareIdentity + if target: + try: + ComputerSystem.check_path(env, target, "Target") + except pywbem.CIMError as exc: + raise InstallationError(values.Unspecified_Error, + "Target must be either NULL or match managed" + " computer system: %s", str(exc)) + if collection: + try: + SystemCollection.check_path(env, collection, "Collection") + except pywbem.CIMError as exc: + raise InstallationError(values.Unspecified_Error, + "Collection does not match system software collection: %s" % + str(exc)) + if target and collection: + raise InstallationError(values.Unspecified_Error, + "Only one of Target and Collection parameters can be specified" + " at the same time.") + if not target and not collection: + raise InstallationError(values.Unspecified_Error, + "Either Target or Collection parameter must be specified." + if method == Job.JOB_METHOD_INSTALL_FROM_SOFTWARE_IDENTITY + else "Missing Target parameter.") + +@cmpi_logging.trace_function +def _install_or_remove_check_params( + env, method, source, target, collection, + install_options, + install_options_values): + """ + Checks parameters of provider's installation methods. + Upon any invalid option an InstallationError will be raised. + @return tuple (action, force, repair) + where action is one of Values.InstallFromSoftwareIdentity properties + """ + if not method in ( + Job.JOB_METHOD_INSTALL_FROM_URI, + Job.JOB_METHOD_INSTALL_FROM_SOFTWARE_IDENTITY, + Job.JOB_METHOD_INSTALL_FROM_BYTE_STREAM): + raise ValueError("unknown method") + + values = Values.InstallFromSoftwareIdentity + supported_options = values.InstallOptions.supported.copy() + if method == Job.JOB_METHOD_INSTALL_FROM_URI: + supported_options.remove(values.InstallOptions.Uninstall) + + if not source: + raise InstallationError(values.Unspecified_Error, + "Missing %s parameter." % (JOB_METHOD_SRC_PARAM_NAMES[method])) + if not install_options: + install_options = [] + elif not isinstance(install_options, list): + raise InstallationError(values.Unspecified_Error, + "InstallOptions must be a list of uint16 values.") + options = {p for p in install_options} + + if options - supported_options: + raise InstallationError(values.Unspecified_Error, + "unsupported install options: {%s}" % + ", ".join([ values.InstallOptions._reverse_map[p] + for p in options - supported_options])) + if install_options_values and len(options) != len(install_options_values): + raise InstallationError(values.Unspecified_Error, + "InstallOptions array must have the same" + " length as InstallOptionsValues: %d != %d" % ( + len(options), len(install_options_values))) + if install_options_values: + for opt, val in zip(install_options, install_options_values): + if val: + raise InstallationError(values.Unspecified_Error, + "install option \"%s\" can not have any" + " associated value: %s" % (opt, val)) + _check_target_and_collection(env, method, target, collection) + exclusive = [opt for opt in options if opt in { + values.InstallOptions.Install, + values.InstallOptions.Update, + values.InstallOptions.Uninstall }] + if len(exclusive) > 1: + raise InstallationError(values.Unspecified_Error, + "specified more than one mutually exclusive option at once: {%s}" % + ", ".join([ values.InstallOptions._reverse_map[p] + for p in exclusive])) + if not exclusive: + exclusive.append(values.InstallOptions.Install) + return ( exclusive[0] + , values.InstallOptions.Force_installation in options + , values.InstallOptions.Repair in options) + +@cmpi_logging.trace_function +def make_job_input_params(method, source, target, collection, + install_options=None, + install_options_values=None): + """ + Make dictionary of input parameters, that are stored in job's metadata. + This dictionary is used in creation of CIM_ConcreteJob and + CIM_InstMethodCall. + """ + input_params = { + "Target" : pywbem.CIMProperty( + name="Target", type="reference", value=target), + } + + if method == Job.JOB_METHOD_VERIFY_INSTALLED_IDENTITY: + input_params["Source"] = pywbem.CIMProperty( + name="Source", type="reference", value=source) + return input_params + + input_params["InstallOptionsValues"] = pywbem.CIMProperty( + name="InstallOptionsValues", + type="string", + is_array=True, + value=install_options_values) + input_params["InstallOptions"] = pywbem.CIMProperty( + name="InstallOptions", type="uint16", + is_array=True, + value=install_options) + if method == Job.JOB_METHOD_INSTALL_FROM_URI: + input_params["URI"] = pywbem.CIMProperty( + name="URI", type="string", value=source) + elif method == Job.JOB_METHOD_INSTALL_FROM_SOFTWARE_IDENTITY: + input_params["Source"] = pywbem.CIMProperty( + name="Source", type="reference", value=source) + input_params["Collection"] = pywbem.CIMProperty( + name="Collection", type="reference", value=collection) + elif method == Job.JOB_METHOD_INSTALL_FROM_BYTE_STREAM: + input_params["Image"] = pywbem.CIMProperty( + name="Image", type="reference", value=source) + + return input_params + +@cmpi_logging.trace_function +def install_or_remove_package(env, method, + source, target, collection, + install_options, + install_options_values): + """ + :param method: (``int``) Identifier of method defined in ``core.Job`` + module with variables prefixed with ``JOB_METHOD_``. + """ + values = Values.InstallFromSoftwareIdentity + (action, force, repair) = _install_or_remove_check_params( + env, method, source, target, collection, + install_options, install_options_values) + input_params = make_job_input_params(method, + source, target, collection, install_options, + install_options_values) + metadata = { "method" : method, "input_params" : input_params } + try: + ydb = YumDB.get_instance() + if action == values.InstallOptions.Uninstall: + src = Identity.object_path2nevra(source, with_epoch='ALWAYS') + cmpi_logging.logger.info('removing package %s', src) + jobid = ydb.remove_package(src, async=True, **metadata) + else: + update = action == values.InstallOptions.Update + if method == Job.JOB_METHOD_INSTALL_FROM_URI: + cmpi_logging.logger.info('%s package "%s"', + 'updating' if update else 'installing', source) + src = source + jobid = ydb.install_package_from_uri( + source, update_only=update, force=force or repair, + async=True, **metadata) + else: # software identity + src = Identity.object_path2nevra(source, with_epoch='ALWAYS') + if update: + jobid = ydb.update_package(src, + force=force or repair, async=True, **metadata) + else: + jobid = ydb.install_package(src, + force=force or repair, async=True, **metadata) + cmpi_logging.logger.info('installation job %s for pkg "%s"' + ' enqueued', jobid, src) + return jobid + + except (pywbem.CIMError, errors.InvalidURI) as exc: + cmpi_logging.logger.exception('failed to install/remove package "%s"' + ' from %s: %s', source, + JOB_METHOD_SRC_PARAM_NAMES[method].lower(), str(exc)) + raise InstallationError(values.Unspecified_Error, str(exc)) + +@cmpi_logging.trace_function +def verify_package(env, method, source, target): + """ + :param method: (``int``) Identifier of method defined in ``core.Job`` + module with variables prefixed with ``JOB_METHOD_``. + """ + values = Values.VerifyInstalledIdentity + + if method != Job.JOB_METHOD_VERIFY_INSTALLED_IDENTITY: + raise ValueError("unknown method") + + if not source: + raise InstallationError(values.Unspecified_Error, + "Missing %s parameter." % (JOB_METHOD_SRC_PARAM_NAMES[method])) + + _check_target_and_collection(env, method, target, None) + + input_params = make_job_input_params(method, source, target, None) + metadata = { "method" : method, "input_params" : input_params } + nevra = Identity.object_path2nevra(source, with_epoch='ALWAYS') + ydb = YumDB.get_instance() + cmpi_logging.logger.info('verifying package "%s"', nevra) + jobid = ydb.check_package(nevra, async=True, **metadata) + cmpi_logging.logger.info('verification job %s for pkg "%s" enqueued', + jobid, nevra) + return jobid + diff --git a/src/software/lmi/software/core/InstallationServiceAffectsElement.py b/src/software/lmi/software/core/InstallationServiceAffectsElement.py new file mode 100644 index 0000000..c305468 --- /dev/null +++ b/src/software/lmi/software/core/InstallationServiceAffectsElement.py @@ -0,0 +1,87 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to class +LMI_SoftwareInstallationServiceAffectsElement. +""" + +import pywbem + +from lmi.common import cmpi_logging +from lmi.software.core import ComputerSystem +from lmi.software.core import Identity + +class Values(object): + class ElementEffects(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Exclusive_Use = pywbem.Uint16(2) + Performance_Impact = pywbem.Uint16(3) + Element_Integrity = pywbem.Uint16(4) + Manages = pywbem.Uint16(5) + Consumes = pywbem.Uint16(6) + Enhances_Integrity = pywbem.Uint16(7) + Degrades_Integrity = pywbem.Uint16(8) + Enhances_Performance = pywbem.Uint16(9) + Degrades_Performance = pywbem.Uint16(10) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000..0xFFFF + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'Exclusive Use', + 3 : 'Performance Impact', + 4 : 'Element Integrity', + 5 : 'Manages', + 6 : 'Consumes', + 7 : 'Enhances Integrity', + 8 : 'Degrades Integrity', + 9 : 'Enhances Performance', + 10 : 'Degrades Performance' + } + +@cmpi_logging.trace_function +def fill_model_computer_system(model, keys_only=True): + """ + Fills model's AffectedElement and all non-key properties. + """ + model["AffectedElement"] = ComputerSystem.get_path() + if not keys_only: + model["ElementEffects"] = [ + Values.ElementEffects.Enhances_Integrity, + Values.ElementEffects.Degrades_Integrity, + Values.ElementEffects.Other, + Values.ElementEffects.Other] + model["OtherElementEffectsDescriptions"] = [ + "Enhances Integrity", + "Degrades Integrity", + "Enhances Functionality", + "Degrades Functionality"] + return model + +@cmpi_logging.trace_function +def fill_model_identity(model, pkg, keys_only=True, identity_model=None): + """ + Fills model's AffectedElement and all non-key properties. + """ + model["AffectedElement"] = Identity.pkg2model(pkg, model=identity_model) + if not keys_only: + model["ElementEffects"] = [Values.ElementEffects.Other] + model["OtherElementEffectsDescriptions"] = ["Allows to install"] + return model diff --git a/src/software/lmi/software/core/InstallationServiceCapabilities.py b/src/software/lmi/software/core/InstallationServiceCapabilities.py new file mode 100644 index 0000000..62209bf --- /dev/null +++ b/src/software/lmi/software/core/InstallationServiceCapabilities.py @@ -0,0 +1,226 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to +LMI_SoftwareInstallationServiceCapabilities provider. +""" + +import pywbem + +from lmi.common import cmpi_logging + +class Values(object): + class SupportedExtendedResourceTypes(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Not_Applicable = pywbem.Uint16(2) + Linux_RPM = pywbem.Uint16(3) + HP_UX_Depot = pywbem.Uint16(4) + Windows_MSI = pywbem.Uint16(5) + Solaris_Package = pywbem.Uint16(6) + Macintosh_Disk_Image = pywbem.Uint16(7) + Debian_linux_Package = pywbem.Uint16(8) + VMware_vSphere_Installation_Bundle = pywbem.Uint16(9) + VMware_Software_Bulletin = pywbem.Uint16(10) + HP_Smart_Component = pywbem.Uint16(11) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + _reverse_map = { + 0 : 'Unknown', + 1 : 'Other', + 2 : 'Not Applicable', + 3 : 'Linux RPM', + 4 : 'HP-UX Depot', + 5 : 'Windows MSI', + 6 : 'Solaris Package', + 7 : 'Macintosh Disk Image', + 8 : 'Debian linux Package', + 9 : 'VMware vSphere Installation Bundle', + 10 : 'VMware Software Bulletin', + 11 : 'HP Smart Component' + } + + class SupportedInstallOptions(object): + Defer_target_system_reset = pywbem.Uint16(2) + Force_installation = pywbem.Uint16(3) + Install = pywbem.Uint16(4) + Update = pywbem.Uint16(5) + Repair = pywbem.Uint16(6) + Reboot = pywbem.Uint16(7) + Password = pywbem.Uint16(8) + Uninstall = pywbem.Uint16(9) + Log = pywbem.Uint16(10) + SilentMode = pywbem.Uint16(11) + AdministrativeMode = pywbem.Uint16(12) + ScheduleInstallAt = pywbem.Uint16(13) + # DMTF_Reserved = .. + # Vendor_Specific = 32768..65535 + _reverse_map = { + 2 : 'Defer target/system reset', + 3 : 'Force installation', + 4 : 'Install', + 5 : 'Update', + 6 : 'Repair', + 7 : 'Reboot', + 8 : 'Password', + 9 : 'Uninstall', + 10 : 'Log', + 11 : 'SilentMode', + 12 : 'AdministrativeMode', + 13 : 'ScheduleInstallAt' + } + + class SupportedURISchemes(object): + data = pywbem.Uint16(2) + file = pywbem.Uint16(3) + ftp = pywbem.Uint16(4) + http = pywbem.Uint16(5) + https = pywbem.Uint16(6) + nfs = pywbem.Uint16(7) + tftp = pywbem.Uint16(8) + # DMTF_Reserved = .. + # Vendor_Specific = 0x8000..0xFFFF + _reverse_map = { + 2 : 'data', + 3 : 'file', + 4 : 'ftp', + 5 : 'http', + 6 : 'https', + 7 : 'nfs', + 8 : 'tftp' + } + + class SupportedAsynchronousActions(object): + None_supported = pywbem.Uint16(2) + Install_From_Software_Identity = pywbem.Uint16(3) + Install_from_ByteStream = pywbem.Uint16(4) + Install_from_URI = pywbem.Uint16(5) + _reverse_map = { + 2 : 'None supported', + 3 : 'Install From Software Identity', + 4 : 'Install from ByteStream', + 5 : 'Install from URI' + } + + class CreateGoalSettings(object): + Success = pywbem.Uint16(0) + Not_Supported = pywbem.Uint16(1) + Unknown = pywbem.Uint16(2) + Timeout = pywbem.Uint16(3) + Failed = pywbem.Uint16(4) + Invalid_Parameter = pywbem.Uint16(5) + Alternative_Proposed = pywbem.Uint16(6) + # DMTF_Reserved = .. + # Vendor_Specific = 32768..65535 + + class SupportedSynchronousActions(object): + None_supported = pywbem.Uint16(2) + Install_From_Software_Identity = pywbem.Uint16(3) + Install_from_ByteStream = pywbem.Uint16(4) + Install_from_URI = pywbem.Uint16(5) + _reverse_map = { + 2 : 'None supported', + 3 : 'Install From Software Identity', + 4 : 'Install from ByteStream', + 5 : 'Install from URI' + } + +@cmpi_logging.trace_function +def get_path(): + """@return instance name with prefilled properties""" + op = pywbem.CIMInstanceName( + classname="LMI_SoftwareInstallationServiceCapabilities", + namespace="root/cimv2") + op['InstanceID'] = "LMI:LMI_SoftwareInstallationServiceCapabilities" + return op + +@cmpi_logging.trace_function +def check_path(env, caps, prop_name): + """ + Checks instance name of SoftwareInstallationServiceCapabilities. + @param prop_name name of object name; used for error descriptions + """ + if not isinstance(caps, pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "\"%s\" must be a CIMInstanceName" % prop_name) + our_caps = get_path() + ch = env.get_cimom_handle() + if caps.namespace != our_caps.namespace: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'Namespace of "%s" does not match "%s"' % ( + prop_name, our_caps.namespace)) + if not ch.is_subclass(our_caps.namespace, + sub=caps.classname, + super=our_caps.classname): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Class of \"%s\" must be a sublass of %s" % ( + prop_name, our_caps.classname)) + if caps['InstanceID'] != our_caps['InstanceID']: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "InstanceID of \"%s\" does not match \"%s\"" % + prop_name, our_caps['InstanceID']) + return True + +@cmpi_logging.trace_function +def check_path_property(env, op, prop_name): + """ + Checks, whether prop_name property of op object path is correct. + If not, an exception will be raised. + """ + if not prop_name in op: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Missing %s key property!" % prop_name) + return check_path(env, op[prop_name], prop_name) + +def get_instance(model=None): + """ + Makes instance of LMI_SoftwareInstallationServiceCapabilities or + fills given model with properties. + """ + path = get_path() + if model is None: + model = pywbem.CIMInstance( + "LMI_SoftwareInstallationServiceCapabilities", path=path) + model['InstanceID'] = path['InstanceID'] + + model['CanAddToCollection'] = True + model['Caption'] = 'Capabilities of LMI:LMI_SoftwareInstallationService' + model['Description'] = ('This instance provides information' + ' about LMI:LMI_SoftwareInstallationService\'s capabilities.') + model['SupportedAsynchronousActions'] = [ + Values.SupportedAsynchronousActions.Install_From_Software_Identity, + Values.SupportedAsynchronousActions.Install_from_URI] + model['SupportedExtendedResourceTypes'] = [ + Values.SupportedExtendedResourceTypes.Linux_RPM] + model['SupportedInstallOptions'] = [ + Values.SupportedInstallOptions.Install, + Values.SupportedInstallOptions.Update, + Values.SupportedInstallOptions.Uninstall, + Values.SupportedInstallOptions.Force_installation, + Values.SupportedInstallOptions.Repair] + model['SupportedSynchronousActions'] = [ + Values.SupportedSynchronousActions.None_supported] + model['SupportedTargetTypes'] = ['rpm', 'yum'] + model['SupportedURISchemes'] = [ + Values.SupportedURISchemes.file, + Values.SupportedURISchemes.ftp, + Values.SupportedURISchemes.http, + Values.SupportedURISchemes.https] + return model + diff --git a/src/software/lmi/software/core/Job.py b/src/software/lmi/software/core/Job.py new file mode 100644 index 0000000..4a7753a --- /dev/null +++ b/src/software/lmi/software/core/Job.py @@ -0,0 +1,754 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to SoftwareInstallationJob +and SoftwareVerificationJob. +""" + +from datetime import datetime, timedelta +import pywbem +import time + +from lmi.common import cmpi_logging +from lmi.software import util +from lmi.software.core import Error +from lmi.software.yumdb import errors, jobs +from lmi.software.yumdb import YumDB + +JOB_CLASS_NAMES = ( + "LMI_SoftwareInstallationJob", + "LMI_SoftwareVerificationJob" +) + +JOB_DESCRIPTIONS = { + jobs.YumInstallPackage : + 'Software package installation job %(jobid)d for "%(pkg)s".', + jobs.YumRemovePackage : + 'Software package removal job %(jobid)d for "%(pkg)s".', + jobs.YumUpdateToPackage : + 'Software package update job %(jobid)d for "%(pkg)s".', + jobs.YumUpdatePackage : + 'Software package update job %(jobid)d for "%(pkg)s".', + jobs.YumInstallPackageFromURI : + 'Software package installation job %(jobid)d from uri: "%(uri)s".', + jobs.YumCheckPackage : + 'Software package check job %(jobid)d for "%(pkg)s".', + jobs.YumCheckPackageFile : + 'File verification job %(jobid)d for package "%(pkg)s".', +} + +# identificators of InstallationService method, which may trigger +# creation of asynchronous job +( JOB_METHOD_INSTALL_FROM_SOFTWARE_IDENTITY +, JOB_METHOD_INSTALL_FROM_URI +, JOB_METHOD_INSTALL_FROM_BYTE_STREAM +, JOB_METHOD_VERIFY_INSTALLED_IDENTITY) = range(4) + +# above identificators point to this array to their description +JOB_METHOD_NAMES = ( + "InstallFromSoftwareIdentity", + "InstallFromURI", + "InstallFromByteStream", + "VerifyInstalledIdentity") + +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.. + _reverse_map = { + 0: 'Not Available', + 1: 'No Additional Information', + 2: 'Stressed', + 3: 'Predictive Failure', + 4: 'Non-Recoverable Error', + 5: 'Supporting Entity in Error' + } + + 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 + _reverse_map = { + 0: 'Unknown', + 5: 'OK', + 10: 'Degraded/Warning', + 15: 'Minor failure', + 20: 'Major failure', + 25: 'Critical failure', + 30: 'Non-recoverable error' + } + + class JobState(object): + New = pywbem.Uint16(2) + Starting = pywbem.Uint16(3) + Running = pywbem.Uint16(4) + Suspended = pywbem.Uint16(5) + Shutting_Down = pywbem.Uint16(6) + Completed = pywbem.Uint16(7) + Terminated = pywbem.Uint16(8) + Killed = pywbem.Uint16(9) + Exception = pywbem.Uint16(10) + Service = pywbem.Uint16(11) + Query_Pending = pywbem.Uint16(12) + # DMTF_Reserved = 13..32767 + # Vendor_Reserved = 32768..65535 + _reverse_map = { + 2: 'New', + 3: 'Starting', + 4: 'Running', + 5: 'Suspended', + 6: 'Shutting Down', + 7: 'Completed', + 8: 'Terminated', + 9: 'Killed', + 10: 'Exception', + 11: 'Service', + 12: 'Query Pending' + } + + class GetError(object): + Success = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unspecified_Error = pywbem.Uint32(2) + Timeout = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + Access_Denied = pywbem.Uint32(6) + # DMTF_Reserved = .. + # Vendor_Specific = 32768..65535 + + class KillJob(object): + Success = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unknown = pywbem.Uint32(2) + Timeout = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Access_Denied = pywbem.Uint32(6) + Not_Found = pywbem.Uint32(7) + # DMTF_Reserved = .. + # Vendor_Specific = 32768..65535 + + class RecoveryAction(object): + Unknown = pywbem.Uint16(0) + Other = pywbem.Uint16(1) + Do_Not_Continue = pywbem.Uint16(2) + Continue_With_Next_Job = pywbem.Uint16(3) + Re_run_Job = pywbem.Uint16(4) + Run_Recovery_Job = pywbem.Uint16(5) + _reverse_map = { + 0: 'Unknown', + 1: 'Other', + 2: 'Do Not Continue', + 3: 'Continue With Next Job', + 4: 'Re-run Job', + 5: 'Run Recovery Job' + } + + class RunDayOfWeek(object): + _Saturday = pywbem.Sint8(-7) + _Friday = pywbem.Sint8(-6) + _Thursday = pywbem.Sint8(-5) + _Wednesday = pywbem.Sint8(-4) + _Tuesday = pywbem.Sint8(-3) + _Monday = pywbem.Sint8(-2) + _Sunday = pywbem.Sint8(-1) + ExactDayOfMonth = pywbem.Sint8(0) + Sunday = pywbem.Sint8(1) + Monday = pywbem.Sint8(2) + Tuesday = pywbem.Sint8(3) + Wednesday = pywbem.Sint8(4) + Thursday = pywbem.Sint8(5) + Friday = pywbem.Sint8(6) + Saturday = pywbem.Sint8(7) + _reverse_map = { + 0: 'ExactDayOfMonth', + 1: 'Sunday', + 2: 'Monday', + 3: 'Tuesday', + 4: 'Wednesday', + 5: 'Thursday', + 6: 'Friday', + 7: 'Saturday', + -1: '-Sunday', + -7: '-Saturday', + -6: '-Friday', + -5: '-Thursday', + -4: '-Wednesday', + -3: '-Tuesday', + -2: '-Monday' + } + + class RunMonth(object): + January = pywbem.Uint8(0) + February = pywbem.Uint8(1) + March = pywbem.Uint8(2) + April = pywbem.Uint8(3) + May = pywbem.Uint8(4) + June = pywbem.Uint8(5) + July = pywbem.Uint8(6) + August = pywbem.Uint8(7) + September = pywbem.Uint8(8) + October = pywbem.Uint8(9) + November = pywbem.Uint8(10) + December = pywbem.Uint8(11) + _reverse_map = { + 0: 'January', + 1: 'February', + 2: 'March', + 3: 'April', + 4: 'May', + 5: 'June', + 6: 'July', + 7: 'August', + 8: 'September', + 9: 'October', + 10: 'November', + 11: 'December' + } + + class GetErrors(object): + Success = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unspecified_Error = pywbem.Uint32(2) + Timeout = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + Access_Denied = pywbem.Uint32(6) + # DMTF_Reserved = .. + # Vendor_Specific = 32768..65535 + + 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.. + _reverse_map = { + 0: 'Unknown', + 1: 'Not Available', + 2: 'Communication OK', + 3: 'Lost Communication', + 4: 'No Contact' + } + + 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.. + _reverse_map = { + 0: 'Unknown', + 1: 'Other', + 2: 'OK', + 3: 'Degraded', + 4: 'Stressed', + 5: 'Predictive Failure', + 6: 'Error', + 7: 'Non-Recoverable Error', + 8: 'Starting', + 9: 'Stopping', + 10: 'Stopped', + 11: 'In Service', + 12: 'No Contact', + 13: 'Lost Communication', + 14: 'Aborted', + 15: 'Dormant', + 16: 'Supporting Entity in Error', + 17: 'Completed', + 18: 'Power Mode', + 19: 'Relocating' + } + + 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.. + _reverse_map = { + 0: 'Unknown', + 1: 'Not Available', + 2: 'Servicing', + 3: 'Starting', + 4: 'Stopping', + 5: 'Stopped', + 6: 'Aborted', + 7: 'Dormant', + 8: 'Completed', + 9: 'Migrating', + 10: 'Emigrating', + 11: 'Immigrating', + 12: 'Snapshotting', + 13: 'Shutting Down', + 14: 'In Test', + 15: 'Transitioning', + 16: 'In Service' + } + + class LocalOrUtcTime(object): + Local_Time = pywbem.Uint16(1) + UTC_Time = pywbem.Uint16(2) + _reverse_map = { + 1: 'Local Time', + 2: 'UTC Time' + } + + class RequestStateChange(object): + Completed_with_No_Error = pywbem.Uint32(0) + Not_Supported = pywbem.Uint32(1) + Unknown_Unspecified_Error = pywbem.Uint32(2) + Can_NOT_complete_within_Timeout_Period = pywbem.Uint32(3) + Failed = pywbem.Uint32(4) + Invalid_Parameter = pywbem.Uint32(5) + In_Use = pywbem.Uint32(6) + # DMTF_Reserved = .. + Method_Parameters_Checked___Transition_Started = pywbem.Uint32(4096) + Invalid_State_Transition = pywbem.Uint32(4097) + Use_of_Timeout_Parameter_Not_Supported = pywbem.Uint32(4098) + Busy = pywbem.Uint32(4099) + # Method_Reserved = 4100..32767 + # Vendor_Specific = 32768..65535 + class RequestedState(object): + Start = pywbem.Uint16(2) + Suspend = pywbem.Uint16(3) + Terminate = pywbem.Uint16(4) + Kill = pywbem.Uint16(5) + Service = pywbem.Uint16(6) + # DMTF_Reserved = 7..32767 + # Vendor_Reserved = 32768..65535 + + class PrimaryStatus(object): + Unknown = pywbem.Uint16(0) + OK = pywbem.Uint16(1) + Degraded = pywbem.Uint16(2) + Error = pywbem.Uint16(3) + # DMTF_Reserved = .. + # Vendor_Reserved = 0x8000.. + _reverse_map = { + 0: 'Unknown', + 1: 'OK', + 2: 'Degraded', + 3: 'Error' + } + +@cmpi_logging.trace_function +def get_verification_out_params(job): + """ + Get the output parameters for verification job. They may not be computed + yet. In that case compute them a update the job in YumWorker process. + + :param job: (``jobs.YumCheckPackage``) + :rtype: (``dict``) Dictionary of output parameters with pywbem values. + """ + if not isinstance(job, jobs.YumCheckPackage): + raise TypeError("job must be a YumCheckPackage instance") + if ( not job.metadata.get("output_params", []) + and job.state == job.COMPLETED): + from lmi.software.core import IdentityFileCheck + failed = [] + pkg_info, pkg_check = job.result_data + for file_name in pkg_check: + pkg_file = pkg_check[file_name] + file_check = IdentityFileCheck.test_file(pkg_info, + pkg_check.file_checksum_type, pkg_file) + if not IdentityFileCheck.file_check_passed(file_check): + failed.append(IdentityFileCheck.file_check2model( + file_check, job=job)) + metadata = { + 'output_params' : { + 'Failed' : pywbem.CIMProperty("Failed", + type="reference", is_array=True, value=failed) + } + } + # update local instance + job.update(metadata=metadata) + if YumDB.RUNNING_UNDER_CIMOM_PROCESS: + # update instance on server + YumDB.get_instance().update_job(job.jobid, metadata=metadata) + # else: we are called from YumWorker process; + # (update on server already occured) + # moreover YumWorker blocks on us - we can not wait for another job + # to finish + return job.metadata.get('output_params', []) + +@cmpi_logging.trace_function +def make_method_params(job, class_name, include_input, include_output): + """ + Create a class of given name with all input or output parameters + of the asynchronous method. Typically used to assemble + CIM_ConcreteJob.JobInParameters or CIM_InstMethodCall.MethodParameters + values. + + :param job: (``YumJob``) Instance of job created as a result of method + invocation. It carries method parameters. + :param class_name: (``str``) Name of the class to create. + :param include_input: (``bool``) Whether input parameters should be + included in the returned class. + :param include_output: (``bool``) Whether output parameters should be + included in the returned class. + :rtype: CIMInstance of the created class. + """ + # TODO: this is workaround for bug #920763, use class_name + # when it's fixed + clsname = "CIM_ManagedElement" + path = pywbem.CIMInstanceName(classname=clsname, namespace="root/cimv2") + inst = pywbem.CIMInstance(classname=clsname, path=path) + if include_input and "input_params" in job.metadata: + for (name, value) in job.metadata["input_params"].items(): + inst[name] = value + if include_output: + if isinstance(job, jobs.YumCheckPackage): + # make sure, that output parameters are computed + get_verification_out_params(job) + if "output_params" in job.metadata: + # overwrite any input parameter + for (name, value) in job.metadata["output_params"].iteritems(): + inst[name] = value + return inst + +def job_class2cim_class_name(jobcls): + """ + Here we map classes of job objects to their corresponding CIM class + name. + + :param jobcls: (``type``) Subclass of jobs.YumJob. + """ + if not issubclass(jobcls, ( + jobs.YumInstallPackageFromURI, + jobs.YumSpecificPackageJob)): + raise ValueError("Job class \"%s\" does not have any associated" + " CIM class." % jobcls.__name__) + if issubclass(jobcls, (jobs.YumCheckPackage, jobs.YumCheckPackageFile)): + return "LMI_SoftwareVerificationJob" + return "LMI_SoftwareInstallationJob" + +@cmpi_logging.trace_function +def _fill_nonkeys(job, model): + """ + Fills into the model of instance all non-key properties. + """ + model['Caption'] = 'Software installation job with id=%d' % job.jobid + model['CommunicationStatus'] = Values.CommunicationStatus.Not_Available + model['DeleteOnCompletion'] = job.delete_on_completion + try: + description = JOB_DESCRIPTIONS[job.__class__] + kwargs = job.job_kwargs + kwargs['jobid'] = job.jobid + model['Description'] = description % kwargs + except KeyError: + cmpi_logging.logger.error( + 'no description string found for job class %s' % + job.__class__.__name__) + model['Description'] = pywbem.CIMProperty('Description', + type='string', value=None) + if job.started: + if job.finished: + elapsed = job.finished - job.started + else: + elapsed = time.time() - job.started + model['ElapsedTime'] = pywbem.CIMDateTime(timedelta(seconds=elapsed)) + else: + model["ElapsedTime"] = pywbem.CIMProperty('ElapsedTime', + type='datetime', value=None) + model['ErrorCode'] = pywbem.Uint16(0 if job.state != job.EXCEPTION else 1) + try: + model['JobState'], model['OperationalStatus'], model['JobStatus'] = { + jobs.YumJob.NEW : (Values.JobState.New, + [Values.OperationalStatus.Dormant], 'Enqueued'), + jobs.YumJob.RUNNING : (Values.JobState.Running, + [Values.OperationalStatus.OK], 'Running'), + jobs.YumJob.TERMINATED : (Values.JobState.Terminated, + [Values.OperationalStatus.Stopped], 'Terminated'), + jobs.YumJob.EXCEPTION : (Values.JobState.Exception + , [Values.OperationalStatus.Error] + , 'Failed'), + jobs.YumJob.COMPLETED : (Values.JobState.Completed + , [ Values.OperationalStatus.OK + , Values.OperationalStatus.Completed] + , 'Finished successfully') + }[job.state] + except KeyError: + cmpi_logging.logger.error('unknown job state: %s' % job.state) + model['JobState'] = pywbem.CIMProperty('JobState', + type='uint16', value=None) + model['OperationalStatus'] = [Values.OperationalStatus.Unknown] + model['JobStatus'] = 'Unknown' + model["JobInParameters"] = make_method_params( + job, "__JobInParameters", True, False) + model["JobOutParameters"] = make_method_params( + job, "__JobOutParameters", False, True) + if 'method' in job.metadata: + model['MethodName'] = JOB_METHOD_NAMES[job.metadata["method"]] + else: + model["MethodName"] = pywbem.CIMProperty('MethodName', + type='string', value=None) + model['Name'] = job.metadata['name'] + model['LocalOrUtcTime'] = Values.LocalOrUtcTime.UTC_Time + model['PercentComplete'] = pywbem.Uint16( + 100 if job.state == job.COMPLETED else ( + 50 if job.state == job.RUNNING else + 0)) + model['Priority'] = pywbem.Uint32(job.priority) + if job.started: + model['StartTime'] = pywbem.CIMDateTime(datetime.fromtimestamp( + job.started)) + model['TimeBeforeRemoval'] = pywbem.CIMDateTime(timedelta( + seconds=job.time_before_removal)) + model['TimeOfLastStateChange'] = pywbem.CIMDateTime(datetime.fromtimestamp( + job.last_change)) + model['TimeSubmitted'] = pywbem.CIMDateTime(datetime.fromtimestamp( + job.created)) + +@cmpi_logging.trace_function +def job2model(job, class_name=None, keys_only=True, model=None): + """ + Makes LMI_SoftwareJob out of job object or job id. + + :param job: (``int`` | ``YumAsyncJob``) Job identifier. + In case of integer, caller should also provide class_name of resulting + CIM instance. Otherwise generic LMI_SoftwareJob will be returned. + :param class_name: (``str``) Determines CIM class name of resulting + instance. This should be given when ``job`` is an integer. + :param model: (``CIMInstance`` | ``CIMInstanceName``) If not None, + will be filled with properties, otherwise + a new instance of CIMInstance or CIMObjectPath is created. + :param keys_only: (``bool``) Says whether to fill only key properties. + Also if ``model`` is not given, it determines, whether to make + ``CIMInstanceName`` or ``CIMInstance``. + :rtype: (``CIMInstance`` | ``CIMInstanceName``) + """ + if not isinstance(job, (int, long, jobs.YumAsyncJob)): + raise TypeError("job must be an instance of YumAsyncJob") + if isinstance(job, jobs.YumAsyncJob) and not job.async: + raise ValueError("job must be asynchronous") + if not keys_only and isinstance(job, (int, long)): + raise TypeError("job must be an instance of YumAsyncJob" + " filling non-key properties") + + if class_name is None: + if model is not None: + class_name = model.classname + elif isinstance(job, jobs.YumJob): + class_name = job_class2cim_class_name(job.__class__) + else: + class_name = "LMI_SoftwareJob" + cmpi_logging.logger.warn( + "class_name not supplied for jobid=%d, using general" + " LMI_SoftwareJob as CIM class name") + if model is None: + model = pywbem.CIMInstanceName(class_name, namespace="root/cimv2") + if not keys_only: + model = pywbem.CIMInstance(class_name, path=model) + + jobid = job.jobid if isinstance(job, jobs.YumAsyncJob) else job + model['InstanceID'] = 'LMI:%s:%d' % (class_name, jobid) + if isinstance(model, pywbem.CIMInstance): + model.path['InstanceID'] = model['InstanceID'] #pylint: disable=E1103 + if not keys_only: + _fill_nonkeys(job, model) + return model + +@cmpi_logging.trace_function +def object_path2job(op): + """ + @param op must contain precise InstanceID of job + """ + if not isinstance(op, pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "op must be an instance of CIMInstanceName") + + if (not "InstanceID" in op or not op['InstanceID']): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Missing InstanceID key property.") + instid = op['InstanceID'] + match = util.RE_INSTANCE_ID.match(instid) + if not match or match.group('clsname').lower() not in { + c.lower() for c in JOB_CLASS_NAMES}: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "InstanceID must start with one of {%s} prefixes." + " And end with positive integer." % ( + ", ".join(("LMI:%s:" % cn) for cn in JOB_CLASS_NAMES))) + + instid = int(match.group('id')) + try: + job = YumDB.get_instance().get_job(instid) + clsname = job_class2cim_class_name(job.__class__) + if ( clsname.lower() != op.classname.lower() + and op.classname.lower() != 'LMI_SoftwareJob'.lower()): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Classname \"%s\" does not belong to job with given id." + " \"%s\" is the correct one." % (op.classname, clsname)) + return job + except errors.JobNotFound: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'No such job "%s".' % op['InstanceID']) + +@cmpi_logging.trace_function +def modify_instance(instance): + """ + This call modifies the job's parameters according to given instance. + """ + job = object_path2job(instance.path) + ydb = YumDB.get_instance() + update_kwargs = {} + reschedule_kwargs = {} + # all modifiable properties + prop_name_map = { + "name" : "name", + "priority" : "priority", + "deleteoncompletion" : "delete_on_completion", + "timebeforeremoval" : "time_before_removal" + } + metadata_props = {"name"} + reschedule_props = {"delete_on_completion", "time_before_removal"} + for name, prop in instance.properties.items(): + if prop is None: + cmpi_logging.logger.warn('property "%s" is None') + continue + name = name.lower() + try: + pname = prop_name_map[name] + if pname == "priority" and job.priority != prop.value: + cmpi_logging.logger.info( + 'changing priority of job %s to %d', job, prop.value) + job = ydb.set_job_priority(job.jobid, prop.value) + elif pname in reschedule_props: + if getattr(job, pname) == prop.value: + continue + if pname == "time_before_removal": + value = prop.value.timedelta.total_seconds() + else: + value = prop.value + reschedule_kwargs[pname] = value + else: + if pname in metadata_props: + if not 'metadata' in update_kwargs: + update_kwargs['metadata'] = {} + update_kwargs['metadata'][pname] = prop.value + else: + update_kwargs[pname] = prop.value + except KeyError: + if name == 'instanceid': + continue + cmpi_logging.logger.warn("skipping property %s: %s", name, prop) + + if reschedule_kwargs: + for prop in ('delete_on_completion', 'time_before_removal'): + if prop not in reschedule_kwargs: + reschedule_kwargs[prop] = getattr(job, prop) + cmpi_logging.logger.info('rescheduling job %s to: %s', + job, ", ".join("%s=%s"%(k, v) for k, v in + reschedule_kwargs.items())) + job = ydb.reschedule_job(job.jobid, **reschedule_kwargs) + + if update_kwargs: + cmpi_logging.logger.info('changing atributes of job %s to: %s', + job, ", ".join("%s=%s"%(k, v) for k, v in + update_kwargs.items())) + job = ydb.update_job(job.jobid, **update_kwargs) + + return job2model(job, keys_only=False, model=instance) + +@cmpi_logging.trace_function +def job2error(job): + """ + @return instance of CIM_Error if job is in EXCEPTION state, + None otherwise + """ + if not isinstance(job, jobs.YumJob): + raise TypeError("job must be isntance of YumJob") + if job.state == job.EXCEPTION: + errortup = job.result_data + kwargs = {} + if issubclass(errortup[0], + (errors.RepositoryNotFound, errors.PackageNotFound)): + kwargs['status_code'] = Error.Values. \ + CIMStatusCode.CIM_ERR_NOT_FOUND + if issubclass(errortup[0], errors.PackageNotFound): + kwargs['status_code_description'] = "Package not found" + else: + kwargs['status_code_description'] = "Repository not found" + elif issubclass(errortup[0], errors.PackageAlreadyInstalled): + kwargs['status_code'] = Error.Values. \ + CIMStatusCode.CIM_ERR_ALREADY_EXISTS + kwargs['message'] = getattr(errortup[1], 'message', + str(errortup[1])) + value = Error.make_instance(**kwargs) + return value diff --git a/src/software/lmi/software/core/MethodResult.py b/src/software/lmi/software/core/MethodResult.py new file mode 100644 index 0000000..47eb7ca --- /dev/null +++ b/src/software/lmi/software/core/MethodResult.py @@ -0,0 +1,88 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Just a common functionality related to class LMI_SoftwareMethodResult. +""" + +import pywbem + +from lmi.common import cmpi_logging +from lmi.software.core import Job +from lmi.software.core import InstMethodCall +from lmi.software.yumdb import jobs, errors, YumDB + +@cmpi_logging.trace_function +def object_path2jobid(op): + """ + @param op must contain precise InstanceID of job + """ + if not isinstance(op, pywbem.CIMInstanceName): + raise TypeError("op must be a CIMInstanceName") + if not op["InstanceID"].lower().startswith('lmi:lmi_softwaremethodresult:'): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Missing 'LMI:LMI_SoftwareMethodResult:' prefix in InstanceID.") + instid = op['InstanceID'][len('LMI:LMI_SoftwareMethodResult:'):] + try: + instid = int(instid) + except ValueError: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Failed to parse InstanceID.") + return instid + +@cmpi_logging.trace_function +def object_path2job(op): + """ + @param op must contain precise InstanceID of job + """ + instid = object_path2jobid(op) + try: + return YumDB.get_instance().get_job(instid) + except errors.JobNotFound: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "No such job.") + +@cmpi_logging.trace_function +def job2model(job, keys_only=True, model=None): + """ + This accepts job's id and produces corresponding result model. + """ + if not isinstance(job, jobs.YumJob): + raise TypeError("job must be an instance of YumJob") + if model is None: + model = pywbem.CIMInstanceName("LMI_SoftwareMethodResult", + namespace="root/cimv2") + if not keys_only: + model = pywbem.CIMInstance("LMI_SoftwareMethodResult", path=model) + model['InstanceID'] = "LMI:LMI_SoftwareMethodResult:"+str(job.jobid) + if not keys_only: + model.path['InstanceID'] = model['InstanceID'] #pylint: disable=E1103 + method_name = Job.JOB_METHOD_NAMES[job.metadata['method']] + model['Caption'] = 'Result of method %s' % method_name + model['Description'] = ( + 'Result of asynchronous job number %d created upon invocation' + " of %s's %s method." % (job.jobid, + "LMI_SoftwareInstallationService", method_name)) + model['ElementName'] = 'MethodResult-%d' % job.jobid + model['PostCallIndication'] = pywbem.CIMProperty("PostCallIndication", + type="instance", + value=InstMethodCall.job2model(job, pre=False)) + model['PreCallIndication'] = pywbem.CIMProperty("PreCallIndication", + type="instance", + value=InstMethodCall.job2model(job)) + return model + diff --git a/src/software/lmi/software/core/SystemCollection.py b/src/software/lmi/software/core/SystemCollection.py new file mode 100644 index 0000000..6596d40 --- /dev/null +++ b/src/software/lmi/software/core/SystemCollection.py @@ -0,0 +1,73 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 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 concerning SystemSoftwareCollection provider. +""" + +import pywbem + +from lmi.common import cmpi_logging + +def get_path(): + """@return instance name with prefilled properties""" + op = pywbem.CIMInstanceName( + classname="LMI_SystemSoftwareCollection", + namespace="root/cimv2") + op['InstanceID'] = "LMI:LMI_SystemSoftwareCollection" + return op + +@cmpi_logging.trace_function +def check_path(env, collection, prop_name): + """ + Checks instance name of SystemSoftwareCollection. + @param prop_name name of object name; used for error descriptions + """ + if not isinstance(collection, pywbem.CIMInstanceName): + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "\"%s\" must be a CIMInstanceName" % prop_name) + our_collection = get_path() + ch = env.get_cimom_handle() + if collection.namespace != our_collection.namespace: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + 'Namespace of "%s" does not match "%s"' % ( + prop_name, our_collection.namespace)) + if not ch.is_subclass(our_collection.namespace, + sub=collection.classname, + super=our_collection.classname): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "Class of \"%s\" must be a sublass of %s" % ( + prop_name, our_collection.classname)) + if not 'InstanceID' in collection: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "\"%s\" is missing InstanceID key property", prop_name) + if collection['InstanceID'] != our_collection['InstanceID']: + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, + "InstanceID of \"%s\" does not match \"%s\"" % + prop_name, our_collection['InstanceID']) + return True + +@cmpi_logging.trace_function +def check_path_property(env, op, prop_name): + """ + Checks, whether prop_name property of op object path is correct. + If not, an exception will be risen. + """ + if not prop_name in op: + raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, + "Missing %s key property!" % prop_name) + return check_path(env, op[prop_name], prop_name) diff --git a/src/software/lmi/software/core/__init__.py b/src/software/lmi/software/core/__init__.py new file mode 100644 index 0000000..a7ce6c9 --- /dev/null +++ b/src/software/lmi/software/core/__init__.py @@ -0,0 +1,84 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +""" +Core functionality of particular providers. +Each provider having functionality useful to others has a submodule +in this subpackage with the same name except for LMI_ prefix. +""" + +import pywbem +from lmi.common import cmpi_logging + +@cmpi_logging.trace_function +def generate_references( + env, + object_name, + model, + filter_result_class_name, + filter_role, + filter_result_role, + keys_only, + handlers): + """ + Function for generating references to object_name. It's supposed + to be used directly from references method of provider classes. + @param handlers is a list of tuples: + [ (role, class_name, handler), ... ] + where handler is a function, that will be called for matching + parameters, yielding instance(s/names). It accepts arguments: + env, object_name, model, keys_only. + """ + if not isinstance(object_name, pywbem.CIMInstanceName): + raise TypeError("object_name must be a CIMInstanceName") + if not isinstance(model, pywbem.CIMInstance): + raise TypeError("model must be a CIMInstance") + + ch = env.get_cimom_handle() + model.path.update(dict((t[0], None) for t in handlers)) + + try: + for i, (role, clsname, handler) in enumerate(handlers): + if ( ( filter_role + and filter_role.lower() != role.lower()) + or not ch.is_subclass(object_name.namespace, + sub=object_name.classname, + super=clsname)): + continue + other_results = list((htuple[0], htuple[1]) for htuple in ( + handlers[:i] + handlers[i+1:])) + for res_role, res_clsname in other_results: + if ( ( not filter_result_role + or filter_result_role.lower() == res_role.lower()) + and (not filter_result_class_name or ch.is_subclass( + object_name.namespace, + sub=filter_result_class_name, + super=res_clsname))): + break + else: + continue + # matches filters + for obj in handler(env, object_name, model, keys_only): + yield obj + except pywbem.CIMError as exc: + if exc.args[0] != pywbem.CIM_ERR_NOT_FOUND: + raise + diff --git a/src/software/lmi/software/util/__init__.py b/src/software/lmi/software/util/__init__.py new file mode 100644 index 0000000..d92fd7c --- /dev/null +++ b/src/software/lmi/software/util/__init__.py @@ -0,0 +1,170 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +"""Common utilities for LMI_Software* providers +""" + +import platform +import re +import signal + +RE_INSTANCE_ID = re.compile(r'LMI:(?P<clsname>[a-z][a-z_0-9]+):(?P<id>\d+)', + re.IGNORECASE) + +RE_EVRA = re.compile( + r'^(?P<epoch>\d+):(?P<version>[^-]+)-(?P<release>.+)\.(?P<arch>[^.]+)$') +RE_NEVRA = re.compile( + r'^(?P<name>.+)-(?P<evra>(?P<epoch>\d+):(?P<version>[^-]+)' + r'-(?P<release>.+)\.(?P<arch>[^.]+))$') +RE_NEVRA_OPT_EPOCH = re.compile( + r'^(?P<name>.+)-(?P<evra>((?P<epoch>\d+):)?(?P<version>[^-]+)' + r'-(?P<release>.+)\.(?P<arch>[^.]+))$') +RE_ENVRA = re.compile( + r'^(?P<epoch>\d+):(?P<name>.+)-(?P<evra>(?P<version>[^-]+)' + r'-(?P<release>.+)\.(?P<arch>[^.]+))$') + +def _get_distname(): + """ + @return name of linux distribution + """ + if hasattr(platform, 'linux_distribution'): + return platform.linux_distribution( + full_distribution_name=False)[0].lower() + else: + return platform.dist()[0].lower() + + +def get_target_operating_system(): + """ + @return (val, text). + Where val is a number from ValueMap of TargetOperatingSystem property + of CIM_SoftwareElement class and text is its testual representation. + """ + + system = platform.system() + if system.lower() == 'linux': + try: + val, dist = \ + { 'redhat' : (79, 'RedHat Enterprise Linux') + , 'suse' : (81, 'SUSE') + , 'mandriva' : (88, 'Mandriva') + , 'ubuntu' : (93, 'Ubuntu') + , 'debian' : (95, 'Debian') + }[_get_distname()] + except KeyError: + linrel = platform.uname()[2] + if linrel.startswith('2.4'): + val, dist = (97, 'Linux 2.4.x') + elif linrel.startswith('2.6'): + val, dist = (99, 'Linux 2.6.x') + else: + return (36, 'LINUX') # no check for x86_64 + if platform.machine() == 'x86_64': + val += 1 + dist += ' 64-Bit' + return (val, dist) + elif system.lower() in ('macosx', 'darwin'): + return (2, 'MACOS') + # elif system.lower() == 'windows': # no general value + else: + return (0, 'Unknown') + +def check_target_operating_system(system): + """ + @return if param system matches current target operating system + """ + if isinstance(system, basestring): + system = int(system) + if not isinstance(system, (int, long)): + raise TypeError("system must be either string or integer, not %s" % + system.__class__.__name__) + tos = get_target_operating_system() + if system == tos: + return True + if system == 36: # linux + if platform.system().lower() == "linux": + return True + if ( system >= 97 and system <= 100 # linux 2.x.x + and platform.uname()[2].startswith('2.4' if system < 99 else '2.6') + # check machine + and ( bool(platform.machine().endswith('64')) + == bool(not (system % 2)))): + return True + return False + +def nevra2filter(nevra): + """ + Takes either regexp match object resulting from RE_NEVRA match or + a nevra string. + @return dictionary with package filter key-value pairs made from nevra + """ + if isinstance(nevra, basestring): + match = RE_NEVRA_OPT_EPOCH.match(nevra) + elif nevra.__class__.__name__.lower() == "sre_match": + match = nevra + else: + raise TypeError("nevra must be either string or regexp match object") + epoch = match.group("epoch") + if not epoch or match.group("epoch").lower() == "(none)": + epoch = "0" + return { "name" : match.group("name") + , "epoch" : epoch + , "version" : match.group("version") + , "release" : match.group("release") + , "arch" : match.group("arch") + } + +def make_nevra(name, epoch, version, release, 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, version, release, arch) + +def pkg2nevra(pkg, with_epoch='NOT_ZERO'): + """ + @return nevra string made of pkg + """ + return make_nevra(pkg.name, pkg.epoch, pkg.version, + pkg.release, pkg.arch, with_epoch) + +def get_signal_name(signal_num): + """ + @return name of signal for signal_num argument + """ + if not isinstance(signal_num, (int, long)): + raise TypeError("signal_num must be an integer") + try: + return dict((v, k) for k, v in signal.__dict__.items())[signal_num] + except KeyError: + return "UNKNOWN_SIGNAL(%d)" % signal_num + diff --git a/src/software/lmi/software/yumdb/__init__.py b/src/software/lmi/software/yumdb/__init__.py new file mode 100644 index 0000000..656b090 --- /dev/null +++ b/src/software/lmi/software/yumdb/__init__.py @@ -0,0 +1,677 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# +""" +Since yum API functions should not be called with different thread_ids +repeatedly in the same program. It's neccessary, to make these calls +in single thread. But the provider needs to be able to clean up itself, +when its not needed. That's why the yum API needs to be accessed from +separated process, that is created and terminated when needed. + +This package contains all the bowels of this separate process together +with its management and communication facilities. + +YumDB is a context manager supposed to be used by any provider as the +only accessor to yum api. +""" + +import errno +import inspect +import os +import re +import time +from multiprocessing import Process, Queue #pylint: disable=W0404 +from pywbem.cim_provider2 import CIMProvider2 +import Queue as TQueue # T as threaded +import threading +import yum + +from lmi.common import cmpi_logging, singletonmixin +from lmi.common.IndicationManager import IndicationManager +from lmi.software.yumdb import jobs +from lmi.software.yumdb import errors +from lmi.software.yumdb.packageinfo import PackageInfo +from lmi.software.yumdb.packagecheck import PackageFile +from lmi.software.yumdb.packagecheck import PackageCheck +from lmi.software.yumdb.process import YumWorker +from lmi.software.yumdb.repository import Repository +from lmi.software.yumdb.util import DispatchingFormatter +from lmi.software.util import get_signal_name + +# Maximum time in seconds to wait for a job to accomplish. +# If timeout expires, spawned process is checked (it might +# be possibly killed) and is respawned in case it's dead. +MAX_JOB_WAIT_TIME = 30 + +# this may be used as an argument to YumWorker to setup logging +YUM_WORKER_DEBUG_LOGGING_CONFIG = { + "version" : 1, + 'disable_existing_loggers' : True, + "formatters": { + # this is a message format for logging function/method calls + # it's manually set up in YumWorker's init method + "default": { + "()": DispatchingFormatter, + "formatters" : { + "lmi.software.yumdb.util.trace_function": + "%(asctime)s %(levelname)s:%(message)s" + }, + "default" : "%(asctime)s %(levelname)s:%(module)s:" + "%(funcName)s:%(lineno)d - %(message)s" + }, + }, + "handlers": { + "file" : { + "class" : "logging.handlers.RotatingFileHandler", + "filename" : "/var/tmp/YumWorker.log", + "level" : "DEBUG", + "formatter": "default", + }, + }, + "loggers" : { + "root": { + "level": "DEBUG", + "handlers" : ["file"] + }, + "lmi.software.yumdb": { + "level" : "DEBUG", + "handlers" : ["file"], + "propagate" : False, + }, + } +} + +# ***************************************************************************** +# Utilities +# ***************************************************************************** +def log_reply_error(job, reply): + """ + Raises an exception in case of error occured in worker process + while processing job. + """ + if isinstance(reply, (int, long)): + # asynchronous job + return + if not isinstance(reply, jobs.YumJob): + raise TypeError('expected instance of jobs.YumJob for reply, not "%s"' % + reply.__class__.__name__) + if reply.result == jobs.YumJob.RESULT_ERROR: + cmpi_logging.logger.error( + "YumDB: %s failed with error %s: %s", + job, reply.result_data[0].__name__, str(reply.result_data[1])) + cmpi_logging.logger.trace_warn( + "YumDB: %s exception traceback:\n%s%s: %s", + job, "".join(reply.result_data[2]), + reply.result_data[0].__name__, str(reply.result_data[1])) + reply.result_data[1].tb_printed = True + raise reply.result_data[1] + elif reply.result == jobs.YumJob.RESULT_TERMINATED: + cmpi_logging.logger.warn('YumDB: %s terminated', job) + else: + cmpi_logging.logger.debug('YumDB: %s completed with success', job) + +def _make_async_job(jobcls, *args, **kwargs): + """Creates asynchronous job, filling it wih some metadata.""" + if not issubclass(jobcls, jobs.YumAsyncJob): + raise TypeError("jobcls must be a subclass of YumAsyncJob") + job = jobcls(*args, **kwargs) + if job.metadata is None: + job.metadata = {} + job.metadata['name'] = \ + type(job).__name__[len('Yum'):] + ('-%d' % job.jobid) + return job + +# ***************************************************************************** +# Decorators +# ***************************************************************************** +def job_request(async=False): + """ + Decorator factory for job entry points. They are YumDB methods. + All of them must return either job objects or jobid for asynchronous calls. + Job objects are processed by this decorator for caller to obtain only the + information he needs. + + It wrapps them with logger wrapper and in case of asynchronous jobs, + it returns just the jobid. + """ + def _decorator(method): + """ + Decorator that just logs the method's call and returns job's result. + """ + logged = cmpi_logging.trace_method(method) + def _new_func(self, *args, **kwargs): + """Wrapper for YumDB's method.""" + return logged(self, *args, **kwargs).result_data + return _new_func + + def _decorator_async(method): + """ + Decorator for methods accepting async argument. In case of async=True, + the method returns jobid. Job's result is returned otherwise. + """ + logged = cmpi_logging.trace_method(method) + def _new_func(self, *args, **kwargs): + """Wrapper for YumDB's method.""" + callargs = inspect.getcallargs(method, self, *args, **kwargs) + result = logged(self, *args, **kwargs) + if callargs.get('async', False): + return result + else: + return result.result_data + return _new_func + + return _decorator_async if async else _decorator + +class YumDB(singletonmixin.Singleton): + """ + Context manager for accessing yum/rpm database. + All requests are bundled into jobs -- instances of jobs.YumJob and + sent to YumWorker for processing. + + YumWorker is a separate process handling all calls to yum api. + Communication is done via queues (uplink and downlink). + Uplink is used to send jobs to YumWorker and downlink for obtaining + results. + + This is implemented in thread safe manner. + + It should be used as a context manager in case, we want to process + multiple jobs in single transaction. The example of usage: + with YumDB.getInstance() as ydb: + pkgs = ydb.filter_packages(...) + for pkg in pkgs: + ydb.install_package(pkg) + ... + Yum database stays locked in whole block of code under with statement. + """ + + # this is to inform Singleton, that __init__ should be called only once + ignoreSubsequent = True + + # This serves to all code base as a global variable used to check, + # whether YumDB instance is running under cimom broker or under worker + # process. This is important for code used in callback functions passed + # to worker responsible for creating instances of ConcreteJob. This code + # must avoid using calls to YumDB while running under worker. This + # + # Worker process must set this to False before starting its event handling + # loop. + RUNNING_UNDER_CIMOM_PROCESS = True + + @cmpi_logging.trace_method + def __init__(self, **kwargs): #pylint: disable=W0231 + """ + All arguments are passed to yum.YumBase constructor. + """ + self._process = None + if kwargs is None: + kwargs = {} + self._yum_kwargs = kwargs + + self._session_lock = threading.RLock() + self._session_level = 0 + + # used to guard access to _expected list and _process + self._reply_lock = threading.Lock() + # used to wait for job to be processed and received + self._reply_cond = threading.Condition(self._reply_lock) + # ids of all expected jobs -- those to be processed by YumWorker + self._expected = [] + # {job_id : reply, ... } + self._replies = {} + cmpi_logging.logger.trace_info('YumDB: initialized') + + # ************************************************************************* + # Private methods + # ************************************************************************* + @cmpi_logging.trace_method + def _handle_reply_timeout(self, job): + """ + This is called when timeout occurs while waiting on downlink queue for + reply. Delay can be caused by worker process's early termination (bug). + This handler tries to recover from such an situation. + """ + if not self._worker.is_alive(): + if self._worker.exitcode < 0: + cmpi_logging.logger.error("[jobid=%d] worker" + " process(pid=%d) killed by signal %s", job.jobid, + self._worker.pid, get_signal_name(-self._process.exitcode)) + else: + cmpi_logging.logger.error("[jobid=%d] worker" + " process(pid=%d) is dead - exit code: %d", + job.jobid, self._process.pid, self._worker.exitcode) + with self._reply_lock: + self._process = None + cmpi_logging.logger.error( + "[jobid=%d] starting new worker process", job.jobid) + self._expected = [] + if not isinstance(job, jobs.YumBeginSession): + with self._session_lock: + if self._session_level > 0: + cmpi_logging.logger.info('restoring session ' + 'level=%d', self._session_level) + new_session_job = jobs.YumBeginSession() + self._worker.uplink.put(new_session_job) + reply = self._worker.downlink.get() + log_reply_error(new_session_job, reply) + self._worker.uplink.put(job) + self._expected.append(job.jobid) + # other waiting processes need to resend their requests + self._reply_cond.notifyAll() + else: + cmpi_logging.logger.info("[jobid=%d] process is running," + " waiting some more", job.jobid) + + @cmpi_logging.trace_method + def _receive_reply(self, job): + """ + Block on downlink queue to receive expected replies from worker + process. Only one thread can be executing this code at any time. + + Only one thread can block on downlink channel to obtain reply. If + it's reply for him, he takes it and leaves, otherwise he adds it to + _replies dictionary and notifies other threads. This thread is the + one, whose job appears as first in _expected list. + + In case, that worker process terminated due to some error. Restart it + and resend all the job requests again. + """ + while True: + cmpi_logging.logger.debug("[jobid=%d] blocking on downlink queue", + job.jobid) + try: + jobout = self._worker.downlink.get( + block=True, timeout=MAX_JOB_WAIT_TIME) + if jobout.jobid == job.jobid: + cmpi_logging.logger.debug( + "[jobid=%d] received desired reply", job.jobid) + with self._reply_lock: + self._expected.remove(job.jobid) + if len(self._expected): + self._reply_cond.notify() + return jobout + else: + cmpi_logging.logger.info("[jobid=%d] received reply" + " for another thread (jobid=%d)", + job.jobid, jobout.jobid) + with self._reply_lock: + self._replies[jobout.jobid] = jobout + self._reply_cond.notifyAll() + except TQueue.Empty: + cmpi_logging.logger.warn("[jobid=%d] wait for job reply timeout" + "(%d seconds) occured", job.jobid, MAX_JOB_WAIT_TIME) + self._handle_reply_timeout(job) + + @cmpi_logging.trace_method + def _send_and_receive(self, job): + """ + Sends a request to server and blocks until job is processed by + YumWorker and reply is received. + + Only one thread can block on downlink channel to obtain reply. This + thread is the one, whose job appears as first in _expected list. Server + processes input jobs sequentially. That's why it's safe to presume, + that jobs are received in the same order as they were send. Thanks to + that we don't have to care about receiving replies for the other + waiting threads. + + @return result of job + """ + with self._reply_lock: + self._worker.uplink.put(job) + if getattr(job, 'async', False) is True: + return job.jobid + self._expected.append(job.jobid) + while True: + if job.jobid in self._replies: + cmpi_logging.logger.debug( + "[jobid=%d] desired reply already received", + job.jobid) + try: + self._expected.remove(job.jobid) + except ValueError: + cmpi_logging.logger.warn( + "[jobid=%d] reply not in expected list", + job.jobid) + return self._replies.pop(job.jobid) + elif job.jobid not in self._expected: + # process terminated, resending job + cmpi_logging.logger.warn("[jobid=%d] job removed" + " from expected list, sending request again", job.jobid) + self._worker.uplink.put(job) + self._expected.append(job.jobid) + elif job.jobid == self._expected[0]: + # now it's our turn to block on downlink + break + else: # another thread blocks on downlink -> let's sleep + cmpi_logging.logger.debug( + "[jobid=%d] another %d threads expecting reply," + " suspending...", job.jobid, len(self._expected) - 1) + self._reply_cond.wait() + cmpi_logging.logger.debug( + "[jobid=%d] received reply, waking up", job.jobid) + return self._receive_reply(job) + + def _do_job(self, job): + """ + Sends the job to YumWorker process and waits for reply. + If reply is a tuple, there was an error, while job processing. + Incoming exception is in format: + (exception_type, exception_value, formated_traceback_as_string) + @return reply + """ + cmpi_logging.logger.trace_verbose("YumDB: doing %s", job) + reply = self._send_and_receive(job) + log_reply_error(job, reply) + cmpi_logging.logger.trace_verbose("YumDB: job %s done", job.jobid) + return reply + + @property + def _worker(self): + """ + YumWorker process accessor. It's created upon first need. + """ + if self._process is None: + cmpi_logging.logger.trace_info("YumDB: starting YumWorker") + uplink = Queue() + downlink = Queue() + self._process = YumWorker(uplink, downlink, + indication_manager=IndicationManager.get_instance(), + yum_kwargs=self._yum_kwargs) + #logging_config=YUM_WORKER_DEBUG_LOGGING_CONFIG) + self._process.start() + cmpi_logging.logger.trace_info( + "YumDB: YumWorker started with pid=%s", self._process.pid) + return self._process + + # ************************************************************************* + # Special methods + # ************************************************************************* + def __del__(self): + """ + Ensure, that YumWorker process is correctly shutted down. + """ + self.clean_up() + singletonmixing.Singleton.__del__(self) + + @cmpi_logging.trace_method + def __enter__(self): + with self._session_lock: + if self._session_level == 0: + self._do_job(jobs.YumBeginSession()) + cmpi_logging.logger.trace_info('YumDB: new session started') + self._session_level += 1 + cmpi_logging.logger.trace_info('YumDB: nested to session level=%d', + self._session_level) + return self + + @cmpi_logging.trace_method + def __exit__(self, exc_type, exc_value, traceback): + with self._session_lock: + if self._session_level == 1: + self._do_job(jobs.YumEndSession()) + cmpi_logging.logger.trace_info('YumDB: session ended') + cmpi_logging.logger.trace_info('YumDB: emerged from session' + ' level=%d', self._session_level) + self._session_level = max(self._session_level - 1, 0) + + # ************************************************************************* + # Public methods + # ************************************************************************* + @cmpi_logging.trace_method + def clean_up(self): + """ + Shut down the YumWorker process. + """ + with self._reply_lock: + if self._process is not None: + cmpi_logging.logger.info('YumDB: terminating YumWorker') + self._process.uplink.put(None) # terminating command + self._process.join() + cmpi_logging.logger.info('YumDB: YumWorker terminated') + self._process = None + else: + cmpi_logging.logger.warn("YumDB: clean_up called, when process" + " not initialized!") + + # ************************************************************************* + # Jobs with simple results + # ************************************************************************* + @job_request() + def get_package_list(self, kind, + allow_duplicates=False, + sort=False, + include_repos=None, + exclude_repos=None): + """ + @param kind is one of: jobs.YumGetPackageList.SUPPORTED_KINDS + @param allow_duplicates says, whether to list all found versions + of single package + @return [pkg1, pkg2, ...], pkgi is instance of yumdb.PackageInfo + """ + return self._do_job(jobs.YumGetPackageList( + kind, allow_duplicates=allow_duplicates, sort=sort, + include_repos=include_repos, exclude_repos=exclude_repos)) + + @job_request() + def filter_packages(self, kind, + allow_duplicates=False, + sort=False, + include_repos=None, + exclude_repos=None, + **filters): + """ + Similar to get_package_list(), but applies filter on packages. + @see yumdb.jobs.YumFilterPackages job for supported filter keys + """ + return self._do_job(jobs.YumFilterPackages( + kind, allow_duplicates=allow_duplicates, sort=sort, + include_repos=include_repos, exclude_repos=exclude_repos, + **filters)) + + @job_request() + def get_repository_list(self, kind): + """ + @param kind is one of: jobs.YumGetRepositoryList.SUPPORTED_KINDS + @param allow_duplicates says, whether to list all found versions + of single package + @return [pkg1, pkg2, ...], pkgi is instance of yumdb.Repository + """ + return self._do_job(jobs.YumGetRepositoryList(kind)) + + @job_request() + def filter_repositories(self, kind, **filters): + """ + Similar to get_repository_list(), but applies filter on packages. + @see yumdb.jobs.YumFilterRepositories job for supported filter keys + """ + return self._do_job(jobs.YumFilterRepositories(kind, **filters)) + + @job_request() + def set_repository_enabled(self, repoid, enable): + """ + Enable or disable repository. + @param enable is a boolean + """ + return self._do_job(jobs.YumSetRepositoryEnabled(repoid, enable)) + + # ************************************************************************* + # Asynchronous jobs + # ************************************************************************* + @job_request(async=True) + def install_package(self, pkg, async=False, force=False, **metadata): + """ + Install package. + @param pkg is an instance of PackageInfo obtained with + get_package_list() or filter_packages() or a valid nevra as string. + Package must not be installed if force is False. + """ + return self._do_job(_make_async_job(jobs.YumInstallPackage, + pkg, force=force, async=async, metadata=metadata)) + + @job_request(async=True) + def remove_package(self, pkg, async=False, **metadata): + """ + @param pkg is an instance of PackageInfo obtained with + get_package_list() or filter_packages(), which must be installed + """ + return self._do_job(_make_async_job(jobs.YumRemovePackage, + pkg, async=async, metadata=metadata)) + + @job_request(async=True) + def update_to_package(self, desired_pkg, async=False, **metadata): + """ + @param desired_pkg is an instance of PackageInfo, + which must be available + """ + return self._do_job(_make_async_job(jobs.YumUpdateToPackage, + desired_pkg, async=async, metadata=metadata)) + + @job_request(async=True) + def update_package(self, pkg, + async=False, + to_epoch=None, + to_version=None, + to_release=None, + force=False, + **metadata): + """ + @param pkg is an instance of PackageInfo, which must be installed + + The other parameters filter candidate available packages for update. + """ + return self._do_job(_make_async_job(jobs.YumUpdatePackage, + pkg, async, to_epoch, to_version, to_release, force=force, + metadata=metadata)) + + @job_request(async=True) + def check_package(self, pkg, async=False, **metadata): + """ + Return all necessary information from package database for package + verification. + + :param pkg: (``PackageInfo``) An instance of PackageInfo + representing installed package or its nevra string. + :rtype: (``PackageCheck``) + """ + return self._do_job(_make_async_job(jobs.YumCheckPackage, + pkg, async=async, metadata=metadata)) + + @job_request(async=True) + def check_package_file(self, pkg, file_name, async=False): + """ + Return all necessary information from package database concerning + on particular file of package. If ``pkg`` does not contain + ``file_name``, ``FileNotFound`` error is raised. + + :param pkg: (``PackageInfo``) An instance of PackageInfo + representing installed package or its nevra string. + :rtype: (``PackageFile``) + """ + return self._do_job(_make_async_job(jobs.YumCheckPackageFile, + pkg, file_name, async=async)) + + @job_request(async=True) + def install_package_from_uri(self, uri, + async=False, update_only=False, force=False, **metadata): + """ + Install package from uri. + @param uri is either remote url or local path. + """ + return self._do_job(_make_async_job(jobs.YumInstallPackageFromURI, + uri, async, update_only, force=force, metadata=metadata)) + + # ************************************************************************* + # Control of asynchronous jobs + # ************************************************************************* + @job_request() + def get_job(self, jobid): + """ + Return instance of ``YumJob`` with given ``jobid``. + """ + return self._do_job(jobs.YumJobGet(jobid)) + + @job_request() + def get_job_list(self): + """ + Return list of all asynchronous jobs. + """ + return self._do_job(jobs.YumJobGetList()) + + @job_request() + def get_job_by_name(self, name): + """ + Return asynchronous job filtered by its name. + """ + return self._do_job(jobs.YumJobGetByName(name)) + + @job_request() + def set_job_priority(self, jobid, priority): + """ + Change priority of asynchronous job. This will change + its order in queue, if it is still enqeueued. + + Return object of job. + """ + return self._do_job(jobs.YumJobSetPriority(jobid, priority)) + + @job_request() + def update_job(self, jobid, **kwargs): + """ + Update metadata of job. + + :param kwargs: (``dict``) Is a dictionary of job's property names + with mapped new values. Only keys given will be changed in + desired job. + + **Note** that only keys, that do not affect job's priority or its + scheduling for deletion can be changed. See :ref:`YumJobUpdate`. + """ + return self._do_job(jobs.YumJobUpdate(jobid, **kwargs)) + + @job_request() + def reschedule_job(self, jobid, + delete_on_completion, time_before_removal): + """ + Change the scheduling of job for deletion. + + :param delete_on_completion: (``bool``) Says, whether the job will + be scheduled for deletion at ``finished + time_before_removal`` + time. + :param time_before_removal: (``int``) Number of seconds, after the job + is finished, it will be kept alive. + """ + return self._do_job(jobs.YumJobReschedule(jobid, + delete_on_completion, time_before_removal)) + + @job_request() + def delete_job(self, jobid): + """ + Delete job object. This can be called only on finished job. + """ + return self._do_job(jobs.YumJobDelete(jobid)) + + @job_request() + def terminate_job(self, jobid): + """ + Terminate job. This can be called only on *NEW* job. + """ + return self._do_job(jobs.YumJobTerminate(jobid)) + diff --git a/src/software/lmi/software/yumdb/errors.py b/src/software/lmi/software/yumdb/errors.py new file mode 100644 index 0000000..44eb1c7 --- /dev/null +++ b/src/software/lmi/software/yumdb/errors.py @@ -0,0 +1,111 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# +""" +Exceptions raisable by YumWorker. +""" + +class YumDBError(Exception): + """Base class for all errors under yumdb package.""" + +class DatabaseLockError(YumDBError): + """Raised, when the yum database can not be locked.""" + pass + +class TransactionError(YumDBError): + """Base exception representing yum transaction processing error.""" + pass +class TransactionBuildFailed(TransactionError): + """Raised, when transaction building fails.""" + pass +class PackageAlreadyInstalled(TransactionError): + """Raised, when trying to install already installed package.""" + def __init__(self, pkg): + TransactionError.__init__(self, + 'Package "%s" is already installed.' % pkg) +class PackageOpenError(TransactionError): + """Raised, when trying to open package obtained from URI.""" + def __init__(self, pkg, msg): + TransactionError.__init__(self, + 'Failed to open package "%s": %s' % (pkg, msg)) +class TransactionExecutionFailed(TransactionError): + """Raised, when YumBase.doTransaction() method fails.""" + pass + +class PackageError(YumDBError): + """Generic exception for error concerning package handling.""" + pass +class PackageNotFound(PackageError): + """Raised, when requested package could not be found.""" + pass +class PackageNotInstalled(PackageError): + """Raised, when requested package is not installed for desired action.""" + def __init__(self, pkg): + PackageError.__init__(self, 'Package "%s" is not installed.' % pkg) +class FileNotFound(PackageError): + """ + Raised, when requesting check on file that does not belong to + particular package. + """ + pass + +class RepositoryError(YumDBError): + """Generic exception for error concerning repository handling.""" + pass +class RepositoryNotFound(RepositoryError): + """Raised, when requested repository cound not be found.""" + def __init__(self, repoid): + RepositoryError.__init__(self, "No such repository: %s" % repoid) +class RepositoryChangeError(RepositoryError): + """Raised, when modification of repository failed.""" + pass + +class JobError(YumDBError): + """Generic exception for job handling.""" + pass +class UnknownJob(JobError): + """Raised, when no handler is available for given job on worker.""" + pass +class InvalidURI(JobError): + """Raised, when passed uri is not a valid one.""" + def __init__(self, uri): + JobError.__init__(self, "Invalid uri: \"%s\"" % uri) +class InvalidNevra(JobError): + """Raised when trying to instantiate job with invalid nevra string.""" + pass +class JobControlError(JobError): + """Generic exception for management of asynchronous jobs.""" + pass +class JobNotFound(JobControlError): + """Raised upon request for not existing asynchronous job.""" + def __init__(self, target): + JobControlError.__init__(self, "job %s could not be found" % target) +class InvalidJobState(JobControlError): + """ + Raised when requested operation can not be executed on job in + its current state. + """ + pass + +class IndicationError(YumDBError): + """Generic error for indication handling.""" + pass + diff --git a/src/software/lmi/software/yumdb/jobmanager.py b/src/software/lmi/software/yumdb/jobmanager.py new file mode 100644 index 0000000..a224905 --- /dev/null +++ b/src/software/lmi/software/yumdb/jobmanager.py @@ -0,0 +1,574 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# +""" +This is a module for ``JobManager`` which is a separate thread of +``YumWorker`` process. It keeps a cache of asynchronous jobs and handles +input and output queues. + +This module uses its own logging facilities because it runs in separeted +process not having access to broker logging features. + +Before using ``JobManager``, module's variable ``JOB_TO_MODEL`` should +be set to callable taking ``YumJob`` instance and returning its matching +CIM abstraction instance. +""" +import heapq +import inspect +import logging +import Queue +import sys +import threading +import time +import traceback + +from lmi.common.IndicationManager import IndicationManager +from lmi.common.JobManager import JobManager as JM +from lmi.software.yumdb import errors, jobs +from lmi.software.yumdb.util import trace_function + +# This is a callable, which must be initialized before JobManager is used. +# It should be a pointer to function, which takes a job and returns +# corresponding CIM instance. It's used for sending indications. +JOB_TO_MODEL = lambda job: None + +# Minimum time to keep asynchronous job in cache after completion. In seconds. +MINIMUM_TIME_BEFORE_REMOVAL = 10 + +# replacement for cmpi_logging.logger +LOG = None + +# ***************************************************************************** +# Decorators +# ***************************************************************************** +def job_handler(job_from_target=True): + """ + Decorator for JobManager methods serving as handlers for control jobs. + + Decorator locks the job_lock of manager's instance. + """ + def _wrapper_jft(method): + """ + It consumes "target" keyword argument (which is job's id) and makes + it an instance of YumJob. The method is then called with "job" argument + instead of "target". + """ + logged = trace_function(method) + + def _new_func(self, *args, **kwargs): + """Wrapper around method.""" + if 'target' in kwargs: + kwargs['job'] = kwargs.pop('target') + callargs = inspect.getcallargs(method, self, *args, **kwargs) + target = callargs.pop('job') + with self._job_lock: #pylint: disable=W0212 + if not target in self._async_jobs: #pylint: disable=W0212 + raise errors.JobNotFound(target) + job = self._async_jobs[target] #pylint: disable=W0212 + callargs['job'] = job + return logged(**callargs) + return _new_func + + def _simple_wrapper(method): + """Just locks the job lock.""" + def _new_func(self, *args, **kwargs): + """Wrapper around method.""" + with self._job_lock: #pylint: disable=W0212 + return method(self, *args, **kwargs) + return _new_func + + if job_from_target: + return _wrapper_jft + else: + return _simple_wrapper + +class JobIndicationSender(object): + """ + Makes creation and sending of indications easy. It keeps a reference + to job, which can be *snapshotted* for making CIM instance out of it. + These instances are then used to send indications via IndicationManager. + + Typical usage:: + + sender = JobIndicationSender(im, job, [fltr_id1, fltr_id2]) + ... # modify job + sender.snapshot() + sender.send() + + **Note** that number of kept CIM instances won't exceed 2. First one + is created upon instantiation and the second one be calling + ``snapshot()``. Any successive call to ``snapshot()`` will overwrite + the second instance. + """ + + def __init__(self, indication_manager, job, + indications=JM.IND_JOB_CHANGED, new=None): + """ + :param job (``YumJob``) Is job instance, which will be immediately + snapshoted as old instance and later as a new one. + :param indications (``list``) Can either be a list of indication ids + or a single indication id. + :param new (``YumJob``) A job instance stored as new. + """ + if not isinstance(indication_manager, IndicationManager): + raise TypeError("indication_manager must be a subclass of" + " IndicationManager") + if not isinstance(job, jobs.YumJob): + raise TypeError("job must be an instance of YumJob") + if not new is None and not isinstance(new, jobs.YumJob): + raise TypeError("new must be an instance of YumJob") + self._indication_manager = indication_manager + self._job = job + self._old_instance = JOB_TO_MODEL(job) + if new is not None: + new = JOB_TO_MODEL(job) + self._new_instance = new + self._indications = set() + self.indication_ids = indications + + @property + def job(self): + """ + Return instance of ``YumJob``. + """ + return self._job + + @property + def indication_ids(self): + """ + Return set of indication filter IDs. + """ + return self._indications.copy() + + @indication_ids.setter + def indication_ids(self, indication_ids): + """ + Set the indication filter IDs. + + :param indication_ids (``list``) Can be even single id. + """ + if isinstance(indication_ids, basestring): + indication_ids = set([indication_ids]) + self._indications = set(indication_ids) + + @trace_function + def add_indication_ids(self, indication_ids): + """ + Add filter IDs. + """ + if isinstance(indication_ids, basestring): + indication_ids = set([indication_ids]) + self._indications.update(indication_ids) + + @trace_function + def snapshot(self): + """ + Make a second CIM instance, overwriting previous one (not the first). + """ + self._new_instance = JOB_TO_MODEL(self._job) + + @trace_function + def send(self, make_snapshot=False): + """ + Send all requested indications for given job. + """ + if not self._indications: + raise errors.IndicationError( + "can not send any indication without id") + if make_snapshot: + self.snapshot() + if ( JM.IND_JOB_CHANGED in self._indications + and self._new_instance is None): + raise errors.IndicationError("no snapshot made for modified job") + for fltr_id in self._indications: + if fltr_id == JM.IND_JOB_CREATED: + LOG.debug("sending instance creation indication for job %s", + self._job) + self._indication_manager.send_instcreation( + self._new_instance if self._new_instance is not None + else self._old_instance, + fltr_id) + else: + LOG.debug("sending instance modification indication for job %s" + " with ID: %s", self._job, fltr_id) + self._indication_manager.send_instmodification( + self._old_instance, self._new_instance, + fltr_id) + +class JobManager(threading.Thread): + """ + Separate thread for managing queue of jobs requested by client. + There are three kinds of jobs, that are handled differently: + * asynchronous - kept in _async_jobs dictionary until job is + deleted by request or it expires; + no reply is sent to client upon job's completion + * synchronous - reply is sent to client after job's completion; + no reference to the job is kept afterwards + * job control - they are not enqueued in _job_queue for YumWorker + to process, but are handled directly and in the FIFO order + + Both asynchronous and synchronous jobs are enqueued in _job_queue + for YumWorker to obtain them. It's a priority queue sorting jobs by their + priority. + """ + # enumeration of actions, that may be enqueued in calendar + ACTION_REMOVE = 0 + + ACTION_NAMES = ['remove'] + + def __init__(self, queue_in, queue_out, indication_manager): + threading.Thread.__init__(self, name="JobManager") + self._queue_in = queue_in + self._queue_out = queue_out + self._indication_manager = indication_manager + self._terminate = False + + # (time, jobid, action) + self._calendar = [] + # {jobid : job} + self._async_jobs = {} + + # lock for critical access to _calendar, _async_jobs and _job_queue + self._job_lock = threading.RLock() + # priority queue of jobs that are processed by YumWorker + self._job_queue = [] + # condition for YumWorker waiting on empty _job_queue + self._job_enqueued = threading.Condition(self._job_lock) + + # ************************************************************************* + # Private methods + # ************************************************************************* + @trace_function + def _control_job(self, job): + """ + Function dispatching job to handler for particular YumJob subclass. + """ + try: + handler = { + # these are from YumDB client + jobs.YumJobGetList : self._handle_get_list, + jobs.YumJobGet : self._handle_get, + jobs.YumJobGetByName : self._handle_get_by_name, + jobs.YumJobSetPriority : self._handle_set_priority, + jobs.YumJobReschedule : self._handle_reschedule, + jobs.YumJobUpdate : self._handle_update, + jobs.YumJobDelete : self._handle_delete, + jobs.YumJobTerminate : self._handle_terminate, + }[job.__class__] + LOG.info("processing control job %s", str(job)) + except KeyError: + raise errors.UnknownJob("No handler for job \"%s\"." % + job.__class__.__name__) + return handler(**job.job_kwargs) + + @trace_function + def _enqueue_job(self, job): + """ + Insert incoming job into _job_queue. + """ + if isinstance(job, jobs.YumJobControl): + result = job.RESULT_SUCCESS + job.start() + try: + data = self._control_job(job) + except Exception: #pylint: disable=W0703 + result = job.RESULT_ERROR + data = sys.exc_info() + data = (data[0], data[1], traceback.format_tb(data[2])) + LOG.exception("control job %s failed", job) + job.finish(result, data) + LOG.debug("sending reply for %s: (%s, %s)", job, + job.ResultNames[job.result], job.result_data) + self._queue_out.put(job) + else: + if job is None: + LOG.debug('received terminating command') + self._terminate = True + LOG.debug('job %s enqued for YumWorker to handle', job) + heapq.heappush(self._job_queue, job) + if getattr(job, 'async', False) is True: + ind = self._prepare_indication_for(job, JM.IND_JOB_CREATED) + self._async_jobs[job.jobid] = job + ind.send() + self._job_enqueued.notify() + + @trace_function + def _schedule_event(self, after, jobid, action): + """ + Enqueue event into calendar. Event consists of time, jobid and + action. + """ + schedule_at = time.time() + after + for (sched, jid, act) in self._calendar: + if jid == jobid and act == action: + if sched <= schedule_at: # same event already scheduled + return + # schedule it for early time + LOG.debug('rescheduling action %s on job %d to take place' + ' after %d seconds (instead of %d)', + self.ACTION_NAMES[action], jid, after, + sched - schedule_at + after) + self._calendar.remove((sched, jid, act)) + self._calendar.append((schedule_at, jid, act)) + heapq.heapify(self._calendar) + return + LOG.debug('scheduling action %s on job %d to take place after ' + ' %d seconds', self.ACTION_NAMES[action], jobid, after) + heapq.heappush(self._calendar, (schedule_at, jobid, action)) + + @trace_function + def _run_event(self, jobid, action): + """ + Process event from calendar. + """ + if action == self.ACTION_REMOVE: + with self._job_lock: + del self._async_jobs[jobid] + else: + msg = "unsupported action: %s" % action + raise ValueError(msg) + + @trace_function + def _prepare_indication_for(self, job, *args, **kwargs): + """ + Return instance of ``JobIndicationSender``. + """ + return JobIndicationSender(self._indication_manager, job, + *args, **kwargs) + + # ************************************************************************* + # Job handlers + # ************************************************************************* + @job_handler() + def _handle_get(self, job): #pylint: disable=R0201 + """@return job object""" + return job + + @job_handler(False) + def _handle_get_list(self): + """@return list of all asynchronous jobs""" + with self._job_lock: + return sorted(self._async_jobs.values()) + + @job_handler(False) + def _handle_get_by_name(self, target): + """@return job object filtered by name""" + for job in self._async_jobs.values(): + if 'name' in job.metadata and target == job.metadata['name']: + return job + raise errors.JobNotFound(target) + + @job_handler() + def _handle_set_priority(self, job, new_priority): + """ + Modify job's priority and updates its position in queue. + @return modified job object + """ + if not isinstance(new_priority, (int, long)): + raise TypeError('priority must be an integer') + if job.priority != new_priority: + ind = self._prepare_indication_for(job) + job.update(priority=new_priority) + if job in self._job_queue: + heapq.heapify(self._job_queue) + ind.send(True) + return job + + @job_handler() + def _handle_reschedule(self, job, + delete_on_completion, + time_before_removal): + """ + Changes job's schedule for its deletion. + """ + if ( job.delete_on_completion == delete_on_completion + and job.time_before_removal == time_before_removal): + return + if job.finished and job.delete_on_completion: + for i, event in enumerate(self._calendar): + if event[1] == job.jobid and event[2] == self.ACTION_REMOVE: + del self._calendar[i] + heapq.heapify(self._calendar) + break + ind = self._prepare_indication_for(job) + if delete_on_completion: + schedule_at = time_before_removal + if job.finished: + schedule_at = job.finished + schedule_at - time.time() + self._schedule_event(schedule_at, job.jobid, self.ACTION_REMOVE) + job.delete_on_completion = delete_on_completion + job.time_before_removal = time_before_removal + ind.send(True) + return job + + @job_handler() + def _handle_update(self, job, data): #pylint: disable=R0201 + """ + Updates any job metadata. + """ + ind = self._prepare_indication_for(job) + job.update(**data) + ind.send(True) + return job + + @job_handler() + def _handle_delete(self, job): + """ + Deletes finished asynchronous job. + """ + if not job.finished: + raise errors.InvalidJobState( + 'can not delete unfinished job "%s"' % job) + try: + self._job_queue.remove(job) + heapq.heapify(self._job_queue) + LOG.debug('job "%s" removed from queue', job) + except ValueError: + LOG.debug('job "%s" not started and not enqueued', job) + del self._async_jobs[job.jobid] + return job + + @job_handler() + def _handle_terminate(self, job): + """ + Terminates not started job. + """ + if job.started and not job.finished: + raise errors.InvalidJobState('can not kill running job "%s"' % job) + if job.finished: + raise errors.InvalidJobState('job "%s" already finished' % job) + self._job_queue.remove(job) + heapq.heapify(self._job_queue) + ind = self._prepare_indication_for(job) + job.finish(result=job.RESULT_TERMINATED) + ind.send(True) + LOG.info('terminated not started job "%s"', job) + return job + + # ************************************************************************* + # Public properties + # ************************************************************************* + @property + def queue_in(self): + """Incoming queue for YumJob instances.""" + return self._queue_in + + @property + def queue_out(self): + """Output queue for results.""" + return self._queue_out + + # ************************************************************************* + # Public methods + # ************************************************************************* + @trace_function + def finish_job(self, job, result, result_data): + """ + This should be called for any job by YumWorker after the job is + processed. + + If the job is synchronous, reply is send at once. Otherwise the result + is stored for later client's query in the job itself. + """ + with self._job_lock: + if job.state != job.RUNNING: + raise errors.InvalidJobState( + 'can not finish not started job "%s"' % job) + if getattr(job, 'async', False): + ind = self._prepare_indication_for(job, + (JM.IND_JOB_CHANGED, JM.IND_JOB_PERCENT_UPDATED)) + job.finish(result, result_data) + if getattr(job, 'async', False): + if job.delete_on_completion: + schedule_at = max( job.time_before_removal + , MINIMUM_TIME_BEFORE_REMOVAL) + self._schedule_event(schedule_at, job.jobid, + self.ACTION_REMOVE) + if result == job.RESULT_SUCCESS: + ind.add_indication_ids(JM.IND_JOB_SUCCEEDED) + elif result == job.RESULT_ERROR: + ind.add_indication_ids(JM.IND_JOB_FAILED) + ind.send(True) + else: + LOG.debug("sending reply for %s: (%s, %s)", job, + job.ResultNames[job.result], job.result_data) + self._queue_out.put(job) + return job + + @trace_function + def get_job(self, block=True, timeout=None): + """ + Method supposed to be used only by YumWorker. It pops the first job + from _job_queue, starts it and returns it. + """ + start = time.time() + with self._job_lock: + if len(self._job_queue) == 0 and not block: + raise Queue.Empty + while len(self._job_queue) == 0: + if timeout: + LOG.debug('waiting for job for %s seconds' % timeout) + self._job_enqueued.wait(timeout) + if len(self._job_queue) == 0: + now = time.time() + if timeout > now - start: + raise Queue.Empty + job = heapq.heappop(self._job_queue) + if job is not None: + if getattr(job, "async", False): + ind = self._prepare_indication_for(job, + (JM.IND_JOB_CHANGED, JM.IND_JOB_PERCENT_UPDATED)) + job.start() + ind.send(True) + else: + job.start() + return job + + def run(self): + """The entry point of thread.""" + global LOG #pylint: disable=W0603 + LOG = logging.getLogger(__name__) + LOG.info("%s thread started", self.name) + + while self._terminate is False: + try: + timeout = None + with self._job_lock: + if len(self._calendar) > 0: + timeout = self._calendar[0][0] - time.time() + LOG.debug('waiting on input queue for job%s', + (' with timeout %s' % timeout) if timeout else '') + job = self._queue_in.get(timeout=timeout) + with self._job_lock: + self._enqueue_job(job) + while not self._queue_in.empty(): + # this won't throw + self._enqueue_job(self._queue_in.get_nowait()) + + except Queue.Empty: + with self._job_lock: + while ( len(self._calendar) + and self._calendar[0][0] < time.time()): + _, jobid, action = heapq.heappop(self._calendar) + LOG.info('running action %s on job(id=%d)', + self.ACTION_NAMES[action], jobid) + self._run_event(jobid, action) + LOG.info('%s thread terminating', self.name) + diff --git a/src/software/lmi/software/yumdb/jobs.py b/src/software/lmi/software/yumdb/jobs.py new file mode 100644 index 0000000..346ff17 --- /dev/null +++ b/src/software/lmi/software/yumdb/jobs.py @@ -0,0 +1,668 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# +""" +Define job classes representing kinds of jobs of worker process. +""" + +import os +import threading +import time +import yum + +from lmi.software import util +from lmi.software.yumdb import errors +from lmi.software.yumdb.packageinfo import PackageInfo +from lmi.software.yumdb.repository import Repository + +DEFAULT_JOB_PRIORITY = 10 +# in seconds +DEFAULT_TIME_BEFORE_REMOVAL = 60 * 5 + +class YumJob(object): #pylint: disable=R0903 + """ + Base class for any job, that is processable by YumWorker process. + It contains jobid attribute, that must be unique for + each job, it's counted from zero a incremented after each creation. + + metadata attribute typically contain: + name - name of job, that is modifiable by user + method - identificator of method, that lead to creation of job + """ + __slots__ = ( 'jobid', 'created', 'started', 'finished', 'last_change' + , 'priority', 'result', 'result_data') + + # jobs can be created concurrently from multiple threads, that's + # why we need to make its creation thread safe + _JOB_ID_LOCK = threading.Lock() + _JOB_ID = 0 + + # job state enumeration + NEW, RUNNING, COMPLETED, TERMINATED, EXCEPTION = range(5) + # job result enumeration + RESULT_SUCCESS, RESULT_TERMINATED, RESULT_ERROR = range(3) + + ResultNames = ("success", "terminated", "error") + + @staticmethod + def _get_job_id(): + """ + Generates new job ids. It should be called only from constructor + of YumJob. Ensures, that each job has a unique number. + @return number of jobs created since program start -1 + """ + with YumJob._JOB_ID_LOCK: + val = YumJob._JOB_ID + YumJob._JOB_ID += 1 + return val + + @classmethod + def handle_ignore_job_props(cls): + """ + @return set of job properties, that does not count as job's handler + arguments - job handler does not care fore metadata, jobid, priority, + etc... + """ + return set(YumJob.__slots__) + + def __init__(self, priority=10): + if not isinstance(priority, (int, long)): + raise TypeError("priority must be integer") + self.jobid = self._get_job_id() + self.started = None + self.finished = None + self.priority = priority + self.created = time.time() + self.last_change = self.created + self.result = None + self.result_data = None + + @property + def state(self): + """ + @return integer representing job's state + """ + if not self.started: + return self.NEW + if not self.finished: + return self.RUNNING + if self.result == self.RESULT_ERROR: + return self.EXCEPTION + if self.result == self.RESULT_TERMINATED: + return self.TERMINATED + return self.COMPLETED + + @property + def job_kwargs(self): + """ + Jobs are in worker handled in handlers specific for each subclass. + These handlers are methods of worker. They accepts concrete arguments + that can be obtained from job by invoking this property. + @return dictionary of keyword arguments of job + """ + kwargs = {} + cls = self.__class__ + while not cls in (YumJob, object): + for slot in cls.__slots__: + if ( not slot in kwargs + and not slot in cls.handle_ignore_job_props()): + kwargs[slot] = getattr(self, slot) + cls = cls.__bases__[0] + for prop in YumJob.__slots__: + kwargs.pop(prop, None) + return kwargs + + def start(self): + """Modify the state of job to RUNNING.""" + if self.started: + raise errors.InvalidJobState("can not start already started job") + self.started = time.time() + self.last_change = self.started + + def finish(self, result, data=None): + """ + Modify the state of job to one of {COMPLETED, EXCEPTION, TERMINATED}. + Depending on result parameter. + """ + if not self.started and result != self.RESULT_TERMINATED: + raise errors.InvalidJobState("can not finish not started job") + self.finished = time.time() + if result == self.RESULT_TERMINATED: + self.started = self.finished + self.result = result + self.result_data = data + self.last_change = self.finished + + def update(self, **kwargs): + """Change job's properties.""" + change = False + for key, value in kwargs.items(): + if getattr(self, key) != value: + setattr(self, key, value) + change = True + if change is True: + self.last_change = time.time() + + def __eq__(self, other): + return self.__class__ is other.__class__ and self.jobid == other.jobid + + def __ne__(self, other): + return ( self.__class__ is not other.__class__ + or self.jobid != other.jobid) + + def __lt__(self, other): + """ + JobControl jobs have the highest priority. + """ + return ( ( isinstance(self, YumJobControl) + and not isinstance(other, YumJobControl)) + or ( self.priority < other.priority + or ( self.priority == other.priority + and ( self.jobid < other.jobid + or ( self.jobid == other.jobid + and (self.created < other.created)))))) + + def __cmp__(self, other): + if ( isinstance(self, YumJobControl) + and not isinstance(other, YumJobControl)): + return -1 + if ( not isinstance(self, YumJobControl) + and isinstance(other, YumJobControl)): + return 1 + if self.priority < other.priority: + return -1 + if self.priority > other.priority: + return 1 + if self.jobid < other.jobid: + return -1 + if self.jobid > other.jobid: + return 1 + if self.created < other.created: + return -1 + if self.created > other.created: + return 1 + return 0 + + def __str__(self): + return "%s(id=%d,p=%d)" % ( + self.__class__.__name__, self.jobid, self.priority) + + def __getstate__(self): + ret = self.job_kwargs + for prop in self.handle_ignore_job_props(): + ret[prop] = getattr(self, prop) + return ret + + def __setstate__(self, state): + for k, value in state.items(): + setattr(self, k, value) + +class YumAsyncJob(YumJob): #pylint: disable=R0903 + """ + Base class for jobs, that support asynchronnous execution. + No reply is sent upon job completition or error. The results are + kept on server. + """ + __slots__ = ( 'async' + , 'delete_on_completion' + , 'time_before_removal' + , 'metadata') + + @classmethod + def handle_ignore_job_props(cls): + return YumJob.handle_ignore_job_props().union(YumAsyncJob.__slots__) + + def __init__(self, priority=10, async=False, metadata=None): + YumJob.__init__(self, priority) + self.async = bool(async) + self.delete_on_completion = True + self.time_before_removal = DEFAULT_TIME_BEFORE_REMOVAL + if metadata is None and self.async is True: + metadata = {} + self.metadata = metadata + + def __str__(self): + return "%s(id=%d,p=%d%s%s)" % ( + self.__class__.__name__, self.jobid, + self.priority, + ',async' if self.async else '', + (',name="%s"'%self.metadata['name']) + if self.metadata and 'name' in self.metadata else '') + + def update(self, **kwargs): + if 'metadata' in kwargs: + self.metadata.update(kwargs.pop('metadata')) + return YumJob.update(self, **kwargs) + +# ***************************************************************************** +# Job control funtions +# ***************************************************************************** +class YumJobControl(YumJob): #pylint: disable=R0903 + """Base class for any job used for asynchronous jobs management.""" + pass + +class YumJobGetList(YumJobControl): #pylint: disable=R0903 + """Request for obtaining list of all asynchronous jobs.""" + pass + +class YumJobOnJob(YumJobControl): + """ + Base class for any control job acting upon particular asynchronous job. + """ + __slots__ = ('target', ) + def __init__(self, target): + YumJobControl.__init__(self) + if not isinstance(target, (int, long)): + raise TypeError("target must be an integer") + self.target = target + +class YumJobGet(YumJobOnJob): #pylint: disable=R0903 + """Get job object by its id.""" + pass + +class YumJobGetByName(YumJobOnJob): #pylint: disable=R0903 + """Get job object by its name property.""" + def __init__(self, name): + YumJobOnJob.__init__(self, -1) + self.target = name + +class YumJobSetPriority(YumJobOnJob): #pylint: disable=R0903 + """Change priority of job.""" + __slots__ = ('new_priority', ) + + def __init__(self, target, priority): + YumJobOnJob.__init__(self, target) + self.new_priority = priority + +class YumJobUpdate(YumJobOnJob): #pylint: disable=R0903 + """ + .. _YumJobUpdate: + + Update job's metadata. There are some forbidden properties, that + can not be changed in this way. Those are all affecting job's priority + and its scheduling for deletion. Plus any that store job's state. + All forbidden properties are listed in ``FORBIDDEN_PROPERTIES``. + """ + __slots__ = ('data', ) + FORBIDDEN_PROPERTIES = ( + 'async', 'jobid', 'created', 'started', 'priority', 'finished', + 'delete_on_completion', 'time_before_removal', 'last_change') + + def __init__(self, target, **kwargs): + YumJobOnJob.__init__(self, target) + assert not set.intersection( + set(YumJobUpdate.FORBIDDEN_PROPERTIES), set(kwargs)) + self.data = kwargs + +class YumJobReschedule(YumJobOnJob): #pylint: disable=R0903 + """Change the schedule of job's deletion.""" + __slots__ = ('delete_on_completion', 'time_before_removal') + def __init__(self, target, delete_on_completion, time_before_removal): + YumJobOnJob.__init__(self, target) + if not isinstance(time_before_removal, (int, long, float)): + raise TypeError("time_before_removal must be float") + self.delete_on_completion = bool(delete_on_completion) + self.time_before_removal = time_before_removal + +class YumJobDelete(YumJobOnJob): #pylint: disable=R0903 + """Delete job - can only be called on finished job.""" + pass + +class YumJobTerminate(YumJobOnJob): #pylint: disable=R0903 + """ + Can only be called on not yet started job. + Running job can not be terminated. + """ + pass + +# ***************************************************************************** +# Yum API functions +# ***************************************************************************** +class YumBeginSession(YumJob): #pylint: disable=R0903 + """ + Begin session on YumWorker which ensures that yum database is locked + during its lifetime. Sessions can be nested, but the number of + YumEndSession jobs must be processed to make the database unlocked. + """ + pass +class YumEndSession(YumJob): #pylint: disable=R0903 + """ + End the session started with YumBeginSession. If the last active session + is ended, database will be unlocked. + """ + pass + +class YumGetPackageList(YumJob): #pylint: disable=R0903 + """ + Job requesing a list of packages. + Arguments: + kind - supported values are in SUPPORTED_KINDS tuple + * installed lists all installed packages; more packages with + the same name can be installed varying in their architecture + * avail_notinst lists all available, not installed packages; + allow_duplicates must be True to include older packages (but still + available) + * avail_reinst lists all installed packages, that are available; + package can be installed, but not available anymore due to updates + of repository, where only the newest packages are kept + * available lists a union of avail_notinst and avail_reinst + * all lists union of installed and avail_notinst + + allow_duplicates - whether multiple packages can be present + in result for single (name, arch) of package differing + in their version + + sort - whether to sort packages by nevra + + include_repos - either a string passable to RepoStorage.enableRepo() + or a list of repository names, that will be temporared enabled before + listing packages; this is applied after disabling of repositories + + exclude_repos - either a string passable to RepoStorage.disableRepo() + or a list of repository names, that will be temporared disabled before + listing packages; this is applied before enabling of repositories + + Worker replies with [pkg1, pkg2, ...]. + """ + __slots__ = ('kind', 'allow_duplicates', 'sort', 'include_repos', + 'exclude_repos') + + SUPPORTED_KINDS = ( 'installed', 'available', 'avail_reinst' + , 'avail_notinst', 'all') + + def __init__(self, kind, allow_duplicates, sort=False, + include_repos=None, exclude_repos=None): + YumJob.__init__(self) + if not isinstance(kind, basestring): + raise TypeError("kind must be a string") + if not kind in self.SUPPORTED_KINDS: + raise ValueError("kind must be one of {%s}" % + ", ".join(self.SUPPORTED_KINDS)) + for arg in ('include_repos', 'exclude_repos'): + val = locals()[arg] + if ( not val is None + and not isinstance(arg, (tuple, list, basestring))): + raise TypeError("expected list or string for %s" % arg) + self.kind = kind + self.allow_duplicates = bool(allow_duplicates) + self.sort = bool(sort) + self.include_repos = include_repos + self.exclude_repos = exclude_repos + +class YumFilterPackages(YumGetPackageList): #pylint: disable=R0903 + """ + Job similar to YumGetPackageList, but allowing to specify + filter on packages. + Arguments (plus those in YumGetPackageList): + name, epoch, version, release, arch, nevra, envra, evra + + Some of those are redundant, but filtering is optimized for + speed, so supplying all of them won't affect performance. + + Worker replies with [pkg1, pkg2, ...]. + """ + __slots__ = ( + 'name', 'epoch', 'version', 'release', 'arch', + 'nevra', 'envra', 'evra', 'repoid') + + def __init__(self, kind, allow_duplicates, + sort=False, include_repos=None, exclude_repos=None, + name=None, epoch=None, version=None, + release=None, arch=None, + nevra=None, evra=None, + envra=None, + repoid=None): + if nevra is not None and not util.RE_NEVRA.match(nevra): + raise ValueError("Invalid nevra: %s" % nevra) + if evra is not None and not util.RE_EVRA.match(evra): + raise ValueError("Invalid evra: %s" % evra) + if envra is not None and not util.RE_ENVRA.match(evra): + raise ValueError("Invalid envra: %s" % envra) + YumGetPackageList.__init__(self, kind, allow_duplicates, sort, + include_repos=include_repos, exclude_repos=exclude_repos) + self.name = name + self.epoch = None if epoch is None else str(epoch) + self.version = version + self.release = release + self.arch = arch + self.nevra = nevra + self.evra = evra + self.envra = envra + self.repoid = repoid + +class YumSpecificPackageJob(YumAsyncJob): #pylint: disable=R0903 + """ + Abstract job taking instance of yumdb.PackageInfo as argument or + package's nevra. + Arguments: + pkg - plays different role depending on job subclass; + can also be a nevra + """ + __slots__ = ('pkg', ) + def __init__(self, pkg, async=False, metadata=None): + if isinstance(pkg, basestring): + if not util.RE_NEVRA_OPT_EPOCH.match(pkg): + raise errors.InvalidNevra('not a valid nevra "%s"' % pkg) + elif not isinstance(pkg, PackageInfo): + raise TypeError("pkg must be either string or instance" + " of PackageInfo") + YumAsyncJob.__init__(self, async=async, metadata=metadata) + self.pkg = pkg + +class YumInstallPackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Job requesting installation of specific package. + pkg argument should be available. + Arguments: + pkg - same as in YumSpecificPackageJob + force is a boolean saying: + True -> reinstall the package if it's already installed + False -> fail if the package is already installed + + Worker replies with new instance of package. + """ + __slots__ = ('force', ) + def __init__(self, pkg, async=False, force=False, metadata=None): + YumSpecificPackageJob.__init__( + self, pkg, async=async, metadata=metadata) + self.force = bool(force) + +class YumRemovePackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Job requesting removal of specific package. + pkg argument should be installed. + """ + pass + +class YumUpdateToPackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Job requesting update to provided specific package. + Package is updated to epoch, version and release of this + provided available package. + + Worker replies with new instance of package. + """ + pass + +class YumUpdatePackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Job requesting update of package, optionally reducing possible + candidate packages to ones with specific evr. + Arguments: + to_epoch, to_version, to_release + force is a boolean, that has meaning only when update_only is False: + True -> reinstall the package if it's already installed + False -> fail if the package is already installed + + The arguments more given, the more complete filter of candidates. + + Worker replies with new instance of package. + """ + __slots__ = ('to_epoch', 'to_version', 'to_release', 'force') + + def __init__(self, pkg, async=False, + to_epoch=None, to_version=None, to_release=None, force=False, + metadata=None): + if not isinstance(pkg, PackageInfo): + raise TypeError("pkg must be instance of yumdb.PackageInfo") + YumSpecificPackageJob.__init__( + self, pkg, async=async, metadata=metadata) + self.to_epoch = to_epoch + self.to_version = to_version + self.to_release = to_release + self.force = bool(force) + +class YumCheckPackage(YumSpecificPackageJob): #pylint: disable=R0903 + """ + Request verification information for instaled package and its files. + + Arguments: + pkg - either instance of PackageInfo or nevra string. + In latter case it will be replaced for YumWorker with instance + of PackageInfo. + + Worker replies with ``(pkg_info, pkg_check)``. + where: + ``pkg_info`` - is instance of PackageInfo + ``pkg_check`` - new instance of yumdb.PackageCheck + """ + def __init__(self, pkg, async=False, metadata=None): + YumSpecificPackageJob.__init__(self, pkg, async=async, + metadata=metadata) + if isinstance(pkg, PackageInfo) and not pkg.installed: + raise ValueError("package must be installed to check it") + +class YumCheckPackageFile(YumCheckPackage): #pylint: disable=R0903 + """ + Request verification information for particular file of installed + package. + + Worker replies with ``(pkg_info, pkg_check)``. + where: + ``pkg_info`` - is instance of PackageInfo + ``pkg_check`` - new instance of yumdb.PackageCheck containing only + requested file. + """ + __slots__ = ('file_name', ) + def __init__(self, pkg, file_name, *args, **kwargs): + YumCheckPackage.__init__(self, pkg, *args, **kwargs) + if not isinstance(file_name, basestring): + raise TypeError("file_name must be string") + self.file_name = file_name + +class YumInstallPackageFromURI(YumAsyncJob): #pylint: disable=R0903 + """ + Job requesting installation of specific package from URI. + Arguments: + uri is either a path to rpm package on local filesystem or url + of rpm stored on remote host + update_only is a boolean: + True -> install the package only if the older version is installed + False -> install the package if it's not already installed + force is a boolean, that has meaning only when update_only is False: + True -> reinstall the package if it's already installed + False -> fail if the package is already installed + + Worker replies with new instance of package. + """ + __slots__ = ('uri', 'update_only', "force") + def __init__(self, uri, async=False, update_only=False, force=False, + metadata=None): + if not isinstance(uri, basestring): + raise TypeError("uri must be a string") + if uri.startswith('file://'): + uri = uri[len('file://'):] + if not yum.misc.re_remote_url(uri) and not os.path.exists(uri): + raise errors.InvalidURI(uri) + YumAsyncJob.__init__(self, async=async, metadata=metadata) + self.uri = uri + self.update_only = bool(update_only) + self.force = bool(force) + +class YumGetRepositoryList(YumJob): #pylint: disable=R0903 + """ + Job requesing a list of repositories. + Arguments: + kind - supported values are in SUPPORTED_KINDS tuple + + Worker replies with [repo1, repo2, ...]. + """ + __slots__ = ('kind', ) + + SUPPORTED_KINDS = ('all', 'enabled', 'disabled') + + def __init__(self, kind): + YumJob.__init__(self) + if not isinstance(kind, basestring): + raise TypeError("kind must be a string") + if not kind in self.SUPPORTED_KINDS: + raise ValueError("kind must be one of {%s}" % + ", ".join(self.SUPPORTED_KINDS)) + self.kind = kind + +class YumFilterRepositories(YumGetRepositoryList): #pylint: disable=R0903 + """ + Job similar to YumGetRepositoryList, but allowing to specify + filter on packages. + Arguments (plus those in YumGetRepositoryList): + name, gpg_check, repo_gpg_check + + Some of those are redundant, but filtering is optimized for + speed, so supplying all of them won't affect performance. + + Worker replies with [repo1, repo2, ...]. + """ + __slots__ = ('repoid', 'gpg_check', 'repo_gpg_check') + + def __init__(self, kind, + repoid=None, gpg_check=None, repo_gpg_check=None): + YumGetRepositoryList.__init__(self, kind) + self.repoid = repoid + self.gpg_check = None if gpg_check is None else bool(gpg_check) + self.repo_gpg_check = ( + None if repo_gpg_check is None else bool(repo_gpg_check)) + +class YumSpecificRepositoryJob(YumJob): #pylint: disable=R0903 + """ + Abstract job taking instance of yumdb.Repository as argument. + Arguments: + repo - (``Repository`` or ``str``) plays different role depending + on job subclass + """ + __slots__ = ('repo', ) + def __init__(self, repo): + if not isinstance(repo, (Repository, basestring)): + raise TypeError("repoid must be either instance of" + " yumdb.Repository or string") + YumJob.__init__(self) + self.repo = repo + +class YumSetRepositoryEnabled(YumSpecificRepositoryJob):#pylint: disable=R0903 + """ + Job allowing to enable or disable repository. + Arguments: + enable - (``boolean``) representing next state + """ + __slots__ = ('enable', ) + def __init__(self, repo, enable): + YumSpecificRepositoryJob.__init__(self, repo) + self.enable = bool(enable) + diff --git a/src/software/lmi/software/yumdb/packagecheck.py b/src/software/lmi/software/yumdb/packagecheck.py new file mode 100644 index 0000000..e66078c --- /dev/null +++ b/src/software/lmi/software/yumdb/packagecheck.py @@ -0,0 +1,235 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# +""" +Module with definition of RPM package check class. +""" + +from collections import OrderedDict +from datetime import datetime +import grp +import logging +import os +import pwd +import rpm +import yum + +from lmi.software.yumdb import errors + +CHECKSUMTYPE_STR2NUM = dict((val.lower(), k) for (k, val) in + yum.constants.RPM_CHECKSUM_TYPES.items()) + +( FILE_TYPE_UNKNOWN +, FILE_TYPE_FILE +, FILE_TYPE_DIRECTORY +, FILE_TYPE_SYMLINK +, FILE_TYPE_FIFO +, FILE_TYPE_CHARACTER_DEVICE +, FILE_TYPE_BLOCK_DEVICE +) = range(7) + +FILE_TYPE_NAMES = ( 'unknown', 'file', 'directory', 'symlink', 'fifo' + , 'character device', 'block device') + +class PackageFile(object): + """ + Metadata related to particular file on filesystem belonging to RPM package. + Data contained here are from RPM database. + + Attributes: + ``path`` - (``str``) Absolute path of file. + ``file_type`` - (``int``) One of ``FILE_TYPE_*`` identifiers above. + ``uid`` - (``int``) User ID. + ``gid`` - (``int``) Group ID. + ``mode`` - (``int``) Raw file mode. + ``device`` - (``int``) Device number. + ``mtime`` - (``int``) Last modification time in seconds. + ``size`` - (``long``) File size as a number of bytes. + ``link_target`` - (``str``) Link target of symlink. None if ``file_type`` + is not symlink. + ``checksum`` - (``str``) Checksum as string in hexadecimal format. + None if file is not a regular file. + """ + __slots__ = ("path", "file_type", "uid", "gid", "mode", "device", "mtime", + "size", "link_target", "checksum") + + def __init__(self, path, file_type, uid, gid, mode, device, mtime, size, + link_target, checksum): + if not isinstance(file_type, basestring): + raise TypeError("file_type must be a string") + for arg in ('uid', 'gid', 'mode', 'mtime', 'size'): + if not isinstance(locals()[arg], (int, long)): + raise TypeError("%s must be integer" % arg) + if not os.path.isabs(path): + raise ValueError("path must be an absolute path") + self.path = path + try: + self.file_type = FILE_TYPE_NAMES.index(file_type.lower()) + except ValueError: + logging.getLogger(__name__).error('unrecognized file type "%s" for' + ' file "%s"', file_type, path) + self.file_type = FILE_TYPE_NAMES[FILE_TYPE_UNKNOWN] + self.uid = uid + self.gid = gid + self.mode = mode + self.device = device + self.mtime = mtime + self.size = size + self.link_target = (link_target + if self.file_type == FILE_TYPE_SYMLINK else None) + self.checksum = checksum if self.file_type == FILE_TYPE_FILE else None + + @property + def last_modification_datetime(self): + """ + @return instance datetime for last modification time of file + """ + return datetime.fromtimestamp(self.mtime) + + def __getstate__(self): + """ + Used for serialization with pickle. + @return container content that will be serialized + """ + return dict((k, getattr(self, k)) for k in self.__slots__) + + def __setstate__(self, state): + """ + Used for deserialization with pickle. + Restores the object from serialized form. + @param state is an object created by __setstate__() method + """ + for k, value in state.items(): + setattr(self, k, value) + +class PackageCheck(object): + """ + Metadata for package concerning verification. + It contains metadata for each file installed in "files" attribute. + """ + __slots__ = ("objid", "file_checksum_type", "files") + + def __init__(self, objid, file_checksum_type, files=None): + """ + @param objid is an in of original yum package object, which is used + by server for subsequent operations on this package requested by client + """ + if files is not None and not isinstance( + files, (list, tuple, set, dict)): + raise TypeError("files must be an iterable container") + self.objid = objid + self.file_checksum_type = file_checksum_type + if not isinstance(files, dict): + self.files = OrderedDict() + if files is not None: + for file_check in sorted(files, key=lambda f: f.path): + self.files[file_check.path] = file_check + else: + for path in sorted(files): + self.files[path] = files[path] + + def __iter__(self): + return iter(self.files) + + def __len__(self): + return len(self.files) + + def __getitem__(self, filepath): + return self.files[filepath] + + def __setitem__(self, filepath, package_file): + if not isinstance(package_file, PackageFile): + raise TypeError("package_file must be a PackageFile instance") + self.files[filepath] = package_file + + def __contains__(self, fileobj): + if isinstance(fileobj, basestring): + return fileobj in self.files + elif isinstance(fileobj, PackageFile): + return fileobj.path in self.files + else: + raise TypeError("expected file path for argument") + + def __getstate__(self): + """ + Used for serialization with pickle. + @return container content that will be serialized + """ + return dict((k, getattr(self, k)) for k in self.__slots__) + + def __setstate__(self, state): + """ + Used for deserialization with pickle. + Restores the object from serialized form. + @param state is an object created by __setstate__() method + """ + for k, value in state.items(): + setattr(self, k, value) + +def pkg_checksum_type(pkg): + """ + @return integer representation of checksum type + """ + if not isinstance(pkg, yum.packages.YumAvailablePackage): + raise TypeError("pkg must be an instance of YumAvailablePackage") + if isinstance(pkg, yum.rpmsack.RPMInstalledPackage): + return pkg.hdr[rpm.RPMTAG_FILEDIGESTALGO] + return CHECKSUMTYPE_STR2NUM[pkg.yumdb_info.checksum_type.lower()] + +def make_package_check_from_db(vpkg, file_name=None): + """ + Create instance of PackageCheck from instance of + yum.packages._RPMVerifyPackage. + + :param file_name: (``str``) If not None, causes result to have just + one instance of ``PackageFile`` matching this file_name. + If it's not found in the package, ``FileNotFound`` will be raised. + :rtype (``PackageCheck``) + """ + if not isinstance(vpkg, yum.packages._RPMVerifyPackage): + raise TypeError("vpkg must be instance of" + " yum.packages._RPMVerifyPackage") + pkg = vpkg.po + + res = PackageCheck(id(pkg), pkg_checksum_type(pkg)) + files = res.files + for vpf in vpkg: + if file_name is not None and file_name != vpf.filename: + continue + files[vpf.filename] = PackageFile( + vpf.filename, + vpf.ftype, + pwd.getpwnam(vpf.user).pw_uid, + grp.getgrnam(vpf.group).gr_gid, + vpf.mode, + vpf.dev, + vpf.mtime, + vpf.size, + vpf.readlink, + vpf.digest[1] + ) + if file_name is not None: + break + if file_name is not None and len(files) < 1: + raise errors.FileNotFound('File "%s" not found in package "%s".' % ( + file_name, pkg.nevra)) + return res + diff --git a/src/software/lmi/software/yumdb/packageinfo.py b/src/software/lmi/software/yumdb/packageinfo.py new file mode 100644 index 0000000..7a40d19 --- /dev/null +++ b/src/software/lmi/software/yumdb/packageinfo.py @@ -0,0 +1,181 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# +""" +Module holding an abstraction for RPM package. +""" + +from datetime import datetime +import yum + +from lmi.software import util + +class PackageInfo(object): + """ + Container for package metadata. It represents rpm package in yum + database. It's supposed to be passed from YumWorker to YumDB client + and vice-versa. Instances of YumAvailablePackage can not be exchanged + -- results in segfaults. + + To speed up looking up of original yum package object on server, an + atribute "objid" is provided. + """ + __slots__ = ( + "objid", + "name", "epoch", "version", "release", "architecture", + 'summary', 'description', 'license', 'group', 'vendor', + "repoid", 'size', + 'installed', # boolean + 'install_time' # datetime instance + ) + + def __init__(self, objid, name, epoch, version, release, arch, **kwargs): + """ + @param objid is an in of original yum package object, which is used + by server for subsequent operations on this package requested by client + """ + self.objid = objid + self.name = name + self.epoch = epoch + self.version = version + self.release = release + self.architecture = arch + self.summary = kwargs.pop('summary', None) + self.description = kwargs.pop('description', None) + self.license = kwargs.pop('license', None) + self.group = kwargs.pop('group', None) + self.vendor = kwargs.pop('vendor', None) + self.repoid = kwargs.pop("repoid", None) + self.size = kwargs.pop('size', None) + if self.size is not None and not isinstance(self.size, (int, long)): + raise TypeError('size must be an integer') + self.installed = kwargs.pop('installed', None) + if self.installed is not None: + self.installed = bool(self.installed) + self.install_time = kwargs.pop('install_time', None) + if ( self.install_time is not None + and not isinstance(self.install_time, datetime)): + raise TypeError('install_time must be a datetime') + + # ************************************************************************* + # Properties + # ************************************************************************* + @property + def ver(self): + """Shortcut for version property.""" + return self.version + + @property + def rel(self): + """Shortcut for release property.""" + return self.release + + @property + def arch(self): + """Shortcut for architecture property.""" + return self.architecture + + @property + def nevra(self): + """@return nevra of package with epoch always present.""" + return self.get_nevra(with_epoch="ALWAYS") + + @property + def evra(self): + """@return evra of package.""" + return "%s:%s-%s.%s" % ( + self.epoch if self.epoch and self.epoch != "(none)" else "0", + self.version, + self.release, + self.architecture) + + @property + def key_props(self): + """ + @return package properties as dictionary, + that uniquelly identify package in database + """ + return dict((k, getattr(self, k)) for k in ( + 'name', 'epoch', 'version', 'release', 'arch', 'repoid')) + + # ************************************************************************* + # Public methods + # ************************************************************************* + def get_nevra(self, with_epoch='NOT_ZERO'): + """ + @param with_epoch may be one of: + "NOT_ZERO" - include epoch only if it's not zero + "ALWAYS" - include epoch always + "NEVER" - do not include epoch at all + @return nevra of package + """ + return util.make_nevra(self.name, self.epoch, self.version, + self.release, self.arch, with_epoch) + + # ************************************************************************* + # Special methods + # ************************************************************************* + def __str__(self): + return self.nevra + + def __getstate__(self): + """ + Used for serialization with pickle. + @return container content that will be serialized + """ + return dict((k, getattr(self, k)) for k in self.__slots__) + + def __setstate__(self, state): + """ + Used for deserialization with pickle. + Restores the object from serialized form. + @param state is an object created by __setstate__() method + """ + for k, value in state.items(): + setattr(self, k, value) + + def __eq__(self, other): + return ( self.name == other.name + and self.version == other.version + and self.release == other.release + and self.arch == other.arch + and self.epoch == other.epoch + and ( (self.repoid is None or other.repoid is None) + or (self.repoid == other.repoid))) + +def make_package_from_db(pkg): + """ + Create instance of PackageInfo from instance of + yum.packages.YumAvailablePackage. + @return instance of PackageInfo + """ + metadata = dict((k, getattr(pkg, k)) for k in ( + 'summary', 'description', 'license', 'group', 'vendor', 'size', + 'repoid')) + if isinstance(pkg, yum.rpmsack.RPMInstalledPackage): + metadata['installed'] = True + metadata['install_time'] = datetime.fromtimestamp(pkg.installtime) + else: + metadata['installed'] = False + res = PackageInfo(id(pkg), pkg.name, pkg.epoch, pkg.version, pkg.release, + pkg.arch, **metadata) + return res + diff --git a/src/software/lmi/software/yumdb/process.py b/src/software/lmi/software/yumdb/process.py new file mode 100644 index 0000000..85abe61 --- /dev/null +++ b/src/software/lmi/software/yumdb/process.py @@ -0,0 +1,914 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# +""" +Module holding the code of separate process accessing the YUM API. +""" + +import errno +from itertools import chain +import logging +from multiprocessing import Process +import os +import Queue as TQueue # T as threaded +import sys +import time +import traceback +from urlgrabber.grabber import default_grabber +import weakref +import yum + +from lmi.software import util +from lmi.software.yumdb import errors +from lmi.software.yumdb import jobs +from lmi.software.yumdb import packageinfo +from lmi.software.yumdb import packagecheck +from lmi.software.yumdb import repository +from lmi.software.yumdb.jobmanager import JobManager +from lmi.software.yumdb.util import trace_function, setup_logging + +# ***************************************************************************** +# Constants +# ***************************************************************************** +# interval in seconds +FREE_DATABASE_TIMEOUT = 60 +LOCK_WAIT_INTERVAL = 0.5 +RPMDB_PATH = '/var/lib/rpm/Packages' +LOG = None + +# ***************************************************************************** +# Utilities +# **************************************************************************** +def _get_package_filter_function(filters): + """ + @param filters is a dictionary, where keys are package property + names and values are their desired values. + @return a function used to filter list of packages + """ + if not isinstance(filters, dict): + raise TypeError("filters must be a dictionary") + + filters = dict((k, value) for k, value in filters.items() + if value is not None) + + match = None + if "nevra" in filters: + match = util.RE_NEVRA.match(filters["nevra"]) + elif "envra" in filters: + match = util.RE_ENVRA.match(filters["envra"]) + if match is not None: + for attr in ("name", "epoch", "version", "release", "arch"): + match_attr = attr + filters[attr] = match.group(match_attr) + filters.pop('nevra', None) + filters.pop('envra', None) + elif "evra" in filters: + for prop_name in ("epoch", "version", "release", "epoch"): + filters.pop(prop_name, None) + filter_list = [] + # properties are sorted by their filtering ability + # (the most unprobable property, that can match, comes first) + for prop_name in ("evra", "name", "version", "epoch", + "release", "repoid", "arch"): + if not prop_name in filters: + continue + filter_list.append((prop_name, filters.pop(prop_name))) + def _cmp_props(pkg): + """@return True if pkg matches properies filter""" + return all(getattr(pkg, p) == v for p, v in filter_list) + return _cmp_props + +class RepoFilterSetter(object): + """ + A context manager, that will set a repository filter lasting + as long as the object itself. + """ + def __init__(self, yum_base, include_repos=None, exclude_repos=None): + if not isinstance(yum_base, yum.YumBase): + raise TypeError("yum_base must be a YumBase instance") + self._yum_base = yum_base + self._include = include_repos + self._exclude = exclude_repos + # after __enter__ this will be dictionary containing ( + # repoid, enabled) pairs + self._prev_states = None + + def __enter__(self): + self._prev_states = { r.id: r.enabled + for r in self._yum_base.repos.repos.values()} + if isinstance(self._exclude, (list, tuple, set)): + exclude = ",".join(self._exclude) + else: + exclude = self._exclude + # set of repositories, that were affected + repos = set() + if exclude: + repos.update(self._yum_base.repos.disableRepo(exclude)) + LOG.info('disabling repositories: [%s]', ", ".join(repos)) + if isinstance(self._include, (list, tuple, set)): + include = ",".join(self._include) + else: + include = self._include + if include: + affected = self._yum_base.repos.enableRepo(include) + LOG.info('enabling repositories: [%s]', ", ".join(affected)) + repos.update(affected) + for repoid, prev_enabled in self._prev_states.items(): + if ( repoid not in repos + or ( bool(prev_enabled) + is bool(self._yum_base.repos.getRepo(repoid).enabled))): + # keep only manipulated repositories + del self._prev_states[repoid] + if len(self._prev_states): + for repoid in (r for r, v in self._prev_states.items() if v): + self._yum_base.pkgSack.sacks.pop(repoid, None) + self._yum_base.repos.populateSack() + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + # restore previous repository states + if len(self._prev_states): + LOG.info('restoring repositories: [%s]', + ", ".join(self._prev_states.keys())) + for repoid, enabled in self._prev_states.items(): + repo = self._yum_base.repos.getRepo(repoid) + if enabled: + repo.enable() + else: + repo.disable() + for repoid in (r for r, v in self._prev_states.items() if not v): + self._yum_base.pkgSack.sacks.pop(repoid, None) + self._yum_base.repos.populateSack() + +# ***************************************************************************** +# Decorators +# ***************************************************************************** +def _needs_database(method): + """ + Decorator for YumWorker job handlers, that need to access the yum database. + It ensures, that database is initialized and locks it in case, that + no session is active. + """ + logged = trace_function(method) + def _wrapper(self, *args, **kwargs): + """ + Wrapper for the job handler method. + """ + created_session = False + self._init_database() #pylint: disable=W0212 + if self._session_level == 0: #pylint: disable=W0212 + self._session_level = 1 #pylint: disable=W0212 + created_session = True + self._lock_database() #pylint: disable=W0212 + try: + LOG.debug("calling job handler %s with args=(%s)", + method.__name__, + ", ".join(chain( + (str(a) for a in args), + ("%s=%s"%(k, str(v)) for k, v in kwargs.items())))) + result = logged(self, *args, **kwargs) + LOG.debug("job handler %s finished", method.__name__) + return result + finally: + if created_session is True: #pylint: disable=W0212 + self._session_level = 0 #pylint: disable=W0212 + self._unlock_database() #pylint: disable=W0212 + return _wrapper + +# ***************************************************************************** +# Classes +# ***************************************************************************** +class YumWorker(Process): + """ + The main process, that works with YUM API. It has two queues, one + for input jobs and second for results. + + Jobs are dispatched by their class names to particular handler method. + + It spawns a second thread for managing asynchronous jobs and queue + of incoming jobs. It's an instance of JobManager. + """ + + def __init__(self, + queue_in, + queue_out, + indication_manager, + yum_kwargs=None, + logging_config=None): + Process.__init__(self, name="YumWorker") + self._jobmgr = JobManager(queue_in, queue_out, indication_manager) + self._session_level = 0 + self._session_ended = False + + if yum_kwargs is None: + yum_kwargs = {} + + self._yum_kwargs = yum_kwargs + self._yum_base = None + + self._pkg_cache = None + # contains (repoid, time_stamp_of_config_file) + # plus (/repos/dir, ...) for each repo config directory + self._repodir_mtimes = {} + self._logging_config = logging_config + + # ************************************************************************* + # Private methods + # ************************************************************************* + @trace_function + def _init_database(self): + """ + Initializes yum base object, when it does no exists. + And updates the cache (when out of date). + """ + if self._yum_base is None: + LOG.info("creating YumBase with kwargs=(%s)", + ", ".join(( "%s=%s"%(k, str(v)) + for k, v in self._yum_kwargs.items()))) + self._yum_base = yum.YumBase(**self._yum_kwargs) + + @trace_function + def _free_database(self): + """ + Release the yum base object to safe memory. + """ + LOG.info("freing database") + self._pkg_cache.clear() + self._yum_base = None + + @trace_function + def _lock_database(self): + """ + Only one process is allowed to work with package database at given time. + That's why we lock it. + + Try to lock it in loop, until success. + """ + while True: + try: + LOG.info("trying to lock database - session level %d", + self._session_level) + self._yum_base.doLock() + LOG.info("successfully locked up") + break + except yum.Errors.LockError as exc: + LOG.warn("failed to lock") + if exc.errno in (errno.EPERM, errno.EACCES, errno.ENOSPC): + LOG.error("can't create lock file") + raise errors.DatabaseLockError("Can't create lock file.") + LOG.info("trying to lock again after %.1f seconds", + LOCK_WAIT_INTERVAL) + time.sleep(LOCK_WAIT_INTERVAL) + + @trace_function + def _unlock_database(self): + """ + The opposite to _lock_database() method. + """ + if self._yum_base is not None: + LOG.info("unlocking database") + self._yum_base.closeRpmDB() + self._yum_base.doUnlock() + + @trace_function + def _get_job(self): + """ + Get job from JobManager thread. + If no job comes for long time, free database to save memory. + """ + while True: + if self._session_ended and self._session_level == 0: + try: + return self._jobmgr.get_job(timeout=FREE_DATABASE_TIMEOUT) + except TQueue.Empty: + self._free_database() + self._session_ended = False + else: + return self._jobmgr.get_job() + + @trace_function + def _transform_packages(self, packages, + cache_packages=True, + flush_cache=True): + """ + Return instances of PackageInfo for each package in packages. + Cache all the packages. + @param packages list of YumAvailablePackage instances + @param cache_packages whether to update cache with packages + @param flush_cache whether to clear the cache before adding input + packages; makes sense only with cachee_packages=True + """ + if cache_packages is True and flush_cache is True: + LOG.debug("flushing package cache") + self._pkg_cache.clear() + res = [] + for orig in packages: + pkg = packageinfo.make_package_from_db(orig) + if cache_packages is True: + self._pkg_cache[pkg.objid] = orig + res.append(pkg) + return res + + @trace_function + def _cache_packages(self, packages, flush_cache=True, transform=False): + """ + Store packages in cache and return them. + @param flush_cache whether to clear the cache before adding new + packages + @param transform whether to return packages as PackageInfos + @return either list of original packages or PackageInfo instances + """ + if transform is True: + return self._transform_packages(packages, flush_cache=flush_cache) + if flush_cache is True: + LOG.debug("flushing package cache") + self._pkg_cache.clear() + for pkg in packages: + self._pkg_cache[id(pkg)] = pkg + return packages + + @trace_function + def _lookup_package(self, pkg): + """ + Lookup the original package in cache. + If it was garbage collected already, make new query to find it. + @return instance of YumAvailablePackage + """ + if not isinstance(pkg, packageinfo.PackageInfo): + raise TypeError("pkg must be instance of PackageInfo") + LOG.debug("looking up yum package %s with id=%d", + pkg, pkg.objid) + try: + result = self._pkg_cache[pkg.objid] + LOG.debug("lookup successful") + except KeyError: + LOG.warn("lookup of package %s with id=%d failed, trying" + " to query database", pkg, pkg.objid) + result = self._handle_filter_packages( + 'installed' if pkg.installed else 'available', + allow_duplicates=False, + sort=False, + transform=False, + **pkg.key_props) + if len(result) < 1: + LOG.warn("package %s not found", pkg) + raise errors.PackageNotFound( + "package %s could not be found" % pkg) + result = result[0] + return result + + @trace_function + def _clear_repository_cache(self): + """ + Clears the repository cache and their configuration directory + last modification times. + """ + if self._yum_base is not None: + for repoid in self._yum_base.repos.repos.keys(): + self._yum_base.repos.delete(repoid) + del self._yum_base.repos + del self._yum_base.pkgSack + self._repodir_mtimes.clear() + + @trace_function + def _check_repository_configs(self): + """ + Checks whether repository information is up to date with configuration + files by comparing timestamps. If not, repository cache will be + released. + """ + dirty = False + if self._repodir_mtimes: + for repodir in self._yum_base.conf.reposdir: + if ( os.path.exists(repodir) + and ( not repodir in self._repodir_mtimes + or ( os.stat(repodir).st_mtime + > self._repodir_mtimes[repodir]))): + LOG.info("repository config dir %s changed", repodir) + dirty = True + break + if not dirty: + for repo in self._yum_base.repos.repos.values(): + filename = repo.repofile + if ( not os.path.exists(filename) + or ( int(os.stat(filename).st_mtime) + > repo.repo_config_age)): + LOG.info('config file of repository "%s" changed', + repo.id) + dirty = True + break + if dirty is True: + LOG.info("repository cache is dirty, cleaning up ...") + self._clear_repository_cache() + self._yum_base.getReposFromConfig() + if dirty is True or not self._repodir_mtimes: + self._update_repodir_mtimes() + + @trace_function + def _update_repodir_mtimes(self): + """ + Updates the last modification times of repo configuration directories. + """ + assert self._yum_base is not None + for repodir in self._yum_base.conf.reposdir: + if os.path.exists(repodir): + self._repodir_mtimes[repodir] = os.stat(repodir).st_mtime + + @trace_function + def _do_work(self, job): + """ + Dispatcher of incoming jobs. Job is passed to the right handler + depending on its class. + """ + if not isinstance(job, jobs.YumJob): + raise TypeError("job must be instance of YumJob") + try: + handler = { + jobs.YumGetPackageList : self._handle_get_package_list, + jobs.YumFilterPackages : self._handle_filter_packages, + jobs.YumInstallPackage : self._handle_install_package, + jobs.YumRemovePackage : self._handle_remove_package, + jobs.YumUpdateToPackage : self._handle_update_to_package, + jobs.YumUpdatePackage : self._handle_update_package, + jobs.YumBeginSession : self._handle_begin_session, + jobs.YumEndSession : self._handle_end_session, + jobs.YumCheckPackage : self._handle_check_package, + jobs.YumCheckPackageFile : self._handle_check_package_file, + jobs.YumInstallPackageFromURI : \ + self._handle_install_package_from_uri, + jobs.YumGetRepositoryList : \ + self._handle_get_repository_list, + jobs.YumFilterRepositories : self._handle_filter_repositories, + jobs.YumSetRepositoryEnabled : \ + self._handle_set_repository_enabled + }[job.__class__] + LOG.info("processing job %s(id=%d)", + job.__class__.__name__, job.jobid) + except KeyError: + LOG.error("No handler for job \"%s\"", job.__class__.__name__) + raise errors.UnknownJob("No handler for job \"%s\"." % + job.__class__.__name__) + return handler(**job.job_kwargs) + + @trace_function + def _run_transaction(self, name): + """ + Builds and runs the yum transaction and checks for errors. + @param name of transaction used only in error description on failure + """ + LOG.info("building transaction %s", name) + (code, msgs) = self._yum_base.buildTransaction() + if code == 1: + LOG.error("building transaction %s failed: %s", + name, "\n".join(msgs)) + raise errors.TransactionBuildFailed( + "Failed to build \"%s\" transaction: %s" % ( + name, "\n".join(msgs))) + LOG.info("processing transaction %s", name) + self._yum_base.processTransaction() + self._yum_base.closeRpmDB() + + @trace_function + def _main_loop(self): + """ + This is a main loop called from run(). Jobs are handled here. + It accepts a job from input queue, handles it, + sends the result to output queue and marks the job as done. + + It is terminated, when None is received from input queue. + """ + while True: + job = self._get_job() + if job is not None: # not a terminate command + result = jobs.YumJob.RESULT_SUCCESS + try: + data = self._do_work(job) + except Exception: #pylint: disable=W0703 + result = jobs.YumJob.RESULT_ERROR + # (type, value, traceback) + data = sys.exc_info() + # traceback is not pickable - replace it with formatted + # text + data = (data[0], data[1], traceback.format_tb(data[2])) + LOG.exception("job %s failed", job) + self._jobmgr.finish_job(job, result, data) + if job is None: + LOG.info("waiting for %s to finish", self._jobmgr.name) + self._jobmgr.join() + break + + @trace_function + def _handle_begin_session(self): + """ + Handler for session begin job. + """ + self._session_level += 1 + LOG.info("beginning session level %s", self._session_level) + if self._session_level == 1: + self._init_database() + self._lock_database() + + @trace_function + def _handle_end_session(self): + """ + Handler for session end job. + """ + LOG.info("ending session level %d", self._session_level) + self._session_level = max(self._session_level - 1, 0) + if self._session_level == 0: + self._unlock_database() + self._session_ended = True + + @_needs_database + def _handle_get_package_list(self, kind, allow_duplicates, sort, + include_repos=None, exclude_repos=None, transform=True): + """ + Handler for listing packages job. + @param transform says, whether to return just a package abstractions + or original ones + @return [pkg1, pkg2, ...] + """ + if kind == 'avail_notinst': + what = 'available' + elif kind == 'available': + what = 'all' + elif kind == 'avail_reinst': + what = 'all' + else: + what = kind + with RepoFilterSetter(self._yum_base, include_repos, exclude_repos): + LOG.debug("calling YumBase.doPackageLists(%s, showdups=%s)", + what, allow_duplicates) + pkglist = self._yum_base.doPackageLists(what, + showdups=allow_duplicates) + if kind == 'all': + result = pkglist.available + pkglist.installed + elif kind == 'available': + result = pkglist.available + pkglist.reinstall_available + elif kind == 'avail_reinst': + result = pkglist.reinstall_available + else: # get installed or available + result = getattr(pkglist, what) + if sort is True: + result.sort() + LOG.debug("returning %s packages", len(result)) + return self._cache_packages(result, transform=transform) + + @_needs_database + def _handle_filter_packages(self, kind, allow_duplicates, sort, + include_repos=None, exclude_repos=None, + transform=True, **filters): + """ + Handler for filtering packages job. + @return [pkg1, pkg2, ...] + """ + pkglist = self._handle_get_package_list(kind, allow_duplicates, False, + include_repos=include_repos, exclude_repos=exclude_repos, + transform=False) + matches = _get_package_filter_function(filters) + result = [p for p in pkglist if matches(p)] + if sort is True: + result.sort() + LOG.debug("%d packages matching", len(result)) + if transform is True: + # caching has been already done by _handle_get_package_list() + result = self._transform_packages(result, cache_packages=False) + return result + + @_needs_database + def _handle_install_package(self, pkg, force=False): + """ + Handler for package installation job. + @return installed package instance + """ + if isinstance(pkg, basestring): + pkgs = self._handle_filter_packages( + 'available' if force else 'avail_notinst', + allow_duplicates=False, sort=True, + transform=False, nevra=pkg) + if len(pkgs) < 1: + raise errors.PackageNotFound('No available package matches' + ' nevra "%s".' % pkg) + elif len(pkgs) > 1: + LOG.warn('multiple packages matches nevra "%s": [%s]', + pkg, ", ".join(p.nevra for p in pkgs)) + pkg_desired = pkgs[-1] + else: + pkg_desired = self._lookup_package(pkg) + if isinstance(pkg_desired, yum.rpmsack.RPMInstalledPackage): + if force is False: + raise errors.PackageAlreadyInstalled(pkg) + action = "reinstall" + else: + action = "install" + getattr(self._yum_base, action)(pkg_desired) + self._run_transaction(action) + installed = self._handle_filter_packages("installed", False, False, + nevra=util.pkg2nevra(pkg_desired, with_epoch="ALWAYS")) + if len(installed) < 1: + raise errors.TransactionExecutionFailed( + "Failed to install desired package %s." % pkg) + return installed[0] + + @_needs_database + def _handle_remove_package(self, pkg): + """ + Handler for package removal job. + """ + if isinstance(pkg, basestring): + pkgs = self._handle_filter_packages('installed', + allow_duplicates=False, sort=False, + transform=False, nevra=pkg) + if len(pkgs) < 1: + raise errors.PackageNotFound('No available package matches' + ' nevra "%s".' % pkg) + pkg = pkgs[-1] + else: + pkg = self._lookup_package(pkg) + self._yum_base.remove(pkg) + self._run_transaction("remove") + + @_needs_database + def _handle_update_to_package(self, pkg): + """ + Handler for specific package update job. + @return package corresponding to pkg after update + """ + if isinstance(pkg, basestring): + pkgs = self._handle_filter_packages('available', + allow_duplicates=False, sort=False, + transform=False, nevra=pkg) + if len(pkgs) < 1: + raise errors.PackageNotFound('No available package matches' + ' nevra "%s".' % pkg) + pkg_desired = pkgs[-1] + else: + pkg_desired = self._lookup_package(pkg) + self._yum_base.update(update_to=True, + name=pkg_desired.name, + epoch=pkg_desired.epoch, + version=pkg_desired.version, + release=pkg_desired.release, + arch=pkg_desired.arch) + self._run_transaction("update") + installed = self._handle_filter_packages("installed", False, False, + **pkg.key_props) + if len(installed) < 1: + raise errors.TransactionExecutionFailed( + "Failed to update to desired package %s." % pkg) + return installed[0] + + @_needs_database + def _handle_update_package(self, pkg, to_epoch, to_version, to_release, + _force=False): + """ + Handler for package update job. + @return updated package instance + """ + if isinstance(pkg, basestring): + pkgs = self._handle_filter_packages('installed', + allow_duplicates=False, sort=False, + transform=False, nevra=pkg) + if len(pkgs) < 1: + raise errors.PackageNotFound('No available package matches' + ' nevra "%s".' % pkg) + pkg = pkgs[-1] + else: + pkg = self._lookup_package(pkg) + if not isinstance(pkg, yum.rpmsack.RPMInstalledPackage): + raise errors.PackageNotInstalled(pkg) + kwargs = { "name" : pkg.name, "arch" : pkg.arch } + if any(v is not None for v in (to_epoch, to_version, to_release)): + kwargs["update_to"] = True + if to_epoch: + kwargs["to_epoch"] = to_epoch + if to_version: + kwargs["to_version"] = to_version + if to_release: + kwargs["to_release"] = to_release + self._yum_base.update(**kwargs) + self._run_transaction("update") + kwargs = dict( (k[3:] if k.startswith("to_") else k, v) + for k, v in kwargs.items()) + installed = self._handle_filter_packages( + "installed", False, False, **kwargs) + if len(installed) < 1: + raise errors.TransactionExecutionFailed( + "Failed to update package %s." % pkg) + return installed[0] + + @_needs_database + def _handle_check_package(self, pkg, file_name=None): + """ + @return PackageCheck instance for requested package + """ + if isinstance(pkg, basestring): + pkgs = self._handle_filter_packages('installed', + allow_duplicates=False, sort=False, + transform=False, nevra=pkg) + if len(pkgs) < 1: + raise errors.PackageNotFound('No available package matches' + ' nevra "%s".' % pkg) + rpm = pkgs[-1] + pkg = self._transform_packages((rpm, ), cache_packages=False)[0] + else: + rpm = self._lookup_package(pkg) + if not isinstance(rpm, yum.rpmsack.RPMInstalledPackage): + raise errors.PackageNotInstalled(rpm) + vpkg = yum.packages._RPMVerifyPackage(rpm, rpm.hdr.fiFromHeader(), + packagecheck.pkg_checksum_type(rpm), [], True) + return (pkg, packagecheck.make_package_check_from_db(vpkg, + file_name=file_name)) + + @_needs_database + def _handle_check_package_file(self, pkg, file_name): + """ + @return PackageCheck instance for requested package containing + just one PackageFile instance for given ``file_name``. + """ + return self._handle_check_package(pkg, file_name) + + @_needs_database + def _handle_install_package_from_uri(self, uri, + update_only=False, force=False): + """ + @return installed PackageInfo instance + """ + try: + pkg = yum.packages.YumUrlPackage(self, + ts=self._yum_base.rpmdb.readOnlyTS(), url=uri, + ua=default_grabber) + except yum.Errors.MiscError as exc: + raise errors.PackageOpenError(uri, str(exc)) + installed = self._handle_filter_packages("installed", False, False, + nevra=util.pkg2nevra(pkg, with_epoch="ALWAYS")) + if installed and force is False: + raise errors.PackageAlreadyInstalled(pkg) + kwargs = { 'po' : pkg } + if installed: + action = 'reinstallLocal' + else: + action = 'installLocal' + kwargs = { 'updateonly' : update_only } + getattr(self._yum_base, action)(uri, **kwargs) + self._run_transaction('installLocal') + installed = self._handle_filter_packages("installed", False, False, + nevra=util.pkg2nevra(pkg, with_epoch="ALWAYS")) + if len(installed) < 1: + raise errors.TransactionExecutionFailed( + "Failed to install desired package %s." % pkg) + return installed[0] + + @_needs_database + def _handle_get_repository_list(self, kind, transform=True): + """ + @return list of yumdb.Repository instances + """ + self._check_repository_configs() + if kind == 'enabled': + repos = sorted(self._yum_base.repos.listEnabled()) + else: + repos = self._yum_base.repos.repos.values() + if kind == 'disabled': + repos = [repo for repo in repos if not repo.enabled] + repos.sort() + if transform: + repos = [repository.make_repository_from_db(r) for r in repos] + LOG.debug("returning %d repositories from %s", + len(repos), kind) + return repos + + @_needs_database + def _handle_filter_repositories(self, kind, **filters): + """ + @return list of yumdb.Repository instances -- filtered + """ + filters = dict((k, v) for k, v in filters.items() if v is not None) + if 'repoid' in filters: + self._check_repository_configs() + try: + repo = repository.make_repository_from_db( + self._yum_base.repos.getRepo(filters["repoid"])) + if ( (kind == "enabled" and not repo.enabled) + or (kind == "disabled" and repo.enabled)): + LOG.warn( + 'no such repository with id="%s"matching filters', + filters['repoid']) + return [] + LOG.debug( + "exactly one repository matching filters found") + return [repo] + except (KeyError, yum.Errors.RepoError): + LOG.warn('repository with id="%s" could not be found', + filters['repoid']) + raise errors.RepositoryNotFound(filters['repoid']) + + repos = self._handle_get_repository_list(kind, transform=False) + result = [] + for repo in repos: + # do the filtering and safe transformed repo into result + for prop, value in filters.items(): + if repository.get_prop_from_yum_repo(repo, prop) != value: + # did not pass the filter + break + else: # all properties passed + result.append(repository.make_repository_from_db(repo)) + LOG.debug("found %d repositories matching", len(result)) + return result + + @_needs_database + def _handle_set_repository_enabled(self, repo, enable): + """ + @return previous enabled state + """ + self._check_repository_configs() + if isinstance(repo, repository.Repository): + repoid = repo.repoid + else: + repoid = repo + try: + repo = self._yum_base.repos.getRepo(repoid) + except (KeyError, yum.Errors.RepoError): + raise errors.RepositoryNotFound(repoid) + res = repo.enabled + try: + if enable ^ res: + if enable is True: + LOG.info("enabling repository %s" % repo) + repo.enable() + else: + LOG.info("disabling repository %s" % repo) + repo.disable() + try: + yum.config.writeRawRepoFile(repo, only=["enabled"]) + except Exception as exc: + raise errors.RepositoryChangeError( + 'failed to modify repository "%s": %s - %s' % ( + repo, exc.__class__.__name__, str(exc))) + else: + LOG.info("no change for repo %s", repo) + except yum.Errors.RepoError as exc: + raise errors.RepositoryChangeError( + 'failed to modify repository "%s": %s' % (repo, str(exc))) + return res + + # ************************************************************************* + # Public properties + # ************************************************************************* + @property + def uplink(self): + """ + @return input queue for jobs + """ + return self._jobmgr.queue_in + + @property + def downlink(self): + """ + @return output queue for job results + """ + return self._jobmgr.queue_out + + # ************************************************************************* + # Public methods + # ************************************************************************* + def run(self): + """ + Thread's entry point. After initial setup it calls _main_loop(). + """ + if self._logging_config is not None: + setup_logging(self._logging_config) + global LOG + LOG = logging.getLogger(__name__) + LOG.info("running as pid=%d", self.pid) + self._jobmgr.start() + LOG.info("started %s as thread %s", + self._jobmgr.name, self._jobmgr.ident) + self._pkg_cache = weakref.WeakValueDictionary() + + # This allows the code, that can be run both from broker and + # YumWorker, to check, whether it's called by this process. + from lmi.software.yumdb import YumDB + YumDB.RUNNING_UNDER_CIMOM_PROCESS = False + + self._main_loop() + LOG.info("terminating") + diff --git a/src/software/lmi/software/yumdb/repository.py b/src/software/lmi/software/yumdb/repository.py new file mode 100644 index 0000000..758ae21 --- /dev/null +++ b/src/software/lmi/software/yumdb/repository.py @@ -0,0 +1,202 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# +""" +Module holding an abstraction for YUM repository. +""" + +from datetime import datetime +import logging +import yum +import yum.Errors + +# maps names of Repository properties to their corresponding property +# names in YumRepository object +PROPERTY_NAME_MAP = { + "repoid" : "id", + "base_urls" : "baseurl", + "config_file" : "repofile", + "cost" : "cost", + "enabled" : "enabled", + "gpg_check" : "gpgcheck", + "last_edit" : "repo_config_age", + "mirror_list" : "mirrorlist", + "mirror_urls" : "mirrorurls", + "name" : "name", + "pkg_dir" : "pkgdir", + "ready" : "ready", + "repo_gpg_check" : "repo_gpgcheck", + "timeout" : "timeout" +} + +def get_prop_from_yum_repo(repo, prop_name): + """ + @param prop_name is one Repository properties + @return value of property from object of YumRepository + """ + if not isinstance(repo, yum.yumRepo.YumRepository): + raise TypeError("repo must be in instance of yum.yumRepo.YumRepository") + if prop_name in PROPERTY_NAME_MAP: + val = getattr(repo, PROPERTY_NAME_MAP[prop_name]) + if prop_name == "last_edit": + val = datetime.fromtimestamp(val) + elif prop_name == "mirror_urls" and not repo.mirrorlist: + val = None + elif prop_name == "ready": + val = val() + elif prop_name in {"arch", "basearch", "releasever"}: + val = repo.yumvar[prop_name] + elif prop_name in {"revision", "last_update"}: + if repo.enabled and repo.repoXML: + md = repo.repoXML + if prop_name == "last_update": + val = datetime.fromtimestamp(md.timestamp) + elif prop_name == "revision": + val = long(md.revision) + else: + val = getattr(repo.repoXML, prop_name) + else: + val = None + elif prop_name == "pkg_count": + # this needs populated sack: ydb.repos.populateSack(repo.id) + val = len(repo.sack) + else: + raise ValueError('Unknown repository property: "%s"' % prop_name) + return val + +class Repository(object): + """ + Container for repository metadata. It represents yum repository. + It's supposed to be passed from YumWorker to YumDB client and + vice-versa. + """ + __slots__ = ( + "objid", # [int] id of python object on server process + "repoid", # [string] repository id name + # (name of config file) + + "arch", # [string] architecture of packages + "basearch", # [string] base system architecture + "base_urls", # [list] base urls as strings + #"cache", + #"cache_dir", + "name", # [string] repository descriptive name + "config_file", # [string] file path to corresponding + # config file + "cost", # [int] cost of repository + "enabled", # [boolean] whether repo is enabled + "gpg_check", # [boolean] whether to check gpg signature + #"metadata_expire", # how long are metadata considered valid + "last_edit", # datetime of last config modification + "last_update", # datetime of last change of repository + # on server + "mirror_list", # url of mirrorlist, or None + "mirror_urls", # list of urls obtained from mirrorlist or None + #"persist_dir", # + #"pkg_count", # number of packages in directory + "pkg_dir", # directory with packages + #"proxy", # boolean saying whether this is a proxy + "ready", # boolean saying, whether it's ready for use + "releasever", # version of targeted distribution + "repo_gpg_check", # [boolean] whether to check gpg + # signarues of data + "revision", + "timeout", # timeout for requests + ) + + def __init__(self, objid, repoid, arch, basearch, base_urls, + config_file, cost, enabled, gpg_check, last_edit, last_update, + name, pkg_dir, ready, releasever, repo_gpg_check, revision, + timeout, mirror_list=None, mirror_urls=None): + for arg in ('last_edit', 'last_update'): + if ( locals()[arg] is not None + and not isinstance(locals()[arg], datetime)): + raise TypeError("%s must be an instance of datetime" % arg) + if not isinstance(timeout, float): + raise TypeError("timeout must be a float") + for arg in ('cost', 'revision'): + if ( locals()[arg] is not None + and not isinstance(locals()[arg], (int, long))): + raise TypeError("%s must be an integer" % arg) + self.objid = objid + self.repoid = repoid + self.arch = arch + self.basearch = basearch + self.base_urls = list(base_urls) + self.config_file = config_file + self.cost = cost + self.enabled = bool(enabled) + self.gpg_check = bool(gpg_check) + self.last_edit = last_edit + self.last_update = last_update + self.mirror_list = "" if not mirror_list else mirror_list + self.mirror_urls = [] if not mirror_urls else list(mirror_urls) + self.name = name + #self.pkg_count = pkg_count + self.pkg_dir = pkg_dir + self.ready = bool(ready) + self.releasever = releasever + self.repo_gpg_check = bool(repo_gpg_check) + self.revision = revision + self.timeout = timeout + + def __str__(self): + return self.repoid + + def __getstate__(self): + """ + Used for serialization with pickle. + @return container content that will be serialized + """ + return dict((k, getattr(self, k)) for k in self.__slots__) + + def __setstate__(self, state): + """ + Used for deserialization with pickle. + Restores the object from serialized form. + @param state is an object created by __setstate__() method + """ + for k, value in state.items(): + setattr(self, k, value) + +def make_repository_from_db(repo): + """ + Create instance of Repository from instance of yum.yumRepo.YumRepository + @return instance of Repository + """ + if not isinstance(repo, yum.yumRepo.YumRepository): + raise TypeError("repo must be in instance of yum.yumRepo.YumRepository") + metadata = {} + for prop_name in Repository.__slots__[1:]: + try: + metadata[prop_name] = get_prop_from_yum_repo(repo, prop_name) + except yum.Errors.RepoError as exc: + # some properties can cause error (like requesting ready) + logging.getLogger(__name__).warn( + 'failed to get property "%s" of repo "%s": %s' % ( + prop_name, repo.name, exc)) + if prop_name == "ready": + metadata[prop_name] = False + continue + + res = Repository(id(repo), **metadata) + return res + diff --git a/src/software/lmi/software/yumdb/util.py b/src/software/lmi/software/yumdb/util.py new file mode 100644 index 0000000..18fcf6b --- /dev/null +++ b/src/software/lmi/software/yumdb/util.py @@ -0,0 +1,163 @@ +# Software Management Providers +# +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# + +""" +Common utilities meant to be used only be ``yumdb`` subpackage. +""" + +from itertools import chain +import inspect +import logging +import os + +from lmi.common import cmpi_logging + +class DispatchingFormatter: + """ + Formatter class for logging module. It allows to predefine different + format string for paricular module names. + """ + def __init__(self, formatters, default): + """ + *format* in parameters description can be either ``string`` or + another formatter object. + + :param formatters (``dict``) Mapping of module names to *format*. + :param default Default *format*. + """ + for k, formatter in formatters.items(): + if isinstance(formatter, basestring): + formatters[k] = logging.Formatter(formatter) + self._formatters = formatters + if isinstance(default, basestring): + default = logging.Formatter(default) + self._default_formatter = default + + def format(self, record): + """ + Interface for logging module. + """ + formatter = self._formatters.get(record.name, self._default_formatter) + return formatter.format(record) + +# ***************************************************************************** +# Decorators +# ***************************************************************************** +def trace_function(func): + """ + Decorator for logging entries and exits of function or method. + """ + if not inspect.ismethod(func) and not inspect.isfunction(func): + raise TypeError("func must be a function") + + def _print_value(val): + """ + Used here for printing function arguments. Shortens the output + string, if that would be too long. + """ + if isinstance(val, list): + if len(val) < 2: + return str(val) + else: + return "[%s, ...]" % _print_value(val[0]) + return str(val) + + logger = logging.getLogger(__name__+'.trace_function') + module = func.__module__.split('.')[-1] + lineno = inspect.currentframe().f_back.f_lineno + + def _wrapper(self, *args, **kwargs): + """ + Wrapper for function or method, that does the logging. + """ + if logger.isEnabledFor(logging.DEBUG): + frm = inspect.currentframe() + logargs = { + "caller_file" : os.path.basename(os.path.splitext( + frm.f_back.f_code.co_filename)[0]), + "caller_lineno" : frm.f_back.f_lineno, + "module" : module, + "func" : func.__name__, + "lineno" : lineno, + "action" : "entering", + "args" : ", ".join(chain( + (_print_value(a) for a in args), + ( "%s=%s"%(k, _print_value(v)) + for k, v in kwargs.items()))) + } + + if not logargs["args"]: + logargs["args"] = "" + else: + logargs["args"] = " with args=(%s)" % logargs["args"] + logger.debug("%(caller_file)s:%(caller_lineno)d - %(action)s" + " %(module)s:%(func)s:%(lineno)d%(args)s" , logargs) + try: + result = func(self, *args, **kwargs) + if logger.isEnabledFor(logging.DEBUG): + logargs["action"] = "exiting" + logger.debug("%(caller_file)s:%(caller_lineno)d - %(action)s" + " %(module)s:%(func)s:%(lineno)d", logargs) + except Exception as exc: + if logger.isEnabledFor(logging.DEBUG): + logargs['action'] = 'exiting' + logargs['error'] = str(exc) + logger.debug("%(caller_file)s:%(caller_lineno)d - %(action)s" + " %(module)s:%(func)s:%(lineno)d with error: %(error)s", + logargs) + raise + return result + + return _wrapper + +def setup_logging(config): + """ + This is meant to be used by ``YumWorker`` process to setup logging + independent of what providers are using. Unfortunately ``YumWorker`` + can not use the same facilities as the rest of program, because + logging is done through *broker*. + """ + try: + logging.config.dictConfig(config) + cmpi_logging.logger = logging.getLogger('lmi.software.yumdb') + except Exception: #pylint: disable=W0703 + # logging is not set up but client expects us to work + # all messages are dumped to /dev/null + logging.config.dictConfig({ + 'version' : 1, + 'disable_existing_loggers' : True, + 'handlers': { + 'null' : { + 'class': 'logging.handlers.FileHandler', + 'level': 'CRITICAL', + 'filename': '/dev/null' + } + }, + 'loggers' : { + 'root' : { + 'level': 'CRITICAL', + 'handlers' : ['null'], + 'propagate' : False + } + } + }) + + |