summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMichal Minar <miminar@redhat.com>2013-02-18 11:06:24 +0100
committerMichal Minar <miminar@redhat.com>2013-02-18 11:06:24 +0100
commit4214208d559ed88f3b29bc046d4679824bf5faa7 (patch)
tree0a70987b073893ef9ebb33e8f7421560a4b05984 /src
parent7c5dc2d2e73b08f20df94c3acd370f02ab0102c0 (diff)
downloadopenlmi-providers-4214208d559ed88f3b29bc046d4679824bf5faa7.tar.gz
openlmi-providers-4214208d559ed88f3b29bc046d4679824bf5faa7.tar.xz
openlmi-providers-4214208d559ed88f3b29bc046d4679824bf5faa7.zip
added LMI_SoftwareIdentityResource provider
now it's possible to list and manage repositories
Diffstat (limited to 'src')
-rw-r--r--src/software/openlmi/software/LMI_SoftwareIdentityResource.py277
-rw-r--r--src/software/openlmi/software/cimom_entry.py5
-rw-r--r--src/software/openlmi/software/core/SoftwareIdentity.py2
-rw-r--r--src/software/openlmi/software/core/SoftwareIdentityResource.py641
-rw-r--r--src/software/openlmi/software/yumdb/__init__.py27
-rw-r--r--src/software/openlmi/software/yumdb/errors.py8
-rw-r--r--src/software/openlmi/software/yumdb/jobs.py68
-rw-r--r--src/software/openlmi/software/yumdb/process.py153
-rw-r--r--src/software/openlmi/software/yumdb/repository.py190
-rw-r--r--src/software/test/base.py63
-rw-r--r--src/software/test/repository.py175
-rwxr-xr-xsrc/software/test/test_hosted_software_collection.py4
-rwxr-xr-xsrc/software/test/test_software_identity_resource.py209
13 files changed, 1808 insertions, 14 deletions
diff --git a/src/software/openlmi/software/LMI_SoftwareIdentityResource.py b/src/software/openlmi/software/LMI_SoftwareIdentityResource.py
new file mode 100644
index 0000000..6acec42
--- /dev/null
+++ b/src/software/openlmi/software/LMI_SoftwareIdentityResource.py
@@ -0,0 +1,277 @@
+# 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>
+#
+
+"""Python Provider for LMI_SoftwareIdentityResource
+
+Instruments the CIM class LMI_SoftwareIdentityResource
+
+"""
+
+import pywbem
+from pywbem.cim_provider2 import CIMProvider2
+
+from openlmi.common import cmpi_logging
+from openlmi.software.core import SoftwareIdentityResource
+from openlmi.software.yumdb import YumDB, errors
+
+class LMI_SoftwareIdentityResource(CIMProvider2):
+ """Instrument the CIM class LMI_SoftwareIdentityResource
+
+ SoftwareIdentityResource describes the URL of a file or other resource
+ that contains all or part of of a SoftwareIdentity for use by the
+ SoftwareInstallationService. For example, a CIM_SoftwareIdentity might
+ consist of a meta data file, a binary executable file, and a
+ installability checker file for some software on a system. This class
+ allows a management client to selectively access the constituents of
+ the install package to perform a check function, or retrieve some meta
+ data information for the install package represented by the
+ SoftwareIdentity class without downloading the entire package.
+ SoftwareIdentityResources will be related to the SoftwareIdentity
+ using the SAPAvailableForElement association.
+
+ """
+
+ def __init__ (self, _env):
+ cmpi_logging.logger.debug('Initializing provider %s from %s' \
+ % (self.__class__.__name__, __file__))
+
+ @cmpi_logging.trace_method
+ def get_instance(self, env, model):
+ """Return an instance.
+
+ Keyword arguments:
+ env -- Provider Environment (pycimmb.ProviderEnvironment)
+ model -- A template of the pywbem.CIMInstance to be returned. The
+ key properties are set on this instance to correspond to the
+ instanceName that was requested. The properties of the model
+ are already filtered according to the PropertyList from the
+ request. Only properties present in the model need to be
+ given values. If you prefer, you can set all of the
+ values, and the instance will be filtered for you.
+
+ Possible Errors:
+ CIM_ERR_ACCESS_DENIED
+ CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized
+ or otherwise incorrect parameters)
+ CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM
+ Instance does not exist in the specified namespace)
+ CIM_ERR_FAILED (some other unspecified error occurred)
+
+ """
+
+ repo = SoftwareIdentityResource.object_path2repo(
+ env, model.path, kind='all')
+ return SoftwareIdentityResource.repo2model(
+ repo, keys_only=False, model=model)
+
+ @cmpi_logging.trace_method
+ def enum_instances(self, env, model, keys_only):
+ """Enumerate instances.
+
+ The WBEM operations EnumerateInstances and EnumerateInstanceNames
+ are both mapped to this method.
+ This method is a python generator
+
+ Keyword arguments:
+ env -- Provider Environment (pycimmb.ProviderEnvironment)
+ model -- A template of the pywbem.CIMInstances to be generated.
+ The properties of the model are already filtered according to
+ the PropertyList from the request. Only properties present in
+ the model need to be given values. If you prefer, you can
+ always set all of the values, and the instance will be filtered
+ for you.
+ keys_only -- A boolean. True if only the key properties should be
+ set on the generated instances.
+
+ Possible Errors:
+ CIM_ERR_FAILED (some other unspecified error occurred)
+
+ """
+ # Prime model.path with knowledge of the keys, so key values on
+ # the CIMInstanceName (model.path) will automatically be set when
+ # we set property values on the model.
+ model.path.update({'CreationClassName': None, 'SystemName': None,
+ 'Name': None, 'SystemCreationClassName': None})
+
+ repolist = YumDB.get_instance().get_repository_list('all')
+ for repo in repolist:
+ yield SoftwareIdentityResource.repo2model(
+ repo, keys_only=keys_only, model=model)
+
+ @cmpi_logging.trace_method
+ def set_instance(self, env, instance, modify_existing):
+ """Return a newly created or modified instance.
+
+ Keyword arguments:
+ env -- Provider Environment (pycimmb.ProviderEnvironment)
+ instance -- The new pywbem.CIMInstance. If modifying an existing
+ instance, the properties on this instance have been filtered by
+ the PropertyList from the request.
+ modify_existing -- True if ModifyInstance, False if CreateInstance
+
+ Return the new instance. The keys must be set on the new instance.
+
+ Possible Errors:
+ CIM_ERR_ACCESS_DENIED
+ CIM_ERR_NOT_SUPPORTED
+ CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized
+ or otherwise incorrect parameters)
+ CIM_ERR_ALREADY_EXISTS (the CIM Instance already exists -- only
+ valid if modify_existing is False, indicating that the operation
+ was CreateInstance)
+ CIM_ERR_NOT_FOUND (the CIM Instance does not exist -- only valid
+ if modify_existing is True, indicating that the operation
+ was ModifyInstance)
+ CIM_ERR_FAILED (some other unspecified error occurred)
+
+ """
+ # TODO creation and modification should be supported
+ raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED)
+
+ @cmpi_logging.trace_method
+ def delete_instance(self, env, instance_name):
+ """Delete an instance.
+
+ Keyword arguments:
+ env -- Provider Environment (pycimmb.ProviderEnvironment)
+ instance_name -- A pywbem.CIMInstanceName specifying the instance
+ to delete.
+
+ Possible Errors:
+ CIM_ERR_ACCESS_DENIED
+ CIM_ERR_NOT_SUPPORTED
+ CIM_ERR_INVALID_NAMESPACE
+ CIM_ERR_INVALID_PARAMETER (including missing, duplicate, unrecognized
+ or otherwise incorrect parameters)
+ CIM_ERR_INVALID_CLASS (the CIM Class does not exist in the specified
+ namespace)
+ CIM_ERR_NOT_FOUND (the CIM Class does exist, but the requested CIM
+ Instance does not exist in the specified namespace)
+ CIM_ERR_FAILED (some other unspecified error occurred)
+
+ """
+ # TODO removal should also be supported
+ raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED)
+
+ @cmpi_logging.trace_method
+ def cim_method_requeststatechange(self, env, object_name,
+ param_requestedstate=None,
+ param_timeoutperiod=None):
+ """Implements LMI_SoftwareIdentityResource.RequestStateChange()
+
+ Requests that the state of the element be changed to the value
+ specified in the RequestedState parameter. When the requested
+ state change takes place, the EnabledState and RequestedState of
+ the element will be the same. Invoking the RequestStateChange
+ method multiple times could result in earlier requests being
+ overwritten or lost. A return code of 0 shall indicate the state
+ change was successfully initiated. A return code of 3 shall
+ indicate that the state transition cannot complete within the
+ interval specified by the TimeoutPeriod parameter. A return code
+ of 4096 (0x1000) shall indicate the state change was successfully
+ initiated, a ConcreteJob has been created, and its reference
+ returned in the output parameter Job. Any other return code
+ indicates an error condition.
+
+ Keyword arguments:
+ env -- Provider Environment (pycimmb.ProviderEnvironment)
+ object_name -- A pywbem.CIMInstanceName or pywbem.CIMCLassName
+ specifying the object on which the method RequestStateChange()
+ should be invoked.
+ param_requestedstate -- The input parameter RequestedState (
+ type pywbem.Uint16 Values.RequestStateChange.RequestedState)
+ The state requested for the element. This information will be
+ placed into the RequestedState property of the instance if the
+ return code of the RequestStateChange method is 0 ('Completed
+ with No Error'), or 4096 (0x1000) ('Job Started'). Refer to
+ the description of the EnabledState and RequestedState
+ properties for the detailed explanations of the RequestedState
+ values.
+
+ param_timeoutperiod -- The input parameter TimeoutPeriod (
+ type pywbem.CIMDateTime)
+ A timeout period that specifies the maximum amount of time that
+ the client expects the transition to the new state to take.
+ The interval format must be used to specify the TimeoutPeriod.
+ A value of 0 or a null parameter indicates that the client has
+ no time requirements for the transition. If this property
+ does not contain 0 or null and the implementation does not
+ support this parameter, a return code of 'Use Of Timeout
+ Parameter Not Supported' shall be returned.
+
+
+ Returns a two-tuple containing the return value (
+ type pywbem.Uint32 Values.RequestStateChange)
+ and a list of CIMParameter objects representing the output parameters
+
+ Output parameters:
+ Job -- (type REF (pywbem.CIMInstanceName(
+ classname='CIM_ConcreteJob', ...))
+ May contain a reference to the ConcreteJob created to track the
+ state transition initiated by the method invocation.
+
+ Possible Errors:
+ CIM_ERR_ACCESS_DENIED
+ CIM_ERR_INVALID_PARAMETER (including missing, duplicate,
+ unrecognized or otherwise incorrect parameters)
+ CIM_ERR_NOT_FOUND (the target CIM Class or instance does not
+ exist in the specified namespace)
+ CIM_ERR_METHOD_NOT_AVAILABLE (the CIM Server is unable to honor
+ the invocation request)
+ CIM_ERR_FAILED (some other unspecified error occurred)
+ """
+ out_params = []
+ if param_timeoutperiod is not None:
+ return ( SoftwareIdentityResource.Values.RequestStateChange. \
+ Use_of_Timeout_Parameter_Not_Supported
+ , out_params)
+ if param_requestedstate not in {
+ SoftwareIdentityResource.Values.RequestStateChange. \
+ RequestedState.Enabled,
+ SoftwareIdentityResource.Values.RequestStateChange. \
+ RequestedState.Disabled }:
+ return ( SoftwareIdentityResource.Values.RequestStateChange. \
+ Invalid_State_Transition
+ , out_params)
+
+ with YumDB.get_instance() as ydb:
+ repo = SoftwareIdentityResource.object_path2repo(env,
+ object_name, 'all')
+ enable = param_requestedstate == SoftwareIdentityResource.Values. \
+ RequestStateChange.RequestedState.Enabled
+ cmpi_logging.logger.info("%s repository %s" % ("enabling"
+ if enable else "disabling", repo))
+ try:
+ prev = ydb.set_repository_enabled(
+ repo, enable)
+ except errors.RepositoryChangeError as exc:
+ msg = "failed to modify repository %s: %s" % (repo, str(exc))
+ cmpi_logging.logger.error(msg)
+ raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, msg)
+ msg = ( "repository %s already %s"
+ if prev == enable else "repository %s %s")
+ cmpi_logging.logger.info(msg % (repo,
+ "enabled" if enable else "disabled"))
+
+ rval = SoftwareIdentityResource.Values.RequestStateChange. \
+ Completed_with_No_Error
+ return (rval, out_params)
+
diff --git a/src/software/openlmi/software/cimom_entry.py b/src/software/openlmi/software/cimom_entry.py
index 3b2c921..a259ff0 100644
--- a/src/software/openlmi/software/cimom_entry.py
+++ b/src/software/openlmi/software/cimom_entry.py
@@ -34,6 +34,8 @@ from openlmi.software.LMI_MemberOfSoftwareCollection import \
LMI_MemberOfSoftwareCollection
from openlmi.software.LMI_InstalledSoftwareIdentity import \
LMI_InstalledSoftwareIdentity
+from openlmi.software.LMI_SoftwareIdentityResource import \
+ LMI_SoftwareIdentityResource
from openlmi.software.yumdb import YumDB
def get_providers(env):
@@ -47,7 +49,8 @@ def get_providers(env):
"LMI_SystemSoftwareCollection" : LMI_SystemSoftwareCollection(env),
"LMI_HostedSoftwareCollection" : LMI_HostedSoftwareCollection(env),
"LMI_MemberOfSoftwareCollection" : LMI_MemberOfSoftwareCollection(env),
- "LMI_InstalledSoftwareIdentity" : LMI_InstalledSoftwareIdentity(env)
+ "LMI_InstalledSoftwareIdentity" : LMI_InstalledSoftwareIdentity(env),
+ "LMI_SoftwareIdentityResource" : LMI_SoftwareIdentityResource(env)
}
return providers
diff --git a/src/software/openlmi/software/core/SoftwareIdentity.py b/src/software/openlmi/software/core/SoftwareIdentity.py
index 1c4b9fe..7bcf769 100644
--- a/src/software/openlmi/software/core/SoftwareIdentity.py
+++ b/src/software/openlmi/software/core/SoftwareIdentity.py
@@ -199,7 +199,7 @@ def object_path2pkg(op, kind='installed'):
@cmpi_logging.trace_function
def pkg2model(pkg_info, keys_only=True, model=None):
"""
- @param model if None, will be filled with data, otherwise
+ @param model if not None, will be filled with data, otherwise
a new instance of CIMInstance or CIMObjectPath is created
"""
if not isinstance(pkg_info, PackageInfo):
diff --git a/src/software/openlmi/software/core/SoftwareIdentityResource.py b/src/software/openlmi/software/core/SoftwareIdentityResource.py
new file mode 100644
index 0000000..d8e6877
--- /dev/null
+++ b/src/software/openlmi/software/core/SoftwareIdentityResource.py
@@ -0,0 +1,641 @@
+# -*- 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_SoftwareIdentityResource provider.
+"""
+
+import pywbem
+import socket
+
+from openlmi.common import cmpi_logging
+from openlmi.software.core import ComputerSystem
+from openlmi.software.yumdb import YumDB
+from openlmi.software.yumdb.repository import Repository
+
+class Values(object):
+ class DetailedStatus(object):
+ Not_Available = pywbem.Uint16(0)
+ No_Additional_Information = pywbem.Uint16(1)
+ Stressed = pywbem.Uint16(2)
+ Predictive_Failure = pywbem.Uint16(3)
+ Non_Recoverable_Error = pywbem.Uint16(4)
+ Supporting_Entity_in_Error = pywbem.Uint16(5)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 0x8000..
+ _reverse_map = {
+ 0 : 'Not Available',
+ 1 : 'No Additional Information',
+ 2 : 'Stressed',
+ 3 : 'Predictive Failure',
+ 4 : 'Non-Recoverable Error',
+ 5 : 'Supporting Entity in Error'
+ }
+
+ class RequestedState(object):
+ Unknown = pywbem.Uint16(0)
+ Enabled = pywbem.Uint16(2)
+ Disabled = pywbem.Uint16(3)
+ Shut_Down = pywbem.Uint16(4)
+ No_Change = pywbem.Uint16(5)
+ Offline = pywbem.Uint16(6)
+ Test = pywbem.Uint16(7)
+ Deferred = pywbem.Uint16(8)
+ Quiesce = pywbem.Uint16(9)
+ Reboot = pywbem.Uint16(10)
+ Reset = pywbem.Uint16(11)
+ Not_Applicable = pywbem.Uint16(12)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 32768..65535
+ _reverse_map = {
+ 0 : 'Unknown',
+ 2 : 'Enabled',
+ 3 : 'Disabled',
+ 4 : 'Shut Down',
+ 5 : 'No Change',
+ 6 : 'Offline',
+ 7 : 'Test',
+ 8 : 'Deferred',
+ 9 : 'Quiesce',
+ 10 : 'Reboot',
+ 11 : 'Reset',
+ 12 : 'Not Applicable'
+ }
+
+ class HealthState(object):
+ Unknown = pywbem.Uint16(0)
+ OK = pywbem.Uint16(5)
+ Degraded_Warning = pywbem.Uint16(10)
+ Minor_failure = pywbem.Uint16(15)
+ Major_failure = pywbem.Uint16(20)
+ Critical_failure = pywbem.Uint16(25)
+ Non_recoverable_error = pywbem.Uint16(30)
+ # DMTF_Reserved = ..
+ # Vendor_Specific = 32768..65535
+ _reverse_map = {
+ 0 : 'Unknown',
+ 5 : 'OK',
+ 10 : 'Degraded/Warning',
+ 15 : 'Minor failure',
+ 20 : 'Major failure',
+ 25 : 'Critical failure',
+ 30 : 'Non-recoverable error'
+ }
+
+ class TransitioningToState(object):
+ Unknown = pywbem.Uint16(0)
+ Enabled = pywbem.Uint16(2)
+ Disabled = pywbem.Uint16(3)
+ Shut_Down = pywbem.Uint16(4)
+ No_Change = pywbem.Uint16(5)
+ Offline = pywbem.Uint16(6)
+ Test = pywbem.Uint16(7)
+ Defer = pywbem.Uint16(8)
+ Quiesce = pywbem.Uint16(9)
+ Reboot = pywbem.Uint16(10)
+ Reset = pywbem.Uint16(11)
+ Not_Applicable = pywbem.Uint16(12)
+ # DMTF_Reserved = ..
+ _reverse_map = {
+ 0 : 'Unknown',
+ 2 : 'Enabled',
+ 3 : 'Disabled',
+ 4 : 'Shut Down',
+ 5 : 'No Change',
+ 6 : 'Offline',
+ 7 : 'Test',
+ 8 : 'Defer',
+ 9 : 'Quiesce',
+ 10 : 'Reboot',
+ 11 : 'Reset',
+ 12 : 'Not Applicable'
+ }
+
+ class ResourceType(object):
+ Unknown = pywbem.Uint16(0)
+ Other = pywbem.Uint16(1)
+ Installer_and_Payload = pywbem.Uint16(2)
+ Installer = pywbem.Uint16(3)
+ Payload = pywbem.Uint16(4)
+ Installability_checker = pywbem.Uint16(5)
+ Security_Advisory = pywbem.Uint16(6)
+ Engineering_Advisory = pywbem.Uint16(7)
+ Technical_release_notes = pywbem.Uint16(9)
+ Change_notification = pywbem.Uint16(10)
+ Whitepaper = pywbem.Uint16(11)
+ Marketing_Documentation = pywbem.Uint16(12)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 0x8000..0xFFFF
+ _reverse_map = {
+ 0 : 'Unknown',
+ 1 : 'Other',
+ 2 : 'Installer and Payload',
+ 3 : 'Installer',
+ 4 : 'Payload',
+ 5 : 'Installability checker',
+ 6 : 'Security Advisory',
+ 7 : 'Engineering Advisory',
+ 9 : 'Technical release notes',
+ 10 : 'Change notification',
+ 11 : 'Whitepaper',
+ 12 : 'Marketing Documentation'
+ }
+
+ class EnabledState(object):
+ Unknown = pywbem.Uint16(0)
+ Other = pywbem.Uint16(1)
+ Enabled = pywbem.Uint16(2)
+ Disabled = pywbem.Uint16(3)
+ Shutting_Down = pywbem.Uint16(4)
+ Not_Applicable = pywbem.Uint16(5)
+ Enabled_but_Offline = pywbem.Uint16(6)
+ In_Test = pywbem.Uint16(7)
+ Deferred = pywbem.Uint16(8)
+ Quiesce = pywbem.Uint16(9)
+ Starting = pywbem.Uint16(10)
+ # DMTF_Reserved = 11..32767
+ # Vendor_Reserved = 32768..65535
+ _reverse_map = {
+ 0 : 'Unknown',
+ 1 : 'Other',
+ 2 : 'Enabled',
+ 3 : 'Disabled',
+ 4 : 'Shutting Down',
+ 5 : 'Not Applicable',
+ 6 : 'Enabled but Offline',
+ 7 : 'In Test',
+ 8 : 'Deferred',
+ 9 : 'Quiesce',
+ 10 : 'Starting'
+ }
+
+ class ExtendedResourceType(object):
+ Unknown = pywbem.Uint16(0)
+ Not_Applicable = pywbem.Uint16(2)
+ Linux_RPM = pywbem.Uint16(3)
+ HP_UX_Depot = pywbem.Uint16(4)
+ Windows_MSI = pywbem.Uint16(5)
+ Solaris_Package = pywbem.Uint16(6)
+ Macintosh_Disk_Image = pywbem.Uint16(7)
+ Debian_linux_Package = pywbem.Uint16(8)
+ HP_Smart_Component = pywbem.Uint16(11)
+ # Vendor_Reserved = 101..200
+ HTML = pywbem.Uint16(201)
+ PDF = pywbem.Uint16(202)
+ Text_File = pywbem.Uint16(203)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 0x8000..0xFFFF
+ _reverse_map = {
+ 0 : 'Unknown',
+ 2 : 'Not Applicable',
+ 3 : 'Linux RPM',
+ 4 : 'HP-UX Depot',
+ 5 : 'Windows MSI',
+ 6 : 'Solaris Package',
+ 7 : 'Macintosh Disk Image',
+ 8 : 'Debian linux Package',
+ 201 : 'HTML',
+ 202 : 'PDF',
+ 11 : 'HP Smart Component',
+ 203 : 'Text File'
+ }
+
+ class AccessContext(object):
+ Unknown = pywbem.Uint16(0)
+ Other = pywbem.Uint16(1)
+ Default_Gateway = pywbem.Uint16(2)
+ DNS_Server = pywbem.Uint16(3)
+ SNMP_Trap_Destination = pywbem.Uint16(4)
+ MPLS_Tunnel_Destination = pywbem.Uint16(5)
+ DHCP_Server = pywbem.Uint16(6)
+ SMTP_Server = pywbem.Uint16(7)
+ LDAP_Server = pywbem.Uint16(8)
+ Network_Time_Protocol__NTP__Server = pywbem.Uint16(9)
+ Management_Service = pywbem.Uint16(10)
+ internet_Storage_Name_Service__iSNS_ = pywbem.Uint16(11)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 32768..65535
+ _reverse_map = {
+ 0 : 'Unknown',
+ 1 : 'Other',
+ 2 : 'Default Gateway',
+ 3 : 'DNS Server',
+ 4 : 'SNMP Trap Destination',
+ 5 : 'MPLS Tunnel Destination',
+ 6 : 'DHCP Server',
+ 7 : 'SMTP Server',
+ 8 : 'LDAP Server',
+ 9 : 'Network Time Protocol (NTP) Server',
+ 10 : 'Management Service',
+ 11 : 'internet Storage Name Service (iSNS)'
+ }
+
+ class AvailableRequestedStates(object):
+ Enabled = pywbem.Uint16(2)
+ Disabled = pywbem.Uint16(3)
+ Shut_Down = pywbem.Uint16(4)
+ Offline = pywbem.Uint16(6)
+ Test = pywbem.Uint16(7)
+ Defer = pywbem.Uint16(8)
+ Quiesce = pywbem.Uint16(9)
+ Reboot = pywbem.Uint16(10)
+ Reset = pywbem.Uint16(11)
+ # DMTF_Reserved = ..
+ _reverse_map = {
+ 2 : 'Enabled',
+ 3 : 'Disabled',
+ 4 : 'Shut Down',
+ 6 : 'Offline',
+ 7 : 'Test',
+ 8 : 'Defer',
+ 9 : 'Quiesce',
+ 10 : 'Reboot',
+ 11 : 'Reset'
+ }
+
+ class Status(object):
+ OK = 'OK'
+ Error = 'Error'
+ Degraded = 'Degraded'
+ Unknown = 'Unknown'
+ Pred_Fail = 'Pred Fail'
+ Starting = 'Starting'
+ Stopping = 'Stopping'
+ Service = 'Service'
+ Stressed = 'Stressed'
+ NonRecover = 'NonRecover'
+ No_Contact = 'No Contact'
+ Lost_Comm = 'Lost Comm'
+ Stopped = 'Stopped'
+
+ class CommunicationStatus(object):
+ Unknown = pywbem.Uint16(0)
+ Not_Available = pywbem.Uint16(1)
+ Communication_OK = pywbem.Uint16(2)
+ Lost_Communication = pywbem.Uint16(3)
+ No_Contact = pywbem.Uint16(4)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 0x8000..
+ _reverse_map = {
+ 0 : 'Unknown',
+ 1 : 'Not Available',
+ 2 : 'Communication OK',
+ 3 : 'Lost Communication',
+ 4 : 'No Contact'
+ }
+
+ class OperationalStatus(object):
+ Unknown = pywbem.Uint16(0)
+ Other = pywbem.Uint16(1)
+ OK = pywbem.Uint16(2)
+ Degraded = pywbem.Uint16(3)
+ Stressed = pywbem.Uint16(4)
+ Predictive_Failure = pywbem.Uint16(5)
+ Error = pywbem.Uint16(6)
+ Non_Recoverable_Error = pywbem.Uint16(7)
+ Starting = pywbem.Uint16(8)
+ Stopping = pywbem.Uint16(9)
+ Stopped = pywbem.Uint16(10)
+ In_Service = pywbem.Uint16(11)
+ No_Contact = pywbem.Uint16(12)
+ Lost_Communication = pywbem.Uint16(13)
+ Aborted = pywbem.Uint16(14)
+ Dormant = pywbem.Uint16(15)
+ Supporting_Entity_in_Error = pywbem.Uint16(16)
+ Completed = pywbem.Uint16(17)
+ Power_Mode = pywbem.Uint16(18)
+ Relocating = pywbem.Uint16(19)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 0x8000..
+ _reverse_map = {
+ 0 : 'Unknown',
+ 1 : 'Other',
+ 2 : 'OK',
+ 3 : 'Degraded',
+ 4 : 'Stressed',
+ 5 : 'Predictive Failure',
+ 6 : 'Error',
+ 7 : 'Non-Recoverable Error',
+ 8 : 'Starting',
+ 9 : 'Stopping',
+ 10 : 'Stopped',
+ 11 : 'In Service',
+ 12 : 'No Contact',
+ 13 : 'Lost Communication',
+ 14 : 'Aborted',
+ 15 : 'Dormant',
+ 16 : 'Supporting Entity in Error',
+ 17 : 'Completed',
+ 18 : 'Power Mode',
+ 19 : 'Relocating'
+ }
+
+ class OperatingStatus(object):
+ Unknown = pywbem.Uint16(0)
+ Not_Available = pywbem.Uint16(1)
+ Servicing = pywbem.Uint16(2)
+ Starting = pywbem.Uint16(3)
+ Stopping = pywbem.Uint16(4)
+ Stopped = pywbem.Uint16(5)
+ Aborted = pywbem.Uint16(6)
+ Dormant = pywbem.Uint16(7)
+ Completed = pywbem.Uint16(8)
+ Migrating = pywbem.Uint16(9)
+ Emigrating = pywbem.Uint16(10)
+ Immigrating = pywbem.Uint16(11)
+ Snapshotting = pywbem.Uint16(12)
+ Shutting_Down = pywbem.Uint16(13)
+ In_Test = pywbem.Uint16(14)
+ Transitioning = pywbem.Uint16(15)
+ In_Service = pywbem.Uint16(16)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 0x8000..
+ _reverse_map = {
+ 0 : 'Unknown',
+ 1 : 'Not Available',
+ 2 : 'Servicing',
+ 3 : 'Starting',
+ 4 : 'Stopping',
+ 5 : 'Stopped',
+ 6 : 'Aborted',
+ 7 : 'Dormant',
+ 8 : 'Completed',
+ 9 : 'Migrating',
+ 10 : 'Emigrating',
+ 11 : 'Immigrating',
+ 12 : 'Snapshotting',
+ 13 : 'Shutting Down',
+ 14 : 'In Test',
+ 15 : 'Transitioning',
+ 16 : 'In Service'
+ }
+
+ class RequestStateChange(object):
+ Completed_with_No_Error = pywbem.Uint32(0)
+ Not_Supported = pywbem.Uint32(1)
+ Unknown_or_Unspecified_Error = pywbem.Uint32(2)
+ Cannot_complete_within_Timeout_Period = pywbem.Uint32(3)
+ Failed = pywbem.Uint32(4)
+ Invalid_Parameter = pywbem.Uint32(5)
+ In_Use = pywbem.Uint32(6)
+ # DMTF_Reserved = ..
+ Method_Parameters_Checked___Job_Started = pywbem.Uint32(4096)
+ Invalid_State_Transition = pywbem.Uint32(4097)
+ Use_of_Timeout_Parameter_Not_Supported = pywbem.Uint32(4098)
+ Busy = pywbem.Uint32(4099)
+ # Method_Reserved = 4100..32767
+ # Vendor_Specific = 32768..65535
+ class RequestedState(object):
+ Enabled = pywbem.Uint16(2)
+ Disabled = pywbem.Uint16(3)
+ Shut_Down = pywbem.Uint16(4)
+ Offline = pywbem.Uint16(6)
+ Test = pywbem.Uint16(7)
+ Defer = pywbem.Uint16(8)
+ Quiesce = pywbem.Uint16(9)
+ Reboot = pywbem.Uint16(10)
+ Reset = pywbem.Uint16(11)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 32768..65535
+
+ class EnabledDefault(object):
+ Enabled = pywbem.Uint16(2)
+ Disabled = pywbem.Uint16(3)
+ Not_Applicable = pywbem.Uint16(5)
+ Enabled_but_Offline = pywbem.Uint16(6)
+ No_Default = pywbem.Uint16(7)
+ Quiesce = pywbem.Uint16(9)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 32768..65535
+ _reverse_map = {
+ 2 : 'Enabled',
+ 3 : 'Disabled',
+ 5 : 'Not Applicable',
+ 6 : 'Enabled but Offline',
+ 7 : 'No Default',
+ 9 : 'Quiesce'
+ }
+
+ class PrimaryStatus(object):
+ Unknown = pywbem.Uint16(0)
+ OK = pywbem.Uint16(1)
+ Degraded = pywbem.Uint16(2)
+ Error = pywbem.Uint16(3)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 0x8000..
+ _reverse_map = {
+ 0 : 'Unknown',
+ 1 : 'OK',
+ 2 : 'Degraded',
+ 3 : 'Error'
+ }
+
+ class InfoFormat(object):
+ Other = pywbem.Uint16(1)
+ Host_Name = pywbem.Uint16(2)
+ IPv4_Address = pywbem.Uint16(3)
+ IPv6_Address = pywbem.Uint16(4)
+ IPX_Address = pywbem.Uint16(5)
+ DECnet_Address = pywbem.Uint16(6)
+ SNA_Address = pywbem.Uint16(7)
+ Autonomous_System_Number = pywbem.Uint16(8)
+ MPLS_Label = pywbem.Uint16(9)
+ IPv4_Subnet_Address = pywbem.Uint16(10)
+ IPv6_Subnet_Address = pywbem.Uint16(11)
+ IPv4_Address_Range = pywbem.Uint16(12)
+ IPv6_Address_Range = pywbem.Uint16(13)
+ Dial_String = pywbem.Uint16(100)
+ Ethernet_Address = pywbem.Uint16(101)
+ Token_Ring_Address = pywbem.Uint16(102)
+ ATM_Address = pywbem.Uint16(103)
+ Frame_Relay_Address = pywbem.Uint16(104)
+ URL = pywbem.Uint16(200)
+ FQDN = pywbem.Uint16(201)
+ User_FQDN = pywbem.Uint16(202)
+ DER_ASN1_DN = pywbem.Uint16(203)
+ DER_ASN1_GN = pywbem.Uint16(204)
+ Key_ID = pywbem.Uint16(205)
+ Parameterized_URL = pywbem.Uint16(206)
+ # DMTF_Reserved = ..
+ # Vendor_Reserved = 32768..65535
+ _reverse_map = {
+ 1 : 'Other',
+ 2 : 'Host Name',
+ 3 : 'IPv4 Address',
+ 4 : 'IPv6 Address',
+ 5 : 'IPX Address',
+ 6 : 'DECnet Address',
+ 7 : 'SNA Address',
+ 8 : 'Autonomous System Number',
+ 9 : 'MPLS Label',
+ 10 : 'IPv4 Subnet Address',
+ 11 : 'IPv6 Subnet Address',
+ 12 : 'IPv4 Address Range',
+ 13 : 'IPv6 Address Range',
+ 200 : 'URL',
+ 201 : 'FQDN',
+ 202 : 'User FQDN',
+ 203 : 'DER ASN1 DN',
+ 204 : 'DER ASN1 GN',
+ 205 : 'Key ID',
+ 206 : 'Parameterized URL',
+ 100 : 'Dial String',
+ 101 : 'Ethernet Address',
+ 102 : 'Token Ring Address',
+ 103 : 'ATM Address',
+ 104 : 'Frame Relay Address'
+ }
+
+@cmpi_logging.trace_function
+def object_path2repo(env, op, kind='enabled'):
+ """
+ @param op must contain precise information of repository,
+ otherwise an error is raised
+ """
+ if not isinstance(kind, basestring):
+ raise TypeError("kind must be a string")
+ if not isinstance(op, pywbem.CIMInstanceName):
+ raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
+ "op must be an instance of CIMInstanceName")
+
+ if ( (not "CreationClassName" in op or not op['CreationClassName'])
+ or (not "Name" in op or not op["Name"])
+ or ( not "SystemCreationClassName" in op
+ or not op["SystemCreationClassName"])
+ or (not "SystemName" in op or not op["SystemName"])):
+ raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER, "Wrong keys.")
+ if op["SystemName"] != socket.gethostname():
+ raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
+ 'SystemName "%s" does not match "%s".' % (
+ op["SystemName"], socket.gethostname()))
+ ch = env.get_cimom_handle()
+ if not ch.is_subclass("root/cimv2",
+ sub=op["CreationClassName"],
+ super="CIM_SoftwareIdentityResource"):
+ raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
+ 'CreationClassName \"%s\" must be a subclass of "%s".' % (
+ op["CreationClassName"], "CIM_SoftwareIdentityResource"))
+ if not ch.is_subclass("root/cimv2",
+ sub=op["SystemCreationClassName"],
+ super="CIM_ComputerSystem"):
+ raise pywbem.CIMError(pywbem.CIM_ERR_INVALID_PARAMETER,
+ 'SystemCreationClassName of \"%s\" must be a subclass of "%s".'
+ % (op["CreationClassName"], "CIM_SoftwareIdentityResource"))
+ repos = YumDB.get_instance().filter_repositories(kind, name=op["Name"])
+ if len(repos) < 1:
+ raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
+ 'No matching repository found for Name=\"%s\".' % op["Name"])
+ return repos[0]
+
+@cmpi_logging.trace_function
+def _fill_non_keys(repo, model):
+ """
+ Fills into the model of instance all non-key properties.
+ """
+ for slot in repo.__slots__:
+ cmpi_logging.logger.info("repo[name=%s].%s=%s",
+ repo.name, slot, str(getattr(repo, slot)))
+ model['AccessContext'] = Values.AccessContext.Other
+ if repo.mirror_list:
+ model['AccessInfo'] = repo.mirror_list
+ elif repo.base_urls:
+ if len(repo.base_urls) > 0:
+ if len(repo.base_urls) > 1:
+ cmpi_logging.logger.warn(
+ 'multiple base urls found for repository "%s", selecting'
+ ' the last one', repo)
+ model['AccessInfo'] = repo.base_urls[-1]
+ else:
+ cmpi_logging.logger.error(
+ 'no base url found for repository "%s"' % repo)
+ model['AvailableRequestedStates'] = [
+ Values.AvailableRequestedStates.Enabled,
+ Values.AvailableRequestedStates.Disabled]
+ model['Caption'] = repo.caption
+ model['Cost'] = pywbem.Sint32(repo.cost)
+ model['Description'] = "[%s] - %s for %s architecture with cost %d" % (
+ repo.name, repo.caption, repo.basearch, repo.cost)
+ model['ElementName'] = repo.name
+ model['EnabledDefault'] = Values.EnabledDefault.Not_Applicable
+ if repo.enabled:
+ model['EnabledState'] = Values.EnabledState.Enabled
+ else:
+ model['EnabledState'] = Values.EnabledState.Disabled
+ model['ExtendedResourceType'] = Values.ExtendedResourceType.Linux_RPM
+ model['GPGCheck'] = repo.gpg_check
+ if repo.ready:
+ model['HealthState'] = Values.HealthState.OK
+ else:
+ model['HealthState'] = Values.HealthState.Major_failure
+ if repo.revision is not None:
+ model["Generation"] = pywbem.Uint64(repo.revision)
+ model['InfoFormat'] = Values.InfoFormat.URL
+ model['InstanceID'] = 'LMI:REPO:' + repo.name
+ if repo.mirror_list:
+ model["MirrorList"] = repo.mirror_list
+ model['OperationalStatus'] = [ Values.OperationalStatus.OK
+ if repo.ready else Values.OperationalStatus.Error]
+ model['OtherAccessContext'] = "YUM package repository"
+ model['OtherResourceType'] = "RPM Software Package"
+ # this would need to populateSack, which is expensive
+ #model["PackageCount"] = pywbem.Uint32(repo.pkg_count)
+ if repo.ready:
+ model['PrimaryStatus'] = Values.PrimaryStatus.OK
+ else:
+ model['PrimaryStatus'] = Values.PrimaryStatus.Error
+ model['RepoGPGCheck'] = repo.repo_gpg_check
+ if repo.enabled:
+ model['RequestedState'] = Values.RequestedState.Enabled
+ else:
+ model['RequestedState'] = Values.RequestedState.Disabled
+ model['ResourceType'] = Values.ResourceType.Other
+ model['StatusDescriptions'] = [ "Ready" if repo.ready else "Not Ready" ]
+ model['TimeOfLastStateChange'] = pywbem.CIMDateTime(repo.last_edit)
+ if repo.last_update is not None:
+ model['TimeOfLastUpdate'] = pywbem.CIMDateTime(repo.last_update)
+ model['TransitioningToState'] = Values.TransitioningToState.Not_Applicable
+
+@cmpi_logging.trace_function
+def repo2model(repo, keys_only=True, model=None):
+ """
+ @param model if not None, will be filled with data, otherwise
+ a new instance of CIMInstance or CIMObjectPath is created
+ """
+ if not isinstance(repo, Repository):
+ raise TypeError("pkg must be an instance of Repository")
+ if model is None:
+ model = pywbem.CIMInstanceName("LMI_SoftwareIdentityResource",
+ namespace="root/cimv2")
+ if not keys_only:
+ model = pywbem.CIMInstance(
+ "LMI_SoftwareIdentityResource", path=model)
+ if isinstance(model, pywbem.CIMInstance):
+ def _set_key(k, value):
+ """Sets the value of key property of cim instance"""
+ model[k] = value
+ model.path[k] = value #pylint: disable=E1103
+ else:
+ _set_key = model.__setitem__
+ _set_key('CreationClassName', "LMI_SoftwareIdentityResource")
+ _set_key("Name", repo.name)
+ _set_key("SystemCreationClassName", "Linux_ComputerSystem")
+ _set_key("SystemName", ComputerSystem.get_path()["Name"])
+ if not keys_only:
+ _fill_non_keys(repo, model)
+
+ return model
+
diff --git a/src/software/openlmi/software/yumdb/__init__.py b/src/software/openlmi/software/yumdb/__init__.py
index 35a5870..e9a83c3 100644
--- a/src/software/openlmi/software/yumdb/__init__.py
+++ b/src/software/openlmi/software/yumdb/__init__.py
@@ -48,6 +48,7 @@ from openlmi.software.yumdb.packageinfo import PackageInfo
from openlmi.software.yumdb.packagecheck import PackageFile
from openlmi.software.yumdb.packagecheck import PackageCheck
from openlmi.software.yumdb.process import YumWorker
+from openlmi.software.yumdb.repository import Repository
from openlmi.software.util import singletonmixin
# this may be used as an argument to YumWorker
@@ -321,3 +322,29 @@ class YumDB(singletonmixin.Singleton):
"""
return self._do_job(jobs.YumCheckPackage(pkg))
+ @cmpi_logging.trace_method
+ 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))
+
+ @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, repo, enable):
+ """
+ Enable or disable repository.
+ @param enable is a boolean
+ """
+ return self._do_job(jobs.YumSetRepositoryEnabled(repo, enable))
+
diff --git a/src/software/openlmi/software/yumdb/errors.py b/src/software/openlmi/software/yumdb/errors.py
index 646048e..e888712 100644
--- a/src/software/openlmi/software/yumdb/errors.py
+++ b/src/software/openlmi/software/yumdb/errors.py
@@ -41,3 +41,11 @@ class UnknownJob(Exception):
class PackageNotFound(Exception):
"""Raised, when requested package could not be found."""
pass
+class RepositoryNotFound(Exception):
+ """Raised, when requested repository cound not be found."""
+ def __init__(self, repo_name):
+ Exception.__init__(self, "No such repository: %s" % repo_name)
+class RepositoryChangeError(Exception):
+ """Raised, when modification of repository failed."""
+ pass
+
diff --git a/src/software/openlmi/software/yumdb/jobs.py b/src/software/openlmi/software/yumdb/jobs.py
index f280a11..6e6e2da 100644
--- a/src/software/openlmi/software/yumdb/jobs.py
+++ b/src/software/openlmi/software/yumdb/jobs.py
@@ -27,6 +27,7 @@ import threading
from openlmi.software import util
from openlmi.software.yumdb.packageinfo import PackageInfo
+from openlmi.software.yumdb.repository import Repository
class YumJob(object): #pylint: disable=R0903
"""
@@ -244,3 +245,70 @@ class YumCheckPackage(YumSpecificPackageJob): #pylint: disable=R0903
if not pkg.installed:
raise ValueError("package must be installed to check it")
+class YumGetRepositoryList(YumJob): #pylint: disable=R0903
+ """
+ Job requesing a list of repositories.
+ Arguments:
+ kind - supported values are in SUPPORTED_KINDS tuple
+
+ Worker replies with [repo1, repo2, ...].
+ """
+ __slots__ = ('kind', )
+
+ SUPPORTED_KINDS = ('all', 'enabled', 'disabled')
+
+ def __init__(self, kind):
+ YumJob.__init__(self)
+ if not isinstance(kind, basestring):
+ raise TypeError("kind must be a string")
+ if not kind in self.SUPPORTED_KINDS:
+ raise ValueError("kind must be one of {%s}" %
+ ", ".join(self.SUPPORTED_KINDS))
+ self.kind = kind
+
+class YumFilterRepositories(YumGetRepositoryList):
+ """
+ Job similar to YumGetRepositoryList, but allowing to specify
+ filter on packages.
+ Arguments (plus those in YumGetRepositoryList):
+ name, gpg_check, repo_gpg_check
+
+ Some of those are redundant, but filtering is optimized for
+ speed, so supplying all of them won't affect performance.
+
+ Worker replies with [repo1, repo2, ...].
+ """
+ __slots__ = ('name', 'gpg_check', 'repo_gpg_check')
+
+ def __init__(self, kind,
+ name=None, gpg_check=None, repo_gpg_check=None):
+ YumGetRepositoryList.__init__(self, kind)
+ self.name = name
+ self.gpg_check = None if gpg_check is None else bool(gpg_check)
+ self.repo_gpg_check = (
+ None if repo_gpg_check is None else bool(repo_gpg_check))
+
+class YumSpecificRepositoryJob(YumJob): #pylint: disable=R0903
+ """
+ Abstract job taking instance of yumdb.Repository as argument.
+ Arguments:
+ repo - plays different role depending on job subclass
+ """
+ __slots__ = ('repo', )
+ def __init__(self, repo):
+ if not isinstance(repo, Repository):
+ raise TypeError("repo must be instance of yumdb.Repository")
+ YumJob.__init__(self)
+ self.repo = repo
+
+class YumSetRepositoryEnabled(YumSpecificRepositoryJob):#pylint: disable=R0903
+ """
+ Job allowing to enable or disable repository.
+ Arguments:
+ enable - boolean representing next state
+ """
+ __slots__ = ('enable', )
+ def __init__(self, pkg, enable):
+ YumSpecificRepositoryJob.__init__(self, pkg)
+ self.enable = bool(enable)
+
diff --git a/src/software/openlmi/software/yumdb/process.py b/src/software/openlmi/software/yumdb/process.py
index daef180..faa0711 100644
--- a/src/software/openlmi/software/yumdb/process.py
+++ b/src/software/openlmi/software/yumdb/process.py
@@ -28,6 +28,7 @@ import inspect
from itertools import chain
import logging
from multiprocessing import Process
+import os
import Queue as TQueue # T as threaded
import sys
import time
@@ -40,6 +41,7 @@ from openlmi.software.yumdb import errors
from openlmi.software.yumdb import jobs
from openlmi.software.yumdb import packageinfo
from openlmi.software.yumdb import packagecheck
+from openlmi.software.yumdb import repository
# *****************************************************************************
# Constants
@@ -217,6 +219,9 @@ class YumWorker(Process):
self._yum_base = None
self._pkg_cache = None
+ # contains (repoid, time_stamp_of_config_file)
+ # plus (/repos/dir, ...) for each repo config directory
+ self._repodir_mtimes = {}
self._logging_config = logging_config
# *************************************************************************
@@ -352,6 +357,60 @@ class YumWorker(Process):
return result
@_trace_function
+ def _clear_repository_cache(self):
+ """
+ Clears the repository cache and their configuration directory
+ last modification times.
+ """
+ if self._yum_base is not None:
+ self._yum_base.repos.close()
+ del self._yum_base.repos
+ self._repodir_mtimes.clear()
+
+ @_trace_function
+ def _check_repository_configs(self):
+ """
+ Checks whether repository information is up to date with configuration
+ files by comparing timestamps. If not, repository cache will be
+ released.
+ """
+ dirty = False
+ if self._repodir_mtimes:
+ for repodir in self._yum_base.conf.reposdir:
+ if ( os.path.exists(repodir)
+ and ( not repodir in self._repodir_mtimes
+ or ( os.stat(repodir).st_mtime
+ > self._repodir_mtimes[repodir]))):
+ _logger().info("repository config dir %s changed", repodir)
+ dirty = True
+ break
+ if not dirty:
+ for repo in self._yum_base.repos.repos.values():
+ filename = repo.repofile
+ if ( not os.path.exists(filename)
+ or os.stat(filename).st_mtime > repo.repo_config_age):
+ _logger().info('config file of repository "%s"'
+ ' changed', repo.id)
+ dirty = True
+ break
+ if dirty is True:
+ _logger().info("repository cache is dirty, cleaning up ...")
+ self._clear_repository_cache()
+ if dirty is True or not self._repodir_mtimes:
+ self._yum_base.getReposFromConfig()
+ self._update_repodir_mtimes()
+
+ @_trace_function
+ def _update_repodir_mtimes(self):
+ """
+ Updates the last modification times of repo configuration directories.
+ """
+ assert self._yum_base is not None
+ for repodir in self._yum_base.conf.reposdir:
+ if os.path.exists(repodir):
+ self._repodir_mtimes[repodir] = os.stat(repodir).st_mtime
+
+ @_trace_function
def _do_work(self, job):
"""
Dispatcher of incoming jobs. Job is passed to the right handler
@@ -369,7 +428,12 @@ class YumWorker(Process):
jobs.YumUpdatePackage : self._handle_update_package,
jobs.YumBeginSession : self._handle_begin_session,
jobs.YumEndSession : self._handle_end_session,
- jobs.YumCheckPackage : self._handle_check_package
+ jobs.YumCheckPackage : self._handle_check_package,
+ 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)",
job.__class__.__name__, job.jobid)
@@ -555,6 +619,93 @@ class YumWorker(Process):
packagecheck.pkg_checksum_type(pkg), [], True)
return packagecheck.make_package_check_from_db(vpkg)
+ @_needs_database
+ def _handle_get_repository_list(self, kind, transform=True):
+ """
+ @return list of yumdb.Repository instances
+ """
+ self._check_repository_configs()
+ if kind == 'enabled':
+ repos = sorted(self._yum_base.repos.listEnabled())
+ else:
+ repos = self._yum_base.repos.repos.values()
+ if kind == 'disabled':
+ repos = [repo for repo in repos if not repo.enabled]
+ repos.sort()
+ if transform:
+ repos = [repository.make_repository_from_db(r) for r in repos]
+ _logger().debug("returning %d repositories from %s",
+ len(repos), kind)
+ return repos
+
+ @_needs_database
+ def _handle_filter_repositories(self, kind, **filters):
+ """
+ @return list of yumdb.Repository instances -- filtered
+ """
+ filters = dict((k, v) for k, v in filters.items() if v is not None)
+ if 'name' in filters:
+ self._check_repository_configs()
+ try:
+ repo = repository.make_repository_from_db(
+ self._yum_base.repos.getRepo(filters["name"]))
+ if ( (kind == "enabled" and not repo.enabled)
+ or (kind == "disabled" and repo.enabled)):
+ _logger().warn(
+ 'no such repository with id="%s"matching filters',
+ filters['name'])
+ return []
+ _logger().debug(
+ "exactly one repository matching filters found")
+ return [repo]
+ except (KeyError, yum.Errors.RepoError):
+ _logger().warn('repository with id="%s" could not be found',
+ filters['name'])
+ raise errors.RepositoryNotFound(filters['name'])
+ repos = self._handle_get_repository_list(kind, transform=False)
+ result = []
+ for repo in repos:
+ # do the filtering and safe transformed repo into result
+ for prop, value in filters.items():
+ if repository.get_prop_from_yum_repo(repo, prop) != value:
+ # did not pass the filter
+ break
+ else: # all properties passed
+ result.append(repository.make_repository_from_db(repo))
+ _logger().debug("found %d repositories matching", len(result))
+ return result
+
+ @_needs_database
+ def _handle_set_repository_enabled(self, repo, enable):
+ """
+ @return previous enabled state
+ """
+ try:
+ repo = self._yum_base.repos.getRepo(repo.name)
+ except (KeyError, yum.Errors.RepoError):
+ raise errors.RepositoryNotFound(repo.name)
+ res = repo.enabled
+ try:
+ if enable ^ res:
+ if enable is True:
+ _logger().info("enabling repository %s" % repo)
+ repo.enable()
+ else:
+ _logger().info("disabling repository %s" % repo)
+ repo.disable()
+ try:
+ yum.config.writeRawRepoFile(repo, only=["enabled"])
+ except Exception as exc:
+ raise errors.RepositoryChangeError(
+ 'failed to modify repository "%s": %s - %s' % (
+ repo, exc.__class__.__name__, str(exc)))
+ else:
+ _logger().info("no change for repo %s", repo)
+ except yum.Errors.RepoError as exc:
+ raise errors.RepositoryChangeError(
+ 'failed to modify repository "%s": %s' % (repo, str(exc)))
+ return res
+
# *************************************************************************
# Public properties
# *************************************************************************
diff --git a/src/software/openlmi/software/yumdb/repository.py b/src/software/openlmi/software/yumdb/repository.py
new file mode 100644
index 0000000..ee38f53
--- /dev/null
+++ b/src/software/openlmi/software/yumdb/repository.py
@@ -0,0 +1,190 @@
+# -*- encoding: utf-8 -*-
+# Software Management Providers
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Authors: Michal Minar <miminar@redhat.com>
+#
+"""
+Module holding an abstraction for YUM repository.
+"""
+
+from datetime import datetime
+import yum
+
+# maps names of Repository properties to their corresponding property
+# names in YumRepository object
+PROPERTY_NAME_MAP = {
+ "name" : "id",
+ "base_urls" : "baseurl",
+ "caption" : "name",
+ "config_file" : "repofile",
+ "cost" : "cost",
+ "enabled" : "enabled",
+ "gpg_check" : "gpgcheck",
+ "last_edit" : "repo_config_age",
+ "mirror_list" : "mirrorlist",
+ "mirror_urls" : "mirrorurls",
+ "pkg_dir" : "pkgdir",
+ "ready" : "ready",
+ "repo_gpg_check" : "repo_gpgcheck",
+ "timeout" : "timeout"
+}
+
+def get_prop_from_yum_repo(repo, prop_name):
+ """
+ @param prop_name is one Repository properties
+ @return value of property from object of YumRepository
+ """
+ if not isinstance(repo, yum.yumRepo.YumRepository):
+ raise TypeError("repo must be in instance of yum.yumRepo.YumRepository")
+ if prop_name in PROPERTY_NAME_MAP:
+ val = getattr(repo, PROPERTY_NAME_MAP[prop_name])
+ if prop_name == "last_edit":
+ val = datetime.fromtimestamp(val)
+ elif prop_name == "mirror_urls" and not repo.mirrorlist:
+ val = None
+ elif prop_name == "ready":
+ val = val()
+ elif prop_name in {"arch", "basearch", "releasever"}:
+ val = repo.yumvar[prop_name]
+ elif prop_name in {"revision", "last_update"}:
+ if repo.enabled and repo.repoXML:
+ md = repo.repoXML
+ if prop_name == "last_update":
+ val = datetime.fromtimestamp(md.timestamp)
+ elif prop_name == "revision":
+ val = long(md.revision)
+ else:
+ val = getattr(repo.repoXML, prop_name)
+ else:
+ val = None
+ elif prop_name == "pkg_count":
+ # this needs populated sack: ydb.repos.populateSack(repo.id)
+ val = len(repo.sack)
+ else:
+ raise ValueError('Unknown repository property: "%s"' % prop_name)
+ return val
+
+class Repository(object):
+ """
+ Container for repository metadata. It represents yum repository.
+ It's supposed to be passed from YumWorker to YumDB client and
+ vice-versa.
+ """
+ __slots__ = (
+ "repoid", # [int] id of python object on server process
+ "name", # [string] repository id name
+ # (name of config file)
+
+ "arch", # [string] architecture of packages
+ "basearch", # [string] base system architecture
+ "base_urls", # [list] base urls as strings
+ #"cache",
+ #"cache_dir",
+ "caption", # [string] repository descriptive name
+ "config_file", # [string] file path to corresponding
+ # config file
+ "cost", # [int] cost of repository
+ "enabled", # [boolean] whether repo is enabled
+ "gpg_check", # [boolean] whether to check gpg signature
+ #"metadata_expire", # how long are metadata considered valid
+ "last_edit", # datetime of last config modification
+ "last_update", # datetime of last change of repository
+ # on server
+ "mirror_list", # url of mirrorlist, or None
+ "mirror_urls", # list of urls obtained from mirrorlist or None
+ #"persist_dir", #
+ #"pkg_count", # number of packages in directory
+ "pkg_dir", # directory with packages
+ #"proxy", # boolean saying whether this is a proxy
+ "ready", # boolean saying, whether it's ready for use
+ "releasever", # version of targeted distribution
+ "repo_gpg_check", # [boolean] whether to check gpg
+ # signarues of data
+ "revision",
+ "timeout", # timeout for requests
+ )
+
+ def __init__(self, repoid, name, arch, basearch, base_urls, caption,
+ config_file, cost, enabled, gpg_check, last_edit, last_update,
+ pkg_dir, ready, releasever, repo_gpg_check, revision,
+ timeout, mirror_list=None, mirror_urls=None):
+ for arg in ('last_edit', 'last_update'):
+ if ( locals()[arg] is not None
+ and not isinstance(locals()[arg], datetime)):
+ raise TypeError("%s must be an instance of datetime" % arg)
+ if not isinstance(timeout, float):
+ raise TypeError("timeout must be a float")
+ for arg in ('cost', 'revision'):
+ if ( locals()[arg] is not None
+ and not isinstance(locals()[arg], (int, long))):
+ raise TypeError("%s must be an integer" % arg)
+ self.repoid = repoid
+ self.name = name
+ self.arch = arch
+ self.basearch = basearch
+ self.base_urls = list(base_urls)
+ self.caption = caption
+ self.config_file = config_file
+ self.cost = cost
+ self.enabled = bool(enabled)
+ self.gpg_check = bool(gpg_check)
+ self.last_edit = last_edit
+ self.last_update = last_update
+ self.mirror_list = "" if not mirror_list else mirror_list
+ self.mirror_urls = [] if not mirror_urls else list(mirror_urls)
+ #self.pkg_count = pkg_count
+ self.pkg_dir = pkg_dir
+ self.ready = bool(ready)
+ self.releasever = releasever
+ self.repo_gpg_check = bool(repo_gpg_check)
+ self.revision = revision
+ self.timeout = timeout
+
+ def __str__(self):
+ return self.name
+
+ def __getstate__(self):
+ """
+ Used for serialization with pickle.
+ @return container content that will be serialized
+ """
+ return dict((k, getattr(self, k)) for k in self.__slots__)
+
+ def __setstate__(self, state):
+ """
+ Used for deserialization with pickle.
+ Restores the object from serialized form.
+ @param state is an object created by __setstate__() method
+ """
+ for k, value in state.items():
+ setattr(self, k, value)
+
+def make_repository_from_db(repo):
+ """
+ Create instance of Repository from instance of yum.yumRepo.YumRepository
+ @return instance of Repository
+ """
+ if not isinstance(repo, yum.yumRepo.YumRepository):
+ raise TypeError("repo must be in instance of yum.yumRepo.YumRepository")
+ metadata = dict(
+ (prop_name, get_prop_from_yum_repo(repo, prop_name))
+ for prop_name in Repository.__slots__[1:])
+ res = Repository(id(repo), **metadata)
+ return res
+
diff --git a/src/software/test/base.py b/src/software/test/base.py
index 4e56f46..318f47c 100644
--- a/src/software/test/base.py
+++ b/src/software/test/base.py
@@ -28,6 +28,7 @@ import tempfile
import unittest
from subprocess import check_output
+import repository
import rpmcache
def mark_dangerous(method):
@@ -91,10 +92,12 @@ class SoftwareBaseTestCase(unittest.TestCase): #pylint: disable=R0904
"""
CLASS_NAME = "Define in subclass"
+ KEYS = tuple()
# will be filled when first needed
# it's a dictionary with items (pkg_name, [file_path1, ...])
PKGDB_FILES = None
+ REPODB = None
@classmethod
def get_pkgdb_files(cls):
@@ -109,10 +112,30 @@ class SoftwareBaseTestCase(unittest.TestCase): #pylint: disable=R0904
return res
@classmethod
+ def get_repodb(cls):
+ """
+ @return list of Repository instances
+ """
+ if cls.REPODB is not None:
+ return cls.REPODB
+ SoftwareBaseTestCase.REPODB = res = repository.get_repo_database()
+ return res
+
+ @classmethod
+ def needs_pkgdb(cls):
+ """subclass may override this, if it does not need PKGDB database"""
+ return True
+
+ @classmethod
def needs_pkgdb_files(cls):
"""subclass may override this, if it needs PKGDB_FILES database"""
return False
+ @classmethod
+ def needs_repodb(cls):
+ """subclass may override this, if it needs REPODB database"""
+ return False
+
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
self.longMessage = True
@@ -120,7 +143,14 @@ class SoftwareBaseTestCase(unittest.TestCase): #pylint: disable=R0904
def setUp(self):
unittest.TestCase.setUp(self)
self.objpath = pywbem.CIMInstanceName(
- namespace="root/cimv2", classname=self.CLASS_NAME)
+ namespace="root/cimv2",
+ classname=self.CLASS_NAME,
+ keybindings=pywbem.NocaseDict(
+ dict((k, None) for k in self.KEYS)))
+ if "CreationClassName" in self.KEYS:
+ self.objpath["CreationClassName"] = self.CLASS_NAME
+ if "SystemCreationClassName" in self.KEYS:
+ self.objpath["SystemCreationClassName"] = "Linux_ComputerSystem"
def install_pkg(self, pkg, newer=True, repolist=None):
"""
@@ -187,19 +217,30 @@ class SoftwareBaseTestCase(unittest.TestCase): #pylint: disable=R0904
os.makedirs(cls.cache_dir)
# rpm packages are expected to be in CWD
os.chdir(cls.cache_dir)
- cls.safe_pkgs, cls.dangerous_pkgs = rpmcache.get_pkg_database(
- use_cache=use_cache,
- dangerous=cls.run_dangerous,
- repolist=cls.test_repos,
- cache_dir=cls.cache_dir if use_cache else None)
- for pkg in cls.dangerous_pkgs:
- if not rpmcache.is_pkg_installed(pkg.name):
- rpmcache.install_pkg(pkg, repolist=cls.test_repos)
- if cls.needs_pkgdb_files():
- cls.pkgdb_files = cls.get_pkgdb_files()
+ if cls.needs_pkgdb():
+ cls.safe_pkgs, cls.dangerous_pkgs = rpmcache.get_pkg_database(
+ use_cache=use_cache,
+ dangerous=cls.run_dangerous,
+ repolist=cls.test_repos,
+ cache_dir=cls.cache_dir if use_cache else None)
+ for pkg in cls.dangerous_pkgs:
+ if not rpmcache.is_pkg_installed(pkg.name):
+ rpmcache.install_pkg(pkg, repolist=cls.test_repos)
+ if cls.needs_pkgdb_files():
+ cls.pkgdb_files = cls.get_pkgdb_files()
+ else:
+ cls.safe_pkgs = []
+ cls.dangerous_pkgs = []
+ if cls.needs_repodb():
+ cls.repodb = cls.get_repodb()
@classmethod
def tearDownClass(cls):
+ if hasattr(cls, "repodb") and cls.repodb:
+ # set the enabled states to original values
+ for repo in cls.repodb:
+ if repository.is_repo_enabled(repo) != repo.status:
+ repository.set_repo_enabled(repo, repo.status)
if cls.run_dangerous:
for pkg in cls.dangerous_pkgs:
if rpmcache.is_pkg_installed(pkg.name):
diff --git a/src/software/test/repository.py b/src/software/test/repository.py
new file mode 100644
index 0000000..d881948
--- /dev/null
+++ b/src/software/test/repository.py
@@ -0,0 +1,175 @@
+#!/usr/bin/python
+# -*- Coding:utf-8 -*-
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details. #
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Authors: Michal Minar <miminar@redhat.com>
+"""
+Abstraction of YUM repository for test purposes.
+"""
+
+from collections import defaultdict, namedtuple
+from datetime import datetime
+import os
+import re
+from subprocess import call, check_output
+
+RE_REPO_TAG = re.compile(
+ ur'^repo-(?P<tag>[a-z_-]+)[\s\u00a0]*:'
+ ur'[\s\u00a0]*(?P<value>.*?)[\s\u00a0]*$', re.I | re.U | re.M)
+RE_REPO_INFOS = re.compile(
+ r'^(repo-id\s*:.*?)(?:^\s*$)',
+ re.IGNORECASE | re.MULTILINE | re.DOTALL)
+RE_REPO_CONFIG = re.compile(
+ ur'^(?P<tag>[a-z_-]+)[ \t\u00a0]*='
+ ur'[ \t\u00a0]*((?P<value>.*?)[ \t\u00a0]*)?$',
+ re.I | re.M | re.U)
+RE_REPO_ENABLED = re.compile(
+ r'^enabled\s*=\s*(true|false|0|1|yes|no)\s*$', re.I | re.M)
+
+Repository = namedtuple("Repository", #pylint: disable=C0103
+ # repo properties
+ [ "repoid"
+ , "name"
+ , "status"
+ , "revision"
+ , "tags"
+ , "last_updated"
+ , "pkg_count"
+ , "base_urls"
+ , "metalink"
+ , "mirror_list"
+ , "filename"
+ , "cost"
+ , "gpg_check"
+ , "repo_gpg_check"
+ ])
+
+# example of repo information
+#Repo-id : updates-testing/18/x86_64
+#Repo-name : Fedora 18 - x86_64 - Test Updates
+#Repo-status : enabled
+#Repo-revision: 1360887143
+#Repo-tags : binary-x86_64
+#Repo-updated : Fri Feb 15 02:02:20 2013
+#Repo-pkgs : 13 808
+#Repo-size : 14 G
+#Repo-baseurl : file:///mnt/globalsync/fedora/linux/updates/testing/18/x86_64
+#Repo-expire : 21 600 second(s) (last: Fri Feb 15 10:32:13 2013)
+#Repo-filename: ///etc/yum.repos.d/fedora-updates-testing.repo
+
+def _parse_repo_file(repo_name):
+ """
+ Parse output of yum-config-manager command for single repository.
+ @return dictionary with key-value pairs suitable for passing to Repository
+ constructor.
+ """
+ cmd = ["yum-config-manager", repo_name]
+ out = check_output(cmd).decode('utf-8')
+ result = {}
+ for match in RE_REPO_CONFIG.finditer(out):
+ tag = match.group('tag')
+ value = match.group('value')
+ if tag == "gpgcheck":
+ result["gpg_check"] = value.lower() == "true"
+ elif tag == "repo_gpgcheck":
+ result["repo_gpg_check"] = value.lower() == "true"
+ elif tag == "timeout":
+ result[tag] = float(value)
+ elif tag == "metadata_expire":
+ result[tag] = int(value)
+ elif tag == "cost":
+ result[tag] = int(value)
+ else:
+ continue
+ return result
+
+def make_repo(repo_info):
+ """
+ Makes a Repository instance from string dumped by yum repoinfo command.
+ """
+ metadata = defaultdict(lambda : None)
+ metadata["base_urls"] = []
+ fields = set(Repository._fields)
+ for match in RE_REPO_TAG.finditer(repo_info):
+ tag = match.group('tag')
+ value = match.group('value')
+ try:
+ new_tag = \
+ { 'id' : 'repoid'
+ , 'updated' : 'last_updated'
+ , 'pkgs' : 'pkg_count'
+ , 'baseurl' : 'base_urls'
+ , 'mirrors' : 'mirror_list'
+ }[tag]
+ except KeyError:
+ if tag not in fields:
+ continue # unexpeted, or unwanted tag found
+ new_tag = tag
+ if new_tag == 'repoid':
+ metadata[new_tag] = value.split('/')[0]
+ elif new_tag == "base_urls":
+ metadata[new_tag].append(value)
+ elif new_tag == "last_updated":
+ metadata[new_tag] = datetime.strptime(
+ value, "%a %b %d %H:%M:%S %Y")
+ elif new_tag == "pkg_count":
+ metadata[new_tag] = int(
+ u"".join(re.split(ur'[\s\u00a0]+', value, re.UNICODE)))
+ elif new_tag == "status":
+ metadata[new_tag] = value == "enabled"
+ elif new_tag == 'revision':
+ metadata[new_tag] = int(value)
+ else:
+ metadata[new_tag] = value
+ config_items = _parse_repo_file(metadata['repoid'])
+ for field in Repository._fields:
+ if not field in metadata:
+ metadata[field] = config_items.get(field, None)
+ return Repository(**dict((f, metadata[f]) for f in Repository._fields))
+
+def get_repo_database():
+ """
+ @return list of Repository instances for all configured repositories.
+ """
+ result = []
+ cmd = ["yum", "-q", "-C", "repoinfo", "all"]
+ repo_infos = check_output(cmd).decode('utf-8')
+ for match in RE_REPO_INFOS.finditer(repo_infos):
+ result.append(make_repo(match.group(1)))
+ return result
+
+def is_repo_enabled(repo):
+ """
+ @return True, if repository is enabled
+ """
+ if isinstance(repo, Repository):
+ repo = repo.repoid
+ cmd = ["yum-config-manager", repo]
+ out = check_output(cmd)
+ match = RE_REPO_ENABLED.search(out)
+ return bool(match) and match.group(1).lower() in {"true", "yes", "1"}
+
+def set_repo_enabled(repo, enable):
+ """
+ Enables or disables repository in its configuration file.
+ """
+ if isinstance(repo, Repository):
+ repo = repo.repoid
+ cmd = ["yum-config-manager", "--enable" if enable else "--disable", repo]
+ with open('/dev/null', 'w') as out:
+ call(cmd, stdout=out, stderr=out)
+
diff --git a/src/software/test/test_hosted_software_collection.py b/src/software/test/test_hosted_software_collection.py
index dc798d8..bdac158 100755
--- a/src/software/test/test_hosted_software_collection.py
+++ b/src/software/test/test_hosted_software_collection.py
@@ -36,6 +36,10 @@ class TestHostedSoftwareCollection(base.SoftwareBaseTestCase):
CLASS_NAME = "LMI_HostedSoftwareCollection"
KEYS = ("Antecedent", "Dependent")
+ @classmethod
+ def needs_pkgdb(cls):
+ return False
+
def make_op(self):
"""
@param ses SoftwareElementState property value
diff --git a/src/software/test/test_software_identity_resource.py b/src/software/test/test_software_identity_resource.py
new file mode 100755
index 0000000..f728095
--- /dev/null
+++ b/src/software/test/test_software_identity_resource.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+#
+# 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>
+#
+"""
+Unit tests for LMI_SoftwareIdentityResource provider.
+"""
+
+from datetime import datetime
+import os
+import pywbem
+import socket
+import time
+import unittest
+
+import base
+import repository
+
+class TestSoftwareIdentityResource(
+ base.SoftwareBaseTestCase): #pylint: disable=R0904
+ """
+ Basic cim operations test.
+ """
+
+ CLASS_NAME = "LMI_SoftwareIdentityResource"
+ KEYS = ("CreationClassName", "Name", "SystemCreationClassName",
+ "SystemName")
+
+ @classmethod
+ def needs_pkgdb(cls):
+ return False
+
+ @classmethod
+ def needs_repodb(cls):
+ return True
+
+ def make_op(self, repo):
+ """
+ @param ses SoftwareElementState property value
+ @return object path of SoftwareIdentityResource
+ """
+ if isinstance(repo, repository.Repository):
+ repo = repo.repoid
+ objpath = self.objpath.copy()
+ objpath["Name"] = repo
+ objpath["SystemName"] = socket.gethostname()
+ return objpath
+
+ def tearDown(self):
+ for repo in self.repodb:
+ if repository.is_repo_enabled(repo) != repo.status:
+ repository.set_repo_enabled(repo, repo.status)
+
+ def _check_repo_instance(self, repo, inst):
+ """
+ Checks instance properties of repository.
+ """
+ objpath = self.make_op(repo)
+ self.assertEqual(objpath, inst.path)
+ for key in self.KEYS:
+ self.assertEqual(inst[key], inst.path[key])
+ self.assertEqual(repo.repoid, inst["Name"])
+ self.assertIsInstance(inst["AccessContext"], pywbem.Uint16)
+ self.assertIsInstance(inst["AccessInfo"], basestring)
+ if repo.mirror_list:
+ self.assertEqual(inst["AccessInfo"], repo.mirror_list,
+ "AccessInfo does not match mirror_list for repo %s" %
+ repo.repoid)
+ elif repo.metalink:
+ self.assertEqual(inst["AccessInfo"], repo.metalink,
+ "AccessInfo does not match metalink for repo %s" %
+ repo.repoid)
+ else:
+ self.assertIn(inst["AccessInfo"], repo.base_urls,
+ "AccessInfo missing in base_urls for repo %s" % repo.repoid)
+ self.assertIsInstance(inst["AvailableRequestedStates"], list)
+ self.assertEqual(repo.name, inst["Caption"])
+ self.assertIsInstance(inst["Cost"], pywbem.Sint32)
+ self.assertIsInstance(inst["Description"], basestring)
+ self.assertEqual(repo.repoid, inst["ElementName"])
+ self.assertEqual(5, inst["EnabledDefault"])
+ self.assertEqual(2 if repo.status else 3, inst["EnabledState"],
+ "EnabledState does not match for repo %s" % repo.repoid)
+ self.assertEqual(3, inst["ExtendedResourceType"])
+ if repo.revision is None:
+ self.assertTrue(isinstance(inst["Generation"], (int, long))
+ or inst["Generation"] is None)
+ else:
+ self.assertEqual(repo.revision, inst["Generation"],
+ "Generation does not match for repo %s" % repo.repoid)
+ self.assertEqual(5, inst["HealthState"])
+ self.assertIsInstance(inst["GPGCheck"], bool)
+ self.assertEqual(200, inst["InfoFormat"])
+ self.assertEqual("LMI:REPO:"+repo.repoid, inst["InstanceID"])
+ if repo.mirror_list is None and repo.metalink is None:
+ self.assertIsNone(inst["MirrorList"])
+ else:
+ self.assertEqual(
+ repo.metalink if repo.metalink else repo.mirror_list,
+ inst["MirrorList"])
+ self.assertEqual([2], inst["OperationalStatus"])
+ self.assertIsInstance(inst["OtherAccessContext"], basestring)
+ #self.assertEqual(repo.pkg_count, inst["PackageCount"],
+ #"PackageCount does not match for repo %s" % repo.repoid)
+ self.assertEqual(1, inst["PrimaryStatus"])
+ self.assertIsInstance(inst["RepoGPGCheck"], bool)
+ self.assertEqual(2 if repo.status else 3, inst["RequestedState"])
+ self.assertEqual(1, inst["ResourceType"])
+ self.assertIsInstance(inst["StatusDescriptions"], list)
+ self.assertEqual(1, len(inst["StatusDescriptions"]))
+ if repo.last_updated is None:
+ self.assertTrue(
+ isinstance(inst["TimeOfLastUpdate"], pywbem.CIMDateTime) or
+ inst["TimeOfLastUpdate"] is None)
+ else:
+ time_stamp = repo.last_updated.replace(
+ microsecond=0, tzinfo=pywbem.cim_types.MinutesFromUTC(0))
+ self.assertEqual(time_stamp, inst["TimeOfLastUpdate"].datetime)
+ time_stamp = datetime.fromtimestamp(os.stat(repo.filename).st_mtime) \
+ .replace( microsecond=0
+ , tzinfo=pywbem.cim_types.MinutesFromUTC(0))
+ self.assertEqual(time_stamp, inst["TimeOfLastStateChange"].datetime)
+ self.assertEqual(12, inst["TransitioningToState"])
+
+ def test_get_instance_safe(self):
+ """
+ Tests GetInstance call.
+ """
+ for repo in self.repodb:
+ objpath = self.make_op(repo)
+ inst = self.conn.GetInstance(InstanceName=objpath)
+ self._check_repo_instance(repo, inst)
+
+ @base.mark_dangerous
+ def test_get_instance(self):
+ """
+ Dangerous test, making sure, that properties are set correctly
+ for enabled and disabled repositories.
+ """
+ for repo in self.repodb:
+ objpath = self.make_op(repo)
+ self.assertIs(repository.is_repo_enabled(repo), repo.status)
+ inst = self.conn.GetInstance(InstanceName=objpath)
+ self.assertEqual(objpath, inst.path)
+ for key in self.KEYS:
+ self.assertEqual(inst[key], inst.path[key])
+ self.assertEqual(repo.repoid, inst["Name"])
+ self.assertEqual(2 if repo.status else 3, inst["EnabledState"])
+ self.assertEqual(2 if repo.status else 3, inst["RequestedState"])
+ for repo in self.repodb:
+ objpath = self.make_op(repo)
+ time.sleep(1) # to make sure, that change will be noticed
+ repository.set_repo_enabled(repo, not repo.status)
+ self.assertIs(repository.is_repo_enabled(repo), not repo.status)
+ inst = self.conn.GetInstance(InstanceName=objpath)
+ self.assertEqual(repo.repoid, inst["Name"])
+ self.assertEqual(3 if repo.status else 2, inst["EnabledState"])
+ self.assertEqual(3 if repo.status else 2, inst["RequestedState"])
+
+ def test_enum_instance_names(self):
+ """
+ Tests EnumInstanceNames call on all repositories.
+ """
+ inames = self.conn.EnumerateInstanceNames(ClassName=self.CLASS_NAME)
+ self.assertEqual(len(inames), len(self.repodb))
+ repoids = set(r.repoid for r in self.repodb)
+ for iname in inames:
+ self.assertIsInstance(iname, pywbem.CIMInstanceName)
+ self.assertEqual(iname.namespace, 'root/cimv2')
+ self.assertEqual(sorted(iname.keys()), sorted(self.KEYS))
+ self.assertIn(iname["Name"], repoids)
+ objpath = self.make_op(iname["Name"])
+ self.assertEqual(objpath, iname)
+
+ def test_enum_instances(self):
+ """
+ Tests EnumInstances call on all repositories.
+ """
+ insts = self.conn.EnumerateInstances(ClassName=self.CLASS_NAME)
+ self.assertGreater(len(insts), 0)
+ repoids = dict((r.repoid, r) for r in self.repodb)
+ for inst in insts:
+ self.assertIsInstance(inst, pywbem.CIMInstance)
+ self.assertIn(inst["Name"], repoids)
+ self._check_repo_instance(repoids[inst["Name"]], inst)
+
+def suite():
+ """For unittest loaders."""
+ return unittest.TestLoader().loadTestsFromTestCase(
+ TestSoftwareIdentityResource)
+
+if __name__ == '__main__':
+ unittest.main()