diff options
author | Michal Minar <miminar@redhat.com> | 2013-02-18 11:06:24 +0100 |
---|---|---|
committer | Michal Minar <miminar@redhat.com> | 2013-02-18 11:06:24 +0100 |
commit | 4214208d559ed88f3b29bc046d4679824bf5faa7 (patch) | |
tree | 0a70987b073893ef9ebb33e8f7421560a4b05984 /src/software | |
parent | 7c5dc2d2e73b08f20df94c3acd370f02ab0102c0 (diff) | |
download | openlmi-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/software')
-rw-r--r-- | src/software/openlmi/software/LMI_SoftwareIdentityResource.py | 277 | ||||
-rw-r--r-- | src/software/openlmi/software/cimom_entry.py | 5 | ||||
-rw-r--r-- | src/software/openlmi/software/core/SoftwareIdentity.py | 2 | ||||
-rw-r--r-- | src/software/openlmi/software/core/SoftwareIdentityResource.py | 641 | ||||
-rw-r--r-- | src/software/openlmi/software/yumdb/__init__.py | 27 | ||||
-rw-r--r-- | src/software/openlmi/software/yumdb/errors.py | 8 | ||||
-rw-r--r-- | src/software/openlmi/software/yumdb/jobs.py | 68 | ||||
-rw-r--r-- | src/software/openlmi/software/yumdb/process.py | 153 | ||||
-rw-r--r-- | src/software/openlmi/software/yumdb/repository.py | 190 | ||||
-rw-r--r-- | src/software/test/base.py | 63 | ||||
-rw-r--r-- | src/software/test/repository.py | 175 | ||||
-rwxr-xr-x | src/software/test/test_hosted_software_collection.py | 4 | ||||
-rwxr-xr-x | src/software/test/test_software_identity_resource.py | 209 |
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() |