summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMichal Minar <miminar@redhat.com>2013-03-20 12:59:49 +0100
committerMichal Minar <miminar@redhat.com>2013-03-20 12:59:49 +0100
commiteed3cc9b4b2a78e238ece6dab185e7c76ab13142 (patch)
tree8df1d6b3a3a60e42a27882e3d8003de8cddfa92c /src
parent19ab3372fe708be3e937df5d1ed6945ac813b51c (diff)
downloadopenlmi-providers-eed3cc9b4b2a78e238ece6dab185e7c76ab13142.tar.gz
openlmi-providers-eed3cc9b4b2a78e238ece6dab185e7c76ab13142.tar.xz
openlmi-providers-eed3cc9b4b2a78e238ece6dab185e7c76ab13142.zip
added new providers, allowed for asynchronous jobs
new providers: LMI_SoftwareInstallationJob LMI_SoftwareInstallationService
Diffstat (limited to 'src')
-rw-r--r--src/software/openlmi/software/LMI_HostedSoftwareCollection.py8
-rw-r--r--src/software/openlmi/software/LMI_HostedSoftwareIdentityResource.py13
-rw-r--r--src/software/openlmi/software/LMI_InstalledSoftwareIdentity.py20
-rw-r--r--src/software/openlmi/software/LMI_MemberOfSoftwareCollection.py39
-rw-r--r--src/software/openlmi/software/LMI_ResourceForSoftwareIdentity.py317
-rw-r--r--src/software/openlmi/software/LMI_SoftwareIdentity.py10
-rw-r--r--src/software/openlmi/software/LMI_SoftwareIdentityResource.py22
-rw-r--r--src/software/openlmi/software/LMI_SoftwareInstallationJob.py375
-rw-r--r--src/software/openlmi/software/LMI_SoftwareInstallationService.py783
-rw-r--r--src/software/openlmi/software/LMI_SystemSoftwareCollection.py7
-rw-r--r--src/software/openlmi/software/cimom_entry.py12
-rw-r--r--src/software/openlmi/software/core/Error.py455
-rw-r--r--src/software/openlmi/software/core/Identity.py (renamed from src/software/openlmi/software/core/SoftwareIdentity.py)53
-rw-r--r--src/software/openlmi/software/core/IdentityResource.py (renamed from src/software/openlmi/software/core/SoftwareIdentityResource.py)0
-rw-r--r--src/software/openlmi/software/core/InstallationJob.py579
-rw-r--r--src/software/openlmi/software/core/InstallationService.py729
-rw-r--r--src/software/openlmi/software/core/SystemCollection.py (renamed from src/software/openlmi/software/core/SystemSoftwareCollection.py)0
-rw-r--r--src/software/openlmi/software/yumdb/__init__.py311
-rw-r--r--src/software/openlmi/software/yumdb/errors.py56
-rw-r--r--src/software/openlmi/software/yumdb/jobmanager.py413
-rw-r--r--src/software/openlmi/software/yumdb/jobs.py343
-rw-r--r--src/software/openlmi/software/yumdb/process.py399
-rw-r--r--src/software/openlmi/software/yumdb/util.py97
-rw-r--r--src/software/test/package.py13
-rw-r--r--src/software/test/rpmcache.py12
25 files changed, 4699 insertions, 367 deletions
diff --git a/src/software/openlmi/software/LMI_HostedSoftwareCollection.py b/src/software/openlmi/software/LMI_HostedSoftwareCollection.py
index ad8b6bf..2783d95 100644
--- a/src/software/openlmi/software/LMI_HostedSoftwareCollection.py
+++ b/src/software/openlmi/software/LMI_HostedSoftwareCollection.py
@@ -25,7 +25,7 @@ import pywbem
from pywbem.cim_provider2 import CIMProvider2
from openlmi.common import cmpi_logging
-from openlmi.software.core import ComputerSystem, SystemSoftwareCollection
+from openlmi.software.core import ComputerSystem, SystemCollection
class LMI_HostedSoftwareCollection(CIMProvider2):
"""Instrument the CIM class LMI_HostedSoftwareCollection
@@ -69,12 +69,12 @@ class LMI_HostedSoftwareCollection(CIMProvider2):
raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
"Missing Antecedent key property!")
ComputerSystem.check_path_property(env, model, "Antecedent")
- SystemSoftwareCollection.check_path_property(env, model, "Dependent")
+ SystemCollection.check_path_property(env, model, "Dependent")
model.path.update({"Antecedent":None, "Dependent":None})
model["Antecedent"] = ComputerSystem.get_path()
- model["Dependent"] = SystemSoftwareCollection.get_path()
+ model["Dependent"] = SystemCollection.get_path()
return model
@@ -107,7 +107,7 @@ class LMI_HostedSoftwareCollection(CIMProvider2):
model.path.update({'Dependent': None, 'Antecedent': None})
model["Antecedent"] = ComputerSystem.get_path()
- model["Dependent"] = SystemSoftwareCollection.get_path()
+ model["Dependent"] = SystemCollection.get_path()
yield model
diff --git a/src/software/openlmi/software/LMI_HostedSoftwareIdentityResource.py b/src/software/openlmi/software/LMI_HostedSoftwareIdentityResource.py
index 09ab5d3..05f7725 100644
--- a/src/software/openlmi/software/LMI_HostedSoftwareIdentityResource.py
+++ b/src/software/openlmi/software/LMI_HostedSoftwareIdentityResource.py
@@ -31,7 +31,7 @@ from pywbem.cim_provider2 import CIMProvider2
from openlmi.common import cmpi_logging
from openlmi.software.core import generate_references
from openlmi.software.core import ComputerSystem
-from openlmi.software.core import SoftwareIdentityResource
+from openlmi.software.core import IdentityResource
from openlmi.software.yumdb import YumDB
@cmpi_logging.trace_function
@@ -46,8 +46,7 @@ def generate_system_referents(env, object_name, model, _keys_only):
host=model.path.host)
model["Antecedent"] = ComputerSystem.get_path()
for repo in YumDB.get_instance().get_repository_list('all'):
- model["Dependent"] = SoftwareIdentityResource.repo2model(
- repo, model=repomodel)
+ model["Dependent"] = IdentityResource.repo2model(repo, model=repomodel)
yield model
@cmpi_logging.trace_function
@@ -55,10 +54,10 @@ def generate_repository_referents(env, object_name, model, _keys_only):
"""
Handler for referents enumeration request.
"""
- repo = SoftwareIdentityResource.object_path2repo(
+ repo = IdentityResource.object_path2repo(
env, object_name, kind='all')
model["Antecedent"] = ComputerSystem.get_path()
- model["Dependent"] = SoftwareIdentityResource.repo2model(repo)
+ model["Dependent"] = IdentityResource.repo2model(repo)
yield model
class LMI_HostedSoftwareIdentityResource(CIMProvider2):
@@ -113,7 +112,7 @@ class LMI_HostedSoftwareIdentityResource(CIMProvider2):
raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
'Unknown repository "%s".' % repoid)
model["Antecedent"] = ComputerSystem.get_path()
- model["Dependent"] = SoftwareIdentityResource.repo2model(repos[0])
+ model["Dependent"] = IdentityResource.repo2model(repos[0])
return model
@@ -152,7 +151,7 @@ class LMI_HostedSoftwareIdentityResource(CIMProvider2):
classname='LMI_SoftwareIdentityResource',
namespace='root/cimv2')
for repo in repolist:
- model["Dependent"] = SoftwareIdentityResource.repo2model(
+ model["Dependent"] = IdentityResource.repo2model(
repo, model=repomodel)
yield model
diff --git a/src/software/openlmi/software/LMI_InstalledSoftwareIdentity.py b/src/software/openlmi/software/LMI_InstalledSoftwareIdentity.py
index b4c1b6f..b349afd 100644
--- a/src/software/openlmi/software/LMI_InstalledSoftwareIdentity.py
+++ b/src/software/openlmi/software/LMI_InstalledSoftwareIdentity.py
@@ -31,7 +31,7 @@ from pywbem.cim_provider2 import CIMProvider2
from openlmi.common import cmpi_logging
from openlmi.software.core import generate_references
from openlmi.software.core import ComputerSystem
-from openlmi.software.core import SoftwareIdentity
+from openlmi.software.core import Identity
from openlmi.software.yumdb import YumDB
@cmpi_logging.trace_function
@@ -48,7 +48,7 @@ def generate_system_referents(env, object_name, model, _keys_only):
model["System"] = ComputerSystem.get_path()
with YumDB.get_instance() as ydb:
for pkg_info in ydb.get_package_list('installed', sort=True):
- model["InstalledSoftware"] = SoftwareIdentity.pkg2model(
+ model["InstalledSoftware"] = Identity.pkg2model(
pkg_info, model=pkg_model)
yield model
@@ -57,8 +57,8 @@ def generate_package_referents(_env, object_name, model, _keys_only):
"""
Handler for referents enumeration request.
"""
- pkg_info = SoftwareIdentity.object_path2pkg(object_name, kind="installed")
- model['InstalledSoftware'] = SoftwareIdentity.pkg2model(pkg_info)
+ pkg_info = Identity.object_path2pkg(object_name, kind="installed")
+ model['InstalledSoftware'] = Identity.pkg2model(pkg_info)
model["System"] = ComputerSystem.get_path()
yield model
@@ -108,9 +108,9 @@ class LMI_InstalledSoftwareIdentity(CIMProvider2):
model["System"] = model.path["System"] = ComputerSystem.get_path()
with YumDB.get_instance():
- pkg_info = SoftwareIdentity.object_path2pkg(
+ pkg_info = Identity.object_path2pkg(
model['InstalledSoftware'], kind='installed')
- model['InstalledSoftware'] = SoftwareIdentity.pkg2model(pkg_info)
+ model['InstalledSoftware'] = Identity.pkg2model(pkg_info)
return model
@cmpi_logging.trace_method
@@ -147,7 +147,7 @@ class LMI_InstalledSoftwareIdentity(CIMProvider2):
with YumDB.get_instance() as yb:
pl = yb.get_package_list('installed', sort=True)
for pkg in pl:
- model['InstalledSoftware'] = SoftwareIdentity.pkg2model(
+ model['InstalledSoftware'] = Identity.pkg2model(
pkg, model=inst_model)
yield model
@@ -191,7 +191,7 @@ class LMI_InstalledSoftwareIdentity(CIMProvider2):
ComputerSystem.check_path_property(env, instance, 'System')
with YumDB.get_instance() as ydb:
- pkg_info = SoftwareIdentity.object_path2pkg(
+ pkg_info = Identity.object_path2pkg(
instance["InstalledSoftware"], kind="all")
if pkg_info.installed:
raise pywbem.CIMError(pywbem.CIM_ERR_ALREADY_EXISTS,
@@ -199,7 +199,7 @@ class LMI_InstalledSoftwareIdentity(CIMProvider2):
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"] = SoftwareIdentity.pkg2model(installed)
+ instance["InstalledSoftware"] = Identity.pkg2model(installed)
return instance
@cmpi_logging.trace_method
@@ -232,7 +232,7 @@ class LMI_InstalledSoftwareIdentity(CIMProvider2):
ComputerSystem.check_path_property(env, instance_name, 'System')
with YumDB.get_instance() as ydb:
- pkg_info = SoftwareIdentity.object_path2pkg(
+ pkg_info = Identity.object_path2pkg(
instance_name["InstalledSoftware"], kind="installed")
cmpi_logging.logger.info('removing package "%s"' % pkg_info)
ydb.remove_package(pkg_info)
diff --git a/src/software/openlmi/software/LMI_MemberOfSoftwareCollection.py b/src/software/openlmi/software/LMI_MemberOfSoftwareCollection.py
index e9f73b5..80a10fe 100644
--- a/src/software/openlmi/software/LMI_MemberOfSoftwareCollection.py
+++ b/src/software/openlmi/software/LMI_MemberOfSoftwareCollection.py
@@ -26,8 +26,8 @@ from pywbem.cim_provider2 import CIMProvider2
from openlmi.common import cmpi_logging
from openlmi.software.core import generate_references
-from openlmi.software.core import SystemSoftwareCollection
-from openlmi.software.core import SoftwareIdentity
+from openlmi.software.core import SystemCollection
+from openlmi.software.core import Identity
from openlmi.software.yumdb import YumDB
@cmpi_logging.trace_function
@@ -35,17 +35,16 @@ def generate_collection_referents(env, object_name, model, _keys_only):
"""
Handler for referents enumeration request.
"""
- SystemSoftwareCollection.check_path(env, object_name, "collection")
+ SystemCollection.check_path(env, object_name, "collection")
pkg_model = pywbem.CIMInstanceName(
classname='LMI_SoftwareIdentity',
namespace="root/cimv2",
host=model.path.host)
- model["Collection"] = SystemSoftwareCollection.get_path()
+ 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"] = SoftwareIdentity.pkg2model(
- pkg_info, model=pkg_model)
+ model["Member"] = Identity.pkg2model(pkg_info, model=pkg_model)
yield model
@cmpi_logging.trace_function
@@ -54,15 +53,14 @@ def generate_member_referents(_env, object_name, model, _keys_only):
Handler for referents enumeration request.
"""
try:
- pkg_info = SoftwareIdentity.object_path2pkg(
- object_name, kind="available")
- model['Member'] = SoftwareIdentity.pkg2model(pkg_info)
- model["Collection"] = SystemSoftwareCollection.get_path()
+ 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"])
+ msg = msg % (' with InstanceID="%s"'%object_name["InstanceID"])
else:
msg = msg % ""
raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, msg)
@@ -73,9 +71,8 @@ def generate_member_referents(_env, object_name, model, _keys_only):
class LMI_MemberOfSoftwareCollection(CIMProvider2):
"""Instrument the CIM class LMI_MemberOfSoftwareCollection
- LMI_MemberOfSoftwareCollection is an aggregation used to establish membership
- of ManagedElements in a Collection.
-
+ LMI_MemberOfSoftwareCollection is an aggregation used to establish
+ membership of ManagedElements in a Collection.
"""
def __init__(self, _env):
@@ -105,7 +102,7 @@ class LMI_MemberOfSoftwareCollection(CIMProvider2):
CIM_ERR_FAILED (some other unspecified error occurred)
"""
- SystemSoftwareCollection.check_path_property(env, model, "Collection")
+ SystemCollection.check_path_property(env, model, "Collection")
if not 'Member' in model:
raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
@@ -114,11 +111,10 @@ class LMI_MemberOfSoftwareCollection(CIMProvider2):
raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
"Expected object path for Member!")
- model['Collection'] = SystemSoftwareCollection.get_path()
+ model['Collection'] = SystemCollection.get_path()
with YumDB.get_instance():
- pkg_info = SoftwareIdentity.object_path2pkg(
- model['Member'], 'available')
- model['Member'] = SoftwareIdentity.pkg2model(pkg_info)
+ pkg_info = Identity.object_path2pkg(model['Member'], 'available')
+ model['Member'] = Identity.pkg2model(pkg_info)
return model
@cmpi_logging.trace_method
@@ -148,7 +144,7 @@ class LMI_MemberOfSoftwareCollection(CIMProvider2):
# we set property values on the model.
model.path.update({'Member': None, 'Collection': None})
- model['Collection'] = SystemSoftwareCollection.get_path()
+ model['Collection'] = SystemCollection.get_path()
member_model = pywbem.CIMInstanceName(
classname="LMI_SoftwareIdentity",
namespace="root/cimv2")
@@ -157,8 +153,7 @@ class LMI_MemberOfSoftwareCollection(CIMProvider2):
allow_duplicates=True,
sort=True)
for pkg in pl:
- model['Member'] = SoftwareIdentity.pkg2model(
- pkg, model=member_model)
+ model['Member'] = Identity.pkg2model(pkg, model=member_model)
yield model
@cmpi_logging.trace_method
diff --git a/src/software/openlmi/software/LMI_ResourceForSoftwareIdentity.py b/src/software/openlmi/software/LMI_ResourceForSoftwareIdentity.py
new file mode 100644
index 0000000..3eedc23
--- /dev/null
+++ b/src/software/openlmi/software/LMI_ResourceForSoftwareIdentity.py
@@ -0,0 +1,317 @@
+# -*- encoding: utf-8 -*-
+# Software Management Providers
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""Python Provider for LMI_ResourceForSoftwareIdentity
+
+Instruments the CIM class LMI_ResourceForSoftwareIdentity
+"""
+
+import pywbem
+from pywbem.cim_provider2 import CIMProvider2
+
+from openlmi.common import cmpi_logging
+from openlmi.software.core import generate_references
+from openlmi.software.core import Identity
+from openlmi.software.core import IdentityResource
+from openlmi.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.repo]
+ except KeyError:
+ cmpi_logging.logger.error('unknown or disabled repository'
+ ' "%s" for package "%s"' % (pkg, pkg.repo))
+ 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/openlmi/software/LMI_SoftwareIdentity.py b/src/software/openlmi/software/LMI_SoftwareIdentity.py
index b97ca25..066ef8f 100644
--- a/src/software/openlmi/software/LMI_SoftwareIdentity.py
+++ b/src/software/openlmi/software/LMI_SoftwareIdentity.py
@@ -26,7 +26,7 @@ import pywbem
from pywbem.cim_provider2 import CIMProvider2
from openlmi.common import cmpi_logging
-from openlmi.software.core import SoftwareIdentity
+from openlmi.software.core import Identity
from openlmi.software.yumdb import YumDB
class LMI_SoftwareIdentity(CIMProvider2):
@@ -96,9 +96,8 @@ class LMI_SoftwareIdentity(CIMProvider2):
"""
with YumDB.get_instance():
- pkg_info = SoftwareIdentity.object_path2pkg(model.path, 'all')
- return SoftwareIdentity.pkg2model(
- pkg_info, keys_only=False, model=model)
+ 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):
@@ -132,8 +131,7 @@ class LMI_SoftwareIdentity(CIMProvider2):
pkglist = ydb.get_package_list(
'all', allow_duplicates=True, sort=True)
for pkg_info in pkglist:
- yield SoftwareIdentity.pkg2model(
- pkg_info, keys_only=keys_only, model=model)
+ yield Identity.pkg2model(pkg_info, keys_only=keys_only, model=model)
@cmpi_logging.trace_method
def set_instance(self, env, instance, modify_existing):
diff --git a/src/software/openlmi/software/LMI_SoftwareIdentityResource.py b/src/software/openlmi/software/LMI_SoftwareIdentityResource.py
index 6acec42..7797367 100644
--- a/src/software/openlmi/software/LMI_SoftwareIdentityResource.py
+++ b/src/software/openlmi/software/LMI_SoftwareIdentityResource.py
@@ -29,7 +29,7 @@ import pywbem
from pywbem.cim_provider2 import CIMProvider2
from openlmi.common import cmpi_logging
-from openlmi.software.core import SoftwareIdentityResource
+from openlmi.software.core import IdentityResource
from openlmi.software.yumdb import YumDB, errors
class LMI_SoftwareIdentityResource(CIMProvider2):
@@ -77,9 +77,9 @@ class LMI_SoftwareIdentityResource(CIMProvider2):
"""
- repo = SoftwareIdentityResource.object_path2repo(
+ repo = IdentityResource.object_path2repo(
env, model.path, kind='all')
- return SoftwareIdentityResource.repo2model(
+ return IdentityResource.repo2model(
repo, keys_only=False, model=model)
@cmpi_logging.trace_method
@@ -113,7 +113,7 @@ class LMI_SoftwareIdentityResource(CIMProvider2):
repolist = YumDB.get_instance().get_repository_list('all')
for repo in repolist:
- yield SoftwareIdentityResource.repo2model(
+ yield IdentityResource.repo2model(
repo, keys_only=keys_only, model=model)
@cmpi_logging.trace_method
@@ -240,22 +240,22 @@ class LMI_SoftwareIdentityResource(CIMProvider2):
"""
out_params = []
if param_timeoutperiod is not None:
- return ( SoftwareIdentityResource.Values.RequestStateChange. \
+ return ( IdentityResource.Values.RequestStateChange. \
Use_of_Timeout_Parameter_Not_Supported
, out_params)
if param_requestedstate not in {
- SoftwareIdentityResource.Values.RequestStateChange. \
+ IdentityResource.Values.RequestStateChange. \
RequestedState.Enabled,
- SoftwareIdentityResource.Values.RequestStateChange. \
+ IdentityResource.Values.RequestStateChange. \
RequestedState.Disabled }:
- return ( SoftwareIdentityResource.Values.RequestStateChange. \
+ return ( IdentityResource.Values.RequestStateChange. \
Invalid_State_Transition
, out_params)
with YumDB.get_instance() as ydb:
- repo = SoftwareIdentityResource.object_path2repo(env,
+ repo = IdentityResource.object_path2repo(env,
object_name, 'all')
- enable = param_requestedstate == SoftwareIdentityResource.Values. \
+ enable = param_requestedstate == IdentityResource.Values. \
RequestStateChange.RequestedState.Enabled
cmpi_logging.logger.info("%s repository %s" % ("enabling"
if enable else "disabling", repo))
@@ -271,7 +271,7 @@ class LMI_SoftwareIdentityResource(CIMProvider2):
cmpi_logging.logger.info(msg % (repo,
"enabled" if enable else "disabled"))
- rval = SoftwareIdentityResource.Values.RequestStateChange. \
+ rval = IdentityResource.Values.RequestStateChange. \
Completed_with_No_Error
return (rval, out_params)
diff --git a/src/software/openlmi/software/LMI_SoftwareInstallationJob.py b/src/software/openlmi/software/LMI_SoftwareInstallationJob.py
new file mode 100644
index 0000000..df62f0d
--- /dev/null
+++ b/src/software/openlmi/software/LMI_SoftwareInstallationJob.py
@@ -0,0 +1,375 @@
+"""Python Provider for LMI_SoftwareInstallationJob
+
+Instruments the CIM class LMI_SoftwareInstallationJob
+
+"""
+
+import inspect
+import pywbem
+from pywbem.cim_provider2 import CIMProvider2
+
+from openlmi.common import cmpi_logging
+from openlmi.software.core import Error, InstallationJob
+from openlmi.software.yumdb import errors, YumDB
+
+class LMI_SoftwareInstallationJob(CIMProvider2):
+ """Instrument the CIM class LMI_SoftwareInstallationJob
+
+ 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 = InstallationJob.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 = InstallationJob.object_path2job(model.path)
+ return InstallationJob.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})
+
+ for job in YumDB.get_instance().get_job_list():
+ yield InstallationJob.job2model(job, keys_only, 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 InstallationJob.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 = InstallationJob.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_SoftwareInstallationJob.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 = InstallationJob.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_SoftwareInstallationJob.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 = InstallationJob.object_path2job(object_name)
+ out_params = []
+ if job.state == job.EXCEPTION:
+ errortup = job.result_data
+ callargs = inspect.getcallargs(Error.make_instance)
+ if issubclass(errortup[0],
+ (errors.RepositoryNotFound, errors.PackageNotFound)):
+ callargs['status_code'] = Error.Values. \
+ CIMStatusCode.CIM_ERR_NOT_FOUND
+ if issubclass(errortup[0], errors.PackageNotFound):
+ callargs['status_code_description'] = \
+ "Package not found"
+ else:
+ callargs['status_code_description'] = \
+ "Repository not found"
+ elif issubclass(errortup[0], errors.PackageAlreadyInstalled):
+ callargs['status_code'] = Error.Values. \
+ CIMStatusCode.CIM_ERR_ALREADY_EXISTS
+ callargs['message'] = getattr(errortup[1], 'message',
+ str(errortup[1]))
+ value = [Error.make_instance(**callargs)]
+ param = pywbem.CIMParameter('Errors', type='instance',
+ is_array=True, array_size=1, value=value)
+ 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_SoftwareInstallationJob.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_SoftwareInstallationJob.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)
+ """
+ raise pywbem.CIMError(pywbem.CIM_ERR_METHOD_NOT_AVAILABLE)
diff --git a/src/software/openlmi/software/LMI_SoftwareInstallationService.py b/src/software/openlmi/software/LMI_SoftwareInstallationService.py
new file mode 100644
index 0000000..224e923
--- /dev/null
+++ b/src/software/openlmi/software/LMI_SoftwareInstallationService.py
@@ -0,0 +1,783 @@
+# Software Management Providers
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""Python Provider for LMI_SoftwareInstallationService
+
+Instruments the CIM class LMI_SoftwareInstallationService
+
+"""
+
+import pywbem
+from pywbem.cim_provider2 import CIMProvider2
+
+from openlmi.common import cmpi_logging
+from openlmi.software.core import InstallationJob
+from openlmi.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: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, "uri", param_uri,
+ param_target, None, param_installoptions,
+ param_installoptionsvalues)
+ rval = self.values.InstallFromURI.Job_Completed_with_No_Error
+ out_params[0].value = InstallationJob.job2model(jobid)
+ 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, "identity", param_source,
+ param_target, param_collection, param_installoptions,
+ param_installoptionsvalues)
+ rval = self.values.InstallFromSoftwareIdentity. \
+ Job_Completed_with_No_Error
+ out_params[0].value = InstallationJob.job2model(jobid)
+ 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)
diff --git a/src/software/openlmi/software/LMI_SystemSoftwareCollection.py b/src/software/openlmi/software/LMI_SystemSoftwareCollection.py
index ec2382b..f1a5123 100644
--- a/src/software/openlmi/software/LMI_SystemSoftwareCollection.py
+++ b/src/software/openlmi/software/LMI_SystemSoftwareCollection.py
@@ -24,7 +24,7 @@ import pywbem
from pywbem.cim_provider2 import CIMProvider2
from openlmi.common import cmpi_logging
-from openlmi.software.core import SystemSoftwareCollection
+from openlmi.software.core import SystemCollection
class LMI_SystemSoftwareCollection(CIMProvider2):
"""Instrument the CIM class LMI_SystemSoftwareCollection
@@ -75,8 +75,7 @@ class LMI_SystemSoftwareCollection(CIMProvider2):
if not 'InstanceID' in model:
raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
"Missing InstanceID key property")
- if model['InstanceID'] != \
- SystemSoftwareCollection.get_path()['InstanceID']:
+ if model['InstanceID'] != SystemCollection.get_path()['InstanceID']:
raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
"No such instance.")
@@ -113,7 +112,7 @@ class LMI_SystemSoftwareCollection(CIMProvider2):
# we set property values on the model.
model.path.update({'InstanceID': None})
- model['InstanceID'] = SystemSoftwareCollection.get_path()["InstanceID"]
+ model['InstanceID'] = SystemCollection.get_path()["InstanceID"]
if keys_only is False:
yield self.get_instance(env, model)
else:
diff --git a/src/software/openlmi/software/cimom_entry.py b/src/software/openlmi/software/cimom_entry.py
index a598fb1..640b86e 100644
--- a/src/software/openlmi/software/cimom_entry.py
+++ b/src/software/openlmi/software/cimom_entry.py
@@ -40,6 +40,10 @@ from openlmi.software.LMI_ResourceForSoftwareIdentity import \
LMI_ResourceForSoftwareIdentity
from openlmi.software.LMI_HostedSoftwareIdentityResource import \
LMI_HostedSoftwareIdentityResource
+from openlmi.software.LMI_SoftwareInstallationService import \
+ LMI_SoftwareInstallationService
+from openlmi.software.LMI_SoftwareInstallationJob import \
+ LMI_SoftwareInstallationJob
from openlmi.software.yumdb import YumDB
def get_providers(env):
@@ -55,9 +59,13 @@ def get_providers(env):
"LMI_MemberOfSoftwareCollection" : LMI_MemberOfSoftwareCollection(env),
"LMI_InstalledSoftwareIdentity" : LMI_InstalledSoftwareIdentity(env),
"LMI_SoftwareIdentityResource" : LMI_SoftwareIdentityResource(env),
- "LMI_ResourceForSoftwareIdentity" : LMI_ResourceForSoftwareIdentity(env),
+ "LMI_ResourceForSoftwareIdentity" :
+ LMI_ResourceForSoftwareIdentity(env),
"LMI_HostedSoftwareIdentityResource" :
- LMI_HostedSoftwareIdentityResource(env)
+ LMI_HostedSoftwareIdentityResource(env),
+ "LMI_SoftwareInstallationService" : \
+ LMI_SoftwareInstallationService(env),
+ "LMI_SoftwareInstallationJob" : LMI_SoftwareInstallationJob(env)
}
return providers
diff --git a/src/software/openlmi/software/core/Error.py b/src/software/openlmi/software/core/Error.py
new file mode 100644
index 0000000..03653b2
--- /dev/null
+++ b/src/software/openlmi/software/core/Error.py
@@ -0,0 +1,455 @@
+# -*- encoding: utf-8 -*-
+# Software Management Providers
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Just a common functionality related to class CIM_Error.
+"""
+
+import pywbem
+
+from openlmi.software.core import InstallationService
+
+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'
+ }
+
+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:
+ 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)
+ #inst['PerceivedSeverity'] = pywbem.Uint16(perceived_severity)
+ 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/openlmi/software/core/SoftwareIdentity.py b/src/software/openlmi/software/core/Identity.py
index edf1268..1668586 100644
--- a/src/software/openlmi/software/core/SoftwareIdentity.py
+++ b/src/software/openlmi/software/core/Identity.py
@@ -159,6 +159,36 @@ class Values(object):
# DMTF_Reserved = ..
# Vendor_Reserved = 0x8000..
+
+@cmpi_logging.trace_function
+def object_path2nevra(op, with_epoch='NOT_ZERO'):
+ 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:pkg:"):
+ raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
+ "InstanceID must start with LMI:PKG: prefix.")
+ instid = instid[len("LMI:PKG:"):]
+ 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('ver'),
+ match.group('rel'),
+ match.group('arch'), with_epoch=with_epoch)
+
@cmpi_logging.trace_function
def object_path2pkg(op,
kind='installed',
@@ -178,37 +208,20 @@ def object_path2pkg(op,
"""
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 "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:pkg:"):
- raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
- "InstanceID must start with LMI:PKG: prefix.")
- instid = instid[len("LMI:PKG:"):]
- 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)
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,
- **util.nevra2filter(match))
+ 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 package nevra=\"%s\".' %
- instid)
+ 'No matching package found for InstanceID=\"%s\".' %
+ op["InstanceID"])
@cmpi_logging.trace_function
def pkg2model(pkg_info, keys_only=True, model=None):
diff --git a/src/software/openlmi/software/core/SoftwareIdentityResource.py b/src/software/openlmi/software/core/IdentityResource.py
index 722c29b..722c29b 100644
--- a/src/software/openlmi/software/core/SoftwareIdentityResource.py
+++ b/src/software/openlmi/software/core/IdentityResource.py
diff --git a/src/software/openlmi/software/core/InstallationJob.py b/src/software/openlmi/software/core/InstallationJob.py
new file mode 100644
index 0000000..b082331
--- /dev/null
+++ b/src/software/openlmi/software/core/InstallationJob.py
@@ -0,0 +1,579 @@
+# -*- encoding: utf-8 -*-
+# Software Management Providers
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Just a common functionality related to SoftwareInstallationJob.
+"""
+
+from datetime import datetime, timedelta
+import pywbem
+import time
+
+from openlmi.common import cmpi_logging
+from openlmi.software.yumdb import errors, jobs
+from openlmi.software.yumdb import YumDB
+
+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.YumCheckPackage :
+ 'Software package check job %(jobid)d for "%(pkg)s".',
+ jobs.YumInstallPackageFromURI :
+ 'Software package installation job %(jobid)d from uri: "%(uri)s".',
+}
+
+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'
+ }
+
+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'
+ if 'method_name' in job.metadata:
+ model['MethodName'] = job.metadata['method_name']
+ else:
+ model["MethodName"] = pywbem.CIMProperty('MethodName',
+ type='string', value=None)
+ model['Name'] = job.metadata['name']
+ 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, keys_only=True, model=None):
+ """
+ @param job can either be jobs.YumAsyncJob or integer
+ @param model if not None, will be filled with data, otherwise
+ a new instance of CIMInstance or CIMObjectPath is created
+ """
+ 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 model is None:
+ model = pywbem.CIMInstanceName("LMI_SoftwareInstallationJob",
+ namespace="root/cimv2")
+ if not keys_only:
+ model = pywbem.CIMInstance("LMI_SoftwareInstallationJob",
+ path=model)
+
+ jobid = job.jobid if isinstance(job, jobs.YumAsyncJob) else job
+ model['InstanceID'] = 'LMI:SoftwareInstallationJob:%d' % 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, "Wrong keys.")
+ instid = op['InstanceID']
+ if not instid.lower().startswith("lmi:softwareinstallationjob:"):
+ raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
+ "InstanceID must start with LMI:SoftwareInstallationJob: prefix.")
+ try:
+ instid = int(instid[len("LMI:SoftwareInstallationJob:"):])
+ except ValueError:
+ raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
+ 'Invalid InstanceID "%s"' % instid)
+ try:
+ return YumDB.get_instance().get_job(instid)
+ except errors.JobNotFound:
+ raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
+ 'Not 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)
+
diff --git a/src/software/openlmi/software/core/InstallationService.py b/src/software/openlmi/software/core/InstallationService.py
new file mode 100644
index 0000000..a2f197a
--- /dev/null
+++ b/src/software/openlmi/software/core/InstallationService.py
@@ -0,0 +1,729 @@
+# -*- encoding: utf-8 -*-
+# Software Management Providers
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Just a common functionality related to LMI_SoftwareInstallationService
+provider.
+"""
+
+import pywbem
+
+from openlmi.common import cmpi_logging
+from openlmi.software.core import ComputerSystem
+from openlmi.software.core import Identity
+from openlmi.software.core import SystemCollection
+from openlmi.software.yumdb import errors
+from openlmi.software.yumdb import YumDB
+
+#UNSUPPORTED_INSTALL_OPTIONS = {
+# { "Defer_target_system_reset", "Force_installation",
+# "Reboot", "Password", "Log", "Silent_Mode", "Administrative_Mode",
+# "Schedule_Install_At", "DMTF_Reserved" }
+
+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 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:SoftwareInstallationService"
+ return op
+
+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 sublass 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
+
+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 _check_target_and_collection(env, src_type, 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 src_type == "identity" else
+ "Missing Target parameter.")
+
+def _install_or_remove_check_params(
+ env, src_type, 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
+ """
+ values = Values.InstallFromSoftwareIdentity
+ supported_options = values.InstallOptions.supported.copy()
+ if src_type == "uri":
+ supported_options.remove(values.InstallOptions.Uninstall)
+
+ if not src_type in {"uri", "identity"}:
+ raise ValueError('uri must be one of {"uri", "identity"}')
+ if not source:
+ raise InstallationError(values.Unspecified_Error,
+ "Missing %s parameter." % (
+ "URI" if src_type == "uri" else "Source"))
+ 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, src_type, 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)
+
+def install_or_remove_package(env, src_type, source, target, collection,
+ install_options,
+ install_options_values):
+ """
+ @param src_type is one of {"uri", "identity"}
+ """
+ values = Values.InstallFromSoftwareIdentity
+ (action, force, repair) = _install_or_remove_check_params(
+ env, src_type, source, target, collection,
+ install_options, install_options_values)
+ try:
+ ydb = YumDB.get_instance()
+ if action == values.InstallOptions.Uninstall:
+ nevra = Identity.object_path2nevra(
+ source, with_epoch='ALWAYS')
+ cmpi_logging.logger.info('removing package %s', nevra)
+ jobid = ydb.remove_package(nevra, async=True)
+ else:
+ update = action == values.InstallOptions.Update
+ if src_type == "uri":
+ cmpi_logging.logger.info('%s package "%s"',
+ 'updating' if update else 'installing', source)
+ jobid = ydb.install_package_from_uri(
+ source, update_only=update, force=force or repair,
+ async=True)
+ else: # software identity
+ nevra = Identity.object_path2nevra(
+ source, with_epoch='ALWAYS')
+ if update:
+ jobid = ydb.update_package(nevra,
+ force=force or repair, async=True)
+ else:
+ jobid = ydb.install_package(
+ nevra, force=force or repair, async=True)
+ cmpi_logging.logger.info('installation job %s for pkg "%s"'
+ ' enqueued', jobid, nevra)
+ return jobid
+ except (pywbem.CIMError, errors.InvalidURI) as exc:
+ cmpi_logging.logger.error('failed to install/remove package "%s"'
+ ' from %s: %s', source, src_type, str(exc))
+ raise InstallationError(values.Unspecified_Error, str(exc))
+
diff --git a/src/software/openlmi/software/core/SystemSoftwareCollection.py b/src/software/openlmi/software/core/SystemCollection.py
index 0f59f3b..0f59f3b 100644
--- a/src/software/openlmi/software/core/SystemSoftwareCollection.py
+++ b/src/software/openlmi/software/core/SystemCollection.py
diff --git a/src/software/openlmi/software/yumdb/__init__.py b/src/software/openlmi/software/yumdb/__init__.py
index 512602a..c9fc0c5 100644
--- a/src/software/openlmi/software/yumdb/__init__.py
+++ b/src/software/openlmi/software/yumdb/__init__.py
@@ -33,10 +33,12 @@ only accessor to yum api.
"""
import errno
+import inspect
import os
import re
import time
-from multiprocessing import Process, JoinableQueue, Queue #pylint: disable=W0404
+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
@@ -49,21 +51,29 @@ from openlmi.software.yumdb.packagecheck import PackageFile
from openlmi.software.yumdb.packagecheck import PackageCheck
from openlmi.software.yumdb.process import YumWorker
from openlmi.software.yumdb.repository import Repository
+from openlmi.software.yumdb.util import DispatchingFormatter
from openlmi.software.util import get_signal_name, singletonmixin
# 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 = 120
+MAX_JOB_WAIT_TIME = 30
# this may be used as an argument to YumWorker
YUM_WORKER_DEBUG_LOGGING_CONFIG = {
"version" : 1,
"formatters": {
+ # this is a message format for logging function/method calls
+ # it's manually set up in YumWorker's init method
"default": {
- "format" : "%(asctime)s %(levelname)s:%(module)s:"
- "%(funcName)s:%(lineno)d - %(message)s"
- }
+ "()": DispatchingFormatter,
+ "formatters" : {
+ "openlmi.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" : {
@@ -71,30 +81,121 @@ YUM_WORKER_DEBUG_LOGGING_CONFIG = {
"filename" : "/var/tmp/YumWorker.log",
"level" : "DEBUG",
"formatter": "default",
- }
+ },
},
- "root": {
- "level": "DEBUG",
- "handlers" : ["file"]
- }
+ "loggers" : {
+ "root": {
+ "level": "ERROR",
+ "handlers" : ["file"]
+ },
+ "openlmi.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, tuple):
+ 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: job %s(id=%s) failed with error %s: %s",
- job.__class__.__name__, job.jobid,
- reply[0].__name__, str(reply[1]))
+ "YumDB: %s failed with error %s: %s",
+ job, reply.result_data[0].__name__, str(reply.result_data[1]))
cmpi_logging.logger.trace_warn(
- "YumDB: job %s(id=%s) exception traceback:\n%s%s: %s",
- job.__class__.__name__, job.jobid, "".join(reply[2]),
- reply[0].__name__, str(reply[1]))
- reply[1].tb_printed = True
- raise reply[1]
+ "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)
+ frm = inspect.currentframe()
+ method_name = None
+ while ( frm is not None
+ and ( not 'self' in frm.f_locals
+ or not isinstance(frm.f_locals['self'], CIMProvider2))):
+ frm = frm.f_back
+ if frm is not None:
+ prov = frm.f_locals['self']
+ method_name = frm.f_code.co_name.lower()
+ if method_name.startswith('cim_method_'):
+ method_name = method_name[len('cim_method_'):]
+ if hasattr(prov, 'values'):
+ lowertocorrectcase = {
+ k.lower(): k for k in prov.values.__dict__ }
+ try:
+ method_name = lowertocorrectcase[method_name]
+ except KeyError:
+ pass
+ if method_name is not None:
+ job.metadata['method_name'] = method_name
+ 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):
"""
@@ -122,12 +223,15 @@ class YumDB(singletonmixin.Singleton):
# this is to inform Singleton, that __init__ should be called only once
ignoreSubsequent = True
- def __init__(self, *args, **kwargs): #pylint: disable=W0231
+ @cmpi_logging.trace_method
+ def __init__(self, **kwargs): #pylint: disable=W0231
"""
All arguments are passed to yum.YumBase constructor.
"""
self._process = None
- self._yum_args = (args, kwargs)
+ if kwargs is None:
+ kwargs = {}
+ self._yum_kwargs = kwargs
self._session_lock = threading.RLock()
self._session_level = 0
@@ -194,21 +298,21 @@ class YumDB(singletonmixin.Singleton):
cmpi_logging.logger.debug("[jobid=%d] blocking on downlink queue",
job.jobid)
try:
- jobid, reply = self._worker.downlink.get(
+ jobout = self._worker.downlink.get(
block=True, timeout=MAX_JOB_WAIT_TIME)
- if jobid == job.jobid:
+ if jobout.jobid == job.jobid:
with self._reply_lock:
cmpi_logging.logger.debug(
"[jobid=%d] received desired reply", job.jobid)
self._expected.remove(job.jobid)
if len(self._expected):
self._reply_cond.notify()
- return reply
+ return jobout
else:
# this should not happen
cmpi_logging.logger.error("[jobid=%d] received reply"
" for another thread (jobid=%d)",
- job.jobid, jobid)
+ job.jobid, jobout.jobid)
except TQueue.Empty:
cmpi_logging.logger.warn("[jobid=%d] wait for job reply timeout"
"(%d seconds) occured", job.jobid, MAX_JOB_WAIT_TIME)
@@ -231,6 +335,8 @@ class YumDB(singletonmixin.Singleton):
"""
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 not in self._expected:
@@ -259,12 +365,10 @@ class YumDB(singletonmixin.Singleton):
(exception_type, exception_value, formated_traceback_as_string)
@return reply
"""
- cmpi_logging.logger.trace_verbose("YumDB: doing %s(id=%s) job",
- job.__class__.__name__, job.jobid)
+ 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(id=%s) done",
- job.__class__.__name__, job.jobid)
+ cmpi_logging.logger.trace_verbose("YumDB: job %s done", job.jobid)
return reply
@property
@@ -274,11 +378,11 @@ class YumDB(singletonmixin.Singleton):
"""
if self._process is None:
cmpi_logging.logger.trace_info("YumDB: starting YumWorker")
- uplink = JoinableQueue()
+ uplink = Queue()
downlink = Queue()
self._process = YumWorker(uplink, downlink,
- yum_args=self._yum_args[0], yum_kwargs=self._yum_args[1])
- #logging_config=YUM_WORKER_DEBUG_LOGGING_CONFIG)
+ 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)
@@ -327,7 +431,6 @@ class YumDB(singletonmixin.Singleton):
if self._process is not None:
cmpi_logging.logger.info('YumDB: terminating YumWorker')
self._process.uplink.put(None) # terminating command
- self._process.uplink.join()
self._process.join()
cmpi_logging.logger.info('YumDB: YumWorker terminated')
self._process = None
@@ -335,7 +438,10 @@ class YumDB(singletonmixin.Singleton):
cmpi_logging.logger.warn("YunDB: clean_up called, when process"
" not initialized!")
- @cmpi_logging.trace_method
+ # *************************************************************************
+ # Jobs with simple results
+ # *************************************************************************
+ @job_request()
def get_package_list(self, kind,
allow_duplicates=False,
sort=False,
@@ -351,7 +457,7 @@ class YumDB(singletonmixin.Singleton):
kind, allow_duplicates=allow_duplicates, sort=sort,
include_repos=include_repos, exclude_repos=exclude_repos))
- @cmpi_logging.trace_method
+ @job_request()
def filter_packages(self, kind,
allow_duplicates=False,
sort=False,
@@ -367,75 +473,132 @@ class YumDB(singletonmixin.Singleton):
include_repos=include_repos, exclude_repos=exclude_repos,
**filters))
- @cmpi_logging.trace_method
- def install_package(self, pkg):
+ @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):
"""
Install package.
@param pkg is an instance of PackageInfo obtained with
- get_package_list() or filter_packages(), which must be not installed
+ 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(jobs.YumInstallPackage(pkg))
+ return self._do_job(_make_async_job(jobs.YumInstallPackage,
+ pkg, force=force, async=async))
- @cmpi_logging.trace_method
- def remove_package(self, pkg):
+ @job_request(async=True)
+ def remove_package(self, pkg, async=False):
"""
@param pkg is an instance of PackageInfo obtained with
get_package_list() or filter_packages(), which must be installed
"""
- return self._do_job(jobs.YumRemovePackage(pkg))
+ return self._do_job(_make_async_job(jobs.YumRemovePackage,
+ pkg, async=async))
- @cmpi_logging.trace_method
- def update_to_package(self, desired_pkg):
+ @job_request(async=True)
+ def update_to_package(self, desired_pkg, async=False):
"""
@param desired_pkg is an instance of PackageInfo,
which must be available
"""
- return self._do_job(jobs.YumUpdateToPackage(desired_pkg))
+ return self._do_job(_make_async_job(jobs.YumUpdateToPackage,
+ desired_pkg, async=async))
- @cmpi_logging.trace_method
+ @job_request(async=True)
def update_package(self, pkg,
+ async=False,
to_epoch=None,
to_version=None,
- to_release=None):
+ to_release=None,
+ force=False):
"""
@param pkg is an instance of PackageInfo, which must be installed
The other parameters filter candidate available packages for update.
"""
- return self._do_job(jobs.YumUpdatePackage(
- pkg, to_epoch, to_version, to_release))
+ return self._do_job(_make_async_job(jobs.YumUpdatePackage,
+ pkg, async, to_epoch, to_version, to_release, force=force))
- @cmpi_logging.trace_method
- def check_package(self, pkg):
+ @job_request(async=True)
+ def check_package(self, pkg, async=False):
"""
@param pkg is an instance of PackageInfo representing installed package
@return instance of yumdb.PackageCheck
"""
- return self._do_job(jobs.YumCheckPackage(pkg))
+ return self._do_job(_make_async_job(jobs.YumCheckPackage,
+ pkg, async=async))
- @cmpi_logging.trace_method
- def get_repository_list(self, kind):
+ @job_request(async=True)
+ def install_package_from_uri(self, uri,
+ async=False, update_only=False, force=False):
"""
- @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
+ Install package from uri.
+ @param uri is either remote url or local path.
"""
- return self._do_job(jobs.YumGetRepositoryList(kind))
+ return self._do_job(jobs.YumInstallPackageFromURI(
+ uri, async, update_only, force=force))
- @cmpi_logging.trace_method
- 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))
-
- @cmpi_logging.trace_method
- def set_repository_enabled(self, repoid, enable):
- """
- Enable or disable repository.
- @param enable is a boolean
- """
- return self._do_job(jobs.YumSetRepositoryEnabled(repoid, enable))
+ # *************************************************************************
+ # Control of asynchronous jobs
+ # *************************************************************************
+ @job_request()
+ def get_job(self, jobid):
+ return self._do_job(jobs.YumJobGet(jobid))
+
+ @job_request()
+ def get_job_list(self):
+ return self._do_job(jobs.YumJobGetList())
+
+ @job_request()
+ def get_job_by_name(self, name):
+ return self._do_job(jobs.YumJobGetByName(name))
+
+ @job_request()
+ def set_job_priority(self, jobid, priority):
+ return self._do_job(jobs.YumJobSetPriority(jobid, priority))
+
+ @job_request()
+ def update_job(self, jobid, **kwargs):
+ return self._do_job(jobs.YumJobUpdate(jobid, **kwargs))
+
+ @job_request()
+ def reschedule_job(self, jobid,
+ delete_on_completion, time_before_removal):
+ return self._do_job(jobs.YumJobReschedule(jobid,
+ delete_on_completion, time_before_removal))
+
+ @job_request()
+ def delete_job(self, jobid):
+ return self._do_job(jobs.YumJobDelete(jobid))
+
+ @job_request()
+ def terminate_job(self, jobid):
+ return self._do_job(jobs.YumJobTerminate(jobid))
diff --git a/src/software/openlmi/software/yumdb/errors.py b/src/software/openlmi/software/yumdb/errors.py
index 35dad14..2d9f0e5 100644
--- a/src/software/openlmi/software/yumdb/errors.py
+++ b/src/software/openlmi/software/yumdb/errors.py
@@ -23,29 +23,69 @@
Exceptions raisable by YumWorker.
"""
-class DatabaseLockError(Exception):
+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(Exception):
+
+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 UnknownJob(Exception):
- """Raised, when no handler is available for given job on worker."""
+
+class PackageError(YumDBError):
pass
-class PackageNotFound(Exception):
+class PackageNotFound(PackageError):
"""Raised, when requested package could not be found."""
pass
-class RepositoryNotFound(Exception):
+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 RepositoryError(YumDBError):
+ pass
+class RepositoryNotFound(RepositoryError):
"""Raised, when requested repository cound not be found."""
def __init__(self, repoid):
- Exception.__init__(self, "No such repository: %s" % repoid)
-class RepositoryChangeError(Exception):
+ RepositoryError.__init__(self, "No such repository: %s" % repoid)
+class RepositoryChangeError(RepositoryError):
"""Raised, when modification of repository failed."""
pass
+class JobError(YumDBError):
+ 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):
+ pass
+class JobControlError(JobError):
+ pass
+class JobNotFound(JobControlError):
+ def __init__(self, target):
+ JobControlError.__init__(self, "job %s could not be found" % target)
+class InvalidJobState(JobControlError):
+ pass
+
diff --git a/src/software/openlmi/software/yumdb/jobmanager.py b/src/software/openlmi/software/yumdb/jobmanager.py
new file mode 100644
index 0000000..515bb44
--- /dev/null
+++ b/src/software/openlmi/software/yumdb/jobmanager.py
@@ -0,0 +1,413 @@
+# Software Management Providers
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Authors: Michal Minar <miminar@redhat.com>
+#
+"""
+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.
+"""
+import heapq
+import inspect
+import logging
+import Queue
+import sys
+import threading
+import time
+import traceback
+
+from openlmi.software.yumdb import errors, jobs
+from openlmi.software.yumdb.util import trace_function
+
+# Minimum time to keep asynchronous job in cache after completion. In seconds.
+MINIMUM_TIME_BEFORE_REMOVAL = 10
+
+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 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):
+ threading.Thread.__init__(self, name="JobManager")
+ self._queue_in = queue_in
+ self._queue_out = queue_out
+ 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:
+ self._async_jobs[job.jobid] = job
+ 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)
+
+ # *************************************************************************
+ # 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:
+ job.update(priority=new_priority)
+ if job in self._job_queue:
+ heapq.heapify(self._job_queue)
+ 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
+ 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
+ return job
+
+ @job_handler()
+ def _handle_update(self, job, data): #pylint: disable=R0201
+ """
+ Updates any job metadata.
+ """
+ job.update(**data)
+ 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)
+ job.finish(result=job.RESULT_TERMINATED)
+ 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)
+ 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)
+ 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:
+ 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/openlmi/software/yumdb/jobs.py b/src/software/openlmi/software/yumdb/jobs.py
index 437f123..cb36f43 100644
--- a/src/software/openlmi/software/yumdb/jobs.py
+++ b/src/software/openlmi/software/yumdb/jobs.py
@@ -23,25 +23,45 @@
Define job classes representing kinds of jobs of worker process.
"""
+import os
import threading
+import time
+import yum
from openlmi.software import util
+from openlmi.software.yumdb import errors
from openlmi.software.yumdb.packageinfo import PackageInfo
from openlmi.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 at least a jobid attribute, that must be unique for
+ 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_name - name of provider's method, that lead to creation of job
"""
- __slots__ = ('jobid', )
+ __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():
"""
@@ -54,35 +74,261 @@ class YumJob(object): #pylint: disable=R0903
YumJob._JOB_ID += 1
return val
- def __init__(self):
+ @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 represented be methods with arguments.
- Those can be obtained from job by calling this property.
+ 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:
+ if ( not slot in kwargs
+ and not slot in cls.handle_ignore_job_props()):
kwargs[slot] = getattr(self, slot)
cls = cls.__bases__[0]
- kwargs.pop('jobid', None)
+ 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
- ret.update(jobid=self.jobid)
+ 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
+ """Update job's metadata."""
+ __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
@@ -195,27 +441,42 @@ class YumFilterPackages(YumGetPackageList): #pylint: disable=R0903
self.envra = envra
self.repoid = repoid
-class YumSpecificPackageJob(YumJob): #pylint: disable=R0903
+class YumSpecificPackageJob(YumAsyncJob): #pylint: disable=R0903
"""
- Abstract job taking instance of yumdb.PackageInfo as argument.
+ Abstract job taking instance of yumdb.PackageInfo as argument or
+ package's nevra.
Arguments:
- pkg - plays different role depending on job subclass
+ pkg - plays different role depending on job subclass;
+ can also be a nevra
"""
__slots__ = ('pkg', )
- def __init__(self, pkg):
- if not isinstance(pkg, PackageInfo):
- raise TypeError("pkg must be instance of PackageInfo")
- YumJob.__init__(self)
+ 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.
"""
- pass
+ __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
"""
@@ -240,21 +501,27 @@ class YumUpdatePackage(YumSpecificPackageJob): #pylint: disable=R0903
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')
+ __slots__ = ('to_epoch', 'to_version', 'to_release', 'force')
- def __init__(self, pkg,
- to_epoch=None, to_version=None, to_release=None):
+ 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)
+ 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
"""
@@ -262,11 +529,41 @@ class YumCheckPackage(YumSpecificPackageJob): #pylint: disable=R0903
Worker replies with new instance of yumdb.PackageCheck.
"""
- def __init__(self, pkg):
- YumSpecificPackageJob.__init__(self, pkg)
+ def __init__(self, pkg, async=False, metadata=None):
+ YumSpecificPackageJob.__init__(self, pkg, async=async,
+ metadata=metadata)
if not pkg.installed:
raise ValueError("package must be installed to check it")
+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.
@@ -288,7 +585,7 @@ class YumGetRepositoryList(YumJob): #pylint: disable=R0903
", ".join(self.SUPPORTED_KINDS))
self.kind = kind
-class YumFilterRepositories(YumGetRepositoryList):
+class YumFilterRepositories(YumGetRepositoryList): #pylint: disable=R0903
"""
Job similar to YumGetRepositoryList, but allowing to specify
filter on packages.
diff --git a/src/software/openlmi/software/yumdb/process.py b/src/software/openlmi/software/yumdb/process.py
index 96c471e..e77b360 100644
--- a/src/software/openlmi/software/yumdb/process.py
+++ b/src/software/openlmi/software/yumdb/process.py
@@ -24,7 +24,6 @@ Module holding the code of separate process accessing the YUM API.
"""
import errno
-import inspect
from itertools import chain
import logging
from multiprocessing import Process
@@ -33,6 +32,7 @@ import Queue as TQueue # T as threaded
import sys
import time
import traceback
+from urlgrabber.grabber import default_grabber
import weakref
import yum
@@ -42,6 +42,8 @@ from openlmi.software.yumdb import jobs
from openlmi.software.yumdb import packageinfo
from openlmi.software.yumdb import packagecheck
from openlmi.software.yumdb import repository
+from openlmi.software.yumdb.jobmanager import JobManager
+from openlmi.software.yumdb.util import trace_function, setup_logging
# *****************************************************************************
# Constants
@@ -50,19 +52,11 @@ from openlmi.software.yumdb import repository
FREE_DATABASE_TIMEOUT = 60
LOCK_WAIT_INTERVAL = 0.5
RPMDB_PATH = '/var/lib/rpm/Packages'
+LOG = None
# *****************************************************************************
# Utilities
# ****************************************************************************
-def _logger():
- """
- Returns logger for this module, when first needed.
- @return logger specific for this process
- """
- if not hasattr(_logger, "logger"):
- _logger.logger = logging.getLogger(__name__)
- return _logger.logger
-
def _get_package_filter_function(filters):
"""
@param filters is a dictionary, where keys are package property
@@ -130,14 +124,14 @@ class RepoFilterSetter(object):
repos = set()
if exclude:
repos.update(self._yum_base.repos.disableRepo(exclude))
- _logger().info('disabling repositories: [%s]', ", ".join(repos))
+ 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)
- _logger().info('enabling repositories: [%s]', ", ".join(affected))
+ LOG.info('enabling repositories: [%s]', ", ".join(affected))
repos.update(affected)
for repoid, prev_enabled in self._prev_states.items():
if ( repoid not in repos
@@ -154,7 +148,7 @@ class RepoFilterSetter(object):
def __exit__(self, exc_type, exc_value, exc_tb):
# restore previous repository states
if len(self._prev_states):
- _logger().info('restoring repositories: [%s]',
+ 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)
@@ -169,51 +163,13 @@ class RepoFilterSetter(object):
# *****************************************************************************
# Decorators
# *****************************************************************************
-def _trace_function(func):
- """
- Decorator for logging entries and exits of function or method.
- """
- if not inspect.ismethod(func) and not inspect.isfunction(func):
- raise TypeError("func must be a function")
-
- def _print_value(val):
- """
- Used here for printing function arguments. Shortens the output
- string, if that would be too long.
- """
- if isinstance(val, list):
- if len(val) < 2:
- return str(val)
- else:
- return "[%s, ...]" % _print_value(val[0])
- return str(val)
-
- def _wrapper(self, *args, **kwargs):
- """
- Wrapper for function or method, that does the logging.
- """
- ftype = "method" if inspect.ismethod(func) else "function"
- parent = ( func.im_class.__name__ + "."
- if inspect.ismethod(func) else "")
-
- _logger().debug("entering %s %s%s with args=(%s)",
- ftype, parent, func.__name__,
- ", ".join(chain(
- (_print_value(a) for a in args),
- ( "%s=%s"%(k, _print_value(v))
- for k, v in kwargs.items()))))
- result = func(self, *args, **kwargs)
- _logger().debug("exiting %s %s%s", ftype, parent, func.__name__)
- return result
-
- return _wrapper
-
def _needs_database(method):
"""
Decorator for YumWorker job handlers, that need to access the yum database.
It ensures, that database is initialized and locks it in case, that
no session is active.
"""
+ logged = trace_function(method)
def _wrapper(self, *args, **kwargs):
"""
Wrapper for the job handler method.
@@ -222,18 +178,14 @@ def _needs_database(method):
if self._session_level == 0: #pylint: disable=W0212
self._lock_database() #pylint: disable=W0212
try:
- _logger().debug("calling job handler %s with args=(%s)",
+ 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 = method(self, *args, **kwargs)
- _logger().debug("job handler %s finished", method.__name__)
+ result = logged(self, *args, **kwargs)
+ LOG.debug("job handler %s finished", method.__name__)
return result
- except:
- _logger().debug("job handler %s terminated with exception: %s",
- method.__name__, traceback.format_exc())
- raise
finally:
if self._session_level == 0: #pylint: disable=W0212
self._unlock_database() #pylint: disable=W0212
@@ -248,26 +200,25 @@ class YumWorker(Process):
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,
- yum_args=None,
yum_kwargs=None,
logging_config=None):
Process.__init__(self, name="YumWorker")
- self._queue_in = queue_in
- self._queue_out = queue_out
+ self._jobmgr = JobManager(queue_in, queue_out)
self._session_level = 0
self._session_ended = False
- if yum_args is None:
- yum_args = tuple()
if yum_kwargs is None:
yum_kwargs = {}
- self._yum_args = (yum_args, yum_kwargs)
+ self._yum_kwargs = yum_kwargs
self._yum_base = None
self._pkg_cache = None
@@ -279,31 +230,28 @@ class YumWorker(Process):
# *************************************************************************
# Private methods
# *************************************************************************
- @_trace_function
+ @trace_function
def _init_database(self):
"""
Initializes yum base object, when it does no exists.
And updates the cache (when out of date).
"""
if self._yum_base is None:
- _logger().info("creating YumBase with args=(%s)",
- ", ".join(chain(
- (str(a) for a in self._yum_args[0]),
- ( "%s=%s"%(k, str(v))
- for k, v in self._yum_args[1].items()))))
- self._yum_base = yum.YumBase(
- *self._yum_args[0], **self._yum_args[1])
-
- @_trace_function
+ 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.
"""
- _logger().info("freing database")
+ LOG.info("freing database")
self._pkg_cache.clear()
self._yum_base = None
- @_trace_function
+ @trace_function
def _lock_database(self):
"""
Only one process is allowed to work with package database at given time.
@@ -313,31 +261,47 @@ class YumWorker(Process):
"""
while True:
try:
- _logger().info("trying to lock database - session level %d",
+ LOG.info("trying to lock database - session level %d",
self._session_level)
self._yum_base.doLock()
- _logger().info("successfully locked up")
+ LOG.info("successfully locked up")
break
except yum.Errors.LockError as exc:
- _logger().warn("failed to lock")
+ LOG.warn("failed to lock")
if exc.errno in (errno.EPERM, errno.EACCES, errno.ENOSPC):
- _logger().error("can't create lock file")
+ LOG.error("can't create lock file")
raise errors.DatabaseLockError("Can't create lock file.")
- _logger().info("trying to lock again after %.1f seconds",
+ LOG.info("trying to lock again after %.1f seconds",
LOCK_WAIT_INTERVAL)
time.sleep(LOCK_WAIT_INTERVAL)
- @_trace_function
+ @trace_function
def _unlock_database(self):
"""
The opposite to _lock_database() method.
"""
if self._yum_base is not None:
- _logger().info("unlocking database")
+ LOG.info("unlocking database")
self._yum_base.closeRpmDB()
self._yum_base.doUnlock()
- @_trace_function
+ @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):
@@ -350,7 +314,7 @@ class YumWorker(Process):
packages; makes sense only with cachee_packages=True
"""
if cache_packages is True and flush_cache is True:
- _logger().debug("flushing package cache")
+ LOG.debug("flushing package cache")
self._pkg_cache.clear()
res = []
for orig in packages:
@@ -360,7 +324,7 @@ class YumWorker(Process):
res.append(pkg)
return res
- @_trace_function
+ @trace_function
def _cache_packages(self, packages, flush_cache=True, transform=False):
"""
Store packages in cache and return them.
@@ -372,13 +336,13 @@ class YumWorker(Process):
if transform is True:
return self._transform_packages(packages, flush_cache=flush_cache)
if flush_cache is True:
- _logger().debug("flushing package cache")
+ LOG.debug("flushing package cache")
self._pkg_cache.clear()
for pkg in packages:
self._pkg_cache[id(pkg)] = pkg
return packages
- @_trace_function
+ @trace_function
def _lookup_package(self, pkg):
"""
Lookup the original package in cache.
@@ -387,13 +351,13 @@ class YumWorker(Process):
"""
if not isinstance(pkg, packageinfo.PackageInfo):
raise TypeError("pkg must be instance of PackageInfo")
- _logger().debug("looking up yum package %s with id=%d",
+ LOG.debug("looking up yum package %s with id=%d",
pkg, pkg.objid)
try:
result = self._pkg_cache[pkg.objid]
- _logger().debug("lookup successful")
+ LOG.debug("lookup successful")
except KeyError:
- _logger().warn("lookup of package %s with id=%d failed, trying"
+ 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',
@@ -402,13 +366,13 @@ class YumWorker(Process):
transform=False,
**pkg.key_props)
if len(result) < 1:
- _logger().warn("package %s not found", pkg)
+ LOG.warn("package %s not found", pkg)
raise errors.PackageNotFound(
"package %s could not be found" % pkg)
result = result[0]
return result
- @_trace_function
+ @trace_function
def _clear_repository_cache(self):
"""
Clears the repository cache and their configuration directory
@@ -421,7 +385,7 @@ class YumWorker(Process):
del self._yum_base.pkgSack
self._repodir_mtimes.clear()
- @_trace_function
+ @trace_function
def _check_repository_configs(self):
"""
Checks whether repository information is up to date with configuration
@@ -435,7 +399,7 @@ class YumWorker(Process):
and ( not repodir in self._repodir_mtimes
or ( os.stat(repodir).st_mtime
> self._repodir_mtimes[repodir]))):
- _logger().info("repository config dir %s changed", repodir)
+ LOG.info("repository config dir %s changed", repodir)
dirty = True
break
if not dirty:
@@ -444,18 +408,18 @@ class YumWorker(Process):
if ( not os.path.exists(filename)
or ( int(os.stat(filename).st_mtime)
> repo.repo_config_age)):
- _logger().info('config file of repository "%s" changed',
+ LOG.info('config file of repository "%s" changed',
repo.id)
dirty = True
break
if dirty is True:
- _logger().info("repository cache is dirty, cleaning up ...")
+ 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
+ @trace_function
def _update_repodir_mtimes(self):
"""
Updates the last modification times of repo configuration directories.
@@ -465,7 +429,7 @@ class YumWorker(Process):
if os.path.exists(repodir):
self._repodir_mtimes[repodir] = os.stat(repodir).st_mtime
- @_trace_function
+ @trace_function
def _do_work(self, job):
"""
Dispatcher of incoming jobs. Job is passed to the right handler
@@ -484,55 +448,86 @@ class YumWorker(Process):
jobs.YumBeginSession : self._handle_begin_session,
jobs.YumEndSession : self._handle_end_session,
jobs.YumCheckPackage : self._handle_check_package,
+ 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__]
- _logger().info("processing job %s(id=%d)",
+ LOG.info("processing job %s(id=%d)",
job.__class__.__name__, job.jobid)
except KeyError:
- _logger().info("No handler for job \"%s\"", job.__class__.__name__)
+ 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
+ @trace_function
def _run_transaction(self, name):
"""
Builds and runs the yum transaction and checks for errors.
@param name of transaction used only in error description on failure
"""
- _logger().info("building transaction %s", name)
+ LOG.info("building transaction %s", name)
(code, msgs) = self._yum_base.buildTransaction()
if code == 1:
- _logger().error("building transaction %s failed: %s",
+ LOG.error("building transaction %s failed: %s",
name, "\n".join(msgs))
raise errors.TransactionBuildFailed(
"Failed to build \"%s\" transaction: %s" % (
name, "\n".join(msgs)))
- _logger().info("processing transaction %s", name)
+ LOG.info("processing transaction %s", name)
self._yum_base.processTransaction()
self._yum_base.closeRpmDB()
- @_trace_function
+ @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
- _logger().info("beginning session level %s", self._session_level)
+ LOG.info("beginning session level %s", self._session_level)
if self._session_level == 1:
self._init_database()
self._lock_database()
- @_trace_function
+ @trace_function
def _handle_end_session(self):
"""
Handler for session end job.
"""
- _logger().info("ending session level %d", self._session_level)
+ 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()
@@ -556,7 +551,7 @@ class YumWorker(Process):
else:
what = kind
with RepoFilterSetter(self._yum_base, include_repos, exclude_repos):
- _logger().debug("calling YumBase.doPackageLists(%s, showdups=%s)",
+ LOG.debug("calling YumBase.doPackageLists(%s, showdups=%s)",
what, allow_duplicates)
pkglist = self._yum_base.doPackageLists(what,
showdups=allow_duplicates)
@@ -570,7 +565,7 @@ class YumWorker(Process):
result = getattr(pkglist, what)
if sort is True:
result.sort()
- _logger().debug("returning %s packages", len(result))
+ LOG.debug("returning %s packages", len(result))
return self._cache_packages(result, transform=transform)
@_needs_database
@@ -588,21 +583,40 @@ class YumWorker(Process):
result = [p for p in pkglist if matches(p)]
if sort is True:
result.sort()
- _logger().debug("%d packages matching", len(result))
+ 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):
+ def _handle_install_package(self, pkg, force=False):
"""
Handler for package installation job.
@return installed package instance
"""
- pkg_desired = self._lookup_package(pkg)
- self._yum_base.install(pkg_desired)
- self._run_transaction("install")
+ 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:
@@ -615,7 +629,16 @@ class YumWorker(Process):
"""
Handler for package removal job.
"""
- pkg = self._lookup_package(pkg)
+ 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")
@@ -625,7 +648,16 @@ class YumWorker(Process):
Handler for specific package update job.
@return package corresponding to pkg after update
"""
- pkg_desired = self._lookup_package(pkg)
+ 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,
@@ -641,12 +673,24 @@ class YumWorker(Process):
return installed[0]
@_needs_database
- def _handle_update_package(self, pkg, to_epoch, to_version, to_release):
+ def _handle_update_package(self, pkg, to_epoch, to_version, to_release,
+ _force=False):
"""
Handler for package update job.
@return updated package instance
"""
- pkg = self._lookup_package(pkg)
+ 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
@@ -672,12 +716,54 @@ class YumWorker(Process):
"""
@return PackageFile instance for requested package
"""
- pkg = self._lookup_package(pkg)
+ 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)
vpkg = yum.packages._RPMVerifyPackage(pkg, pkg.hdr.fiFromHeader(),
packagecheck.pkg_checksum_type(pkg), [], True)
return packagecheck.make_package_check_from_db(vpkg)
@_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
@@ -692,7 +778,7 @@ class YumWorker(Process):
repos.sort()
if transform:
repos = [repository.make_repository_from_db(r) for r in repos]
- _logger().debug("returning %d repositories from %s",
+ LOG.debug("returning %d repositories from %s",
len(repos), kind)
return repos
@@ -709,15 +795,15 @@ class YumWorker(Process):
self._yum_base.repos.getRepo(filters["repoid"]))
if ( (kind == "enabled" and not repo.enabled)
or (kind == "disabled" and repo.enabled)):
- _logger().warn(
+ LOG.warn(
'no such repository with id="%s"matching filters',
filters['repoid'])
return []
- _logger().debug(
+ LOG.debug(
"exactly one repository matching filters found")
return [repo]
except (KeyError, yum.Errors.RepoError):
- _logger().warn('repository with id="%s" could not be found',
+ 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)
@@ -730,7 +816,7 @@ class YumWorker(Process):
break
else: # all properties passed
result.append(repository.make_repository_from_db(repo))
- _logger().debug("found %d repositories matching", len(result))
+ LOG.debug("found %d repositories matching", len(result))
return result
@_needs_database
@@ -746,10 +832,10 @@ class YumWorker(Process):
try:
if enable ^ res:
if enable is True:
- _logger().info("enabling repository %s" % repo)
+ LOG.info("enabling repository %s" % repo)
repo.enable()
else:
- _logger().info("disabling repository %s" % repo)
+ LOG.info("disabling repository %s" % repo)
repo.disable()
try:
yum.config.writeRawRepoFile(repo, only=["enabled"])
@@ -758,7 +844,7 @@ class YumWorker(Process):
'failed to modify repository "%s": %s - %s' % (
repo, exc.__class__.__name__, str(exc)))
else:
- _logger().info("no change for repo %s", repo)
+ 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)))
@@ -772,59 +858,32 @@ class YumWorker(Process):
"""
@return input queue for jobs
"""
- return self._queue_in
+ return self._jobmgr.queue_in
@property
def downlink(self):
"""
@return output queue for job results
"""
- return self._queue_out
+ return self._jobmgr.queue_out
# *************************************************************************
# Public methods
# *************************************************************************
def run(self):
"""
- Main loop of process. It accepts a job from input queue, handles it,
- sends the result to output queue and marks the job as done.
-
- It is terminated, when None is received from input queue.
+ Thread's entry point. After initial setup it calls _main_loop().
"""
if self._logging_config is not None:
- try:
- logging.config.dictConfig(self._logging_config)
- except Exception: #pylint: disable=W0703
- # logging is not set up but client expects us to work
- pass
- _logger().info("running as pid=%d", self.pid)
- _logger().info("starting %s main loop", self.__class__.__name__)
+ 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()
- while True:
- if self._session_ended and self._session_level == 0:
- try:
- job = self._queue_in.get(True, FREE_DATABASE_TIMEOUT)
- except TQueue.Empty:
- self._free_database()
- self._session_ended = False
- continue
- else:
- job = self._queue_in.get()
- if job is not None: # not a terminate command
- try:
- result = self._do_work(job)
- except Exception: #pylint: disable=W0703
- # (type, value, traceback)
- result = sys.exc_info()
- # traceback is not pickable - replace it with formatted
- # text
- result = ( result[0], result[1]
- , traceback.format_tb(result[2]))
- _logger().error("job %s(id=%d) failed: %s",
- job.__class__.__name__, job.jobid,
- traceback.format_exc())
- self._queue_out.put((job.jobid, result))
- self._queue_in.task_done()
- if job is None:
- break
+
+ self._main_loop()
+ LOG.info("terminating")
diff --git a/src/software/openlmi/software/yumdb/util.py b/src/software/openlmi/software/yumdb/util.py
new file mode 100644
index 0000000..f2af151
--- /dev/null
+++ b/src/software/openlmi/software/yumdb/util.py
@@ -0,0 +1,97 @@
+from itertools import chain
+import inspect
+import logging
+import os
+
+from openlmi.software.yumdb import errors
+
+class DispatchingFormatter:
+ def __init__(self, formatters, default):
+ 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):
+ 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:
+ 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):
+ try:
+ logging.config.dictConfig(config)
+ except Exception: #pylint: disable=W0703
+ # logging is not set up but client expects us to work
+ pass
+
diff --git a/src/software/test/package.py b/src/software/test/package.py
index 997ee34..3cd0008 100644
--- a/src/software/test/package.py
+++ b/src/software/test/package.py
@@ -21,6 +21,7 @@
Abstraction for RPM package for test purposes.
"""
+import json
import util
class Package(object): #pylint: disable=R0902
@@ -115,3 +116,15 @@ class Package(object): #pylint: disable=R0902
attrs.append('arch')
return util.make_evra(*[getattr(self, '_'+a) for a in attrs])
+class PackageEncoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, Package):
+ return obj.__dict__
+ return json.JSONEncoder.default(self, obj)
+
+def from_json(json_object):
+ if '_arch' in json_object:
+ kwargs = {k[1:]: v for k, v in json_object.items()}
+ return Package(**kwargs)
+ return json_object
+
diff --git a/src/software/test/rpmcache.py b/src/software/test/rpmcache.py
index c2dde24..6dc45a0 100644
--- a/src/software/test/rpmcache.py
+++ b/src/software/test/rpmcache.py
@@ -24,15 +24,14 @@ Creation and manipulation utilities with rpm cache for software tests.
"""
import copy
-import datetime
import os
-import pickle
+import json
import random
import re
from collections import defaultdict
from subprocess import call, check_output, CalledProcessError
-from package import Package
+from package import Package, PackageEncoder, from_json
import util
DB_BACKUP_FILE = 'lmi_software_test_cache'
@@ -425,8 +424,9 @@ def write_pkgdb(safe_pkgs, dangerous_pkgs, cache_dir=''):
Writes package database into a file named DB_BACKUP_FILE.
"""
with open(os.path.join(cache_dir, DB_BACKUP_FILE), 'w') as db_file:
- data = (datetime.datetime.now(), safe_pkgs, dangerous_pkgs)
- pickle.dump(data, db_file)
+ data = (safe_pkgs, dangerous_pkgs)
+ json.dump(data, db_file, cls=PackageEncoder,
+ sort_keys=True, indent=4, separators=(',', ': '))
def load_pkgdb(cache_dir=''):
"""
@@ -434,7 +434,7 @@ def load_pkgdb(cache_dir=''):
@return (safe, dangerous) package lists loaded from file
"""
with open(os.path.join(cache_dir, DB_BACKUP_FILE), 'r') as db_file:
- _, safe, dangerous = pickle.load(db_file)
+ safe, dangerous = json.load(db_file, object_hook=from_json)
#print "Loaded package database from: %s" % date_time
return safe, dangerous