diff options
author | Peter Schiffer <pschiffe@redhat.com> | 2013-07-18 14:15:54 +0200 |
---|---|---|
committer | Peter Schiffer <pschiffe@redhat.com> | 2013-07-18 14:15:54 +0200 |
commit | 7c5b2d12272abd8f9dd0057ea10a8578f492d2dd (patch) | |
tree | a492bf9ad6b0632a7827c0cdbd8b7a3c85c607f8 | |
parent | ec3391938be3058f11036bade813ba4239d7103b (diff) | |
parent | 30d91d80793392adb4c5b3890555662caa0031da (diff) | |
download | openlmi-providers-7c5b2d12272abd8f9dd0057ea10a8578f492d2dd.tar.gz openlmi-providers-7c5b2d12272abd8f9dd0057ea10a8578f492d2dd.tar.xz openlmi-providers-7c5b2d12272abd8f9dd0057ea10a8578f492d2dd.zip |
Merge branch 'master' of ssh://git.fedorahosted.org/git/openlmi-providers
27 files changed, 1014 insertions, 116 deletions
@@ -64,6 +64,11 @@ Following providers are part of this sub-project: This is a CIM interface for the RealmD daemon which allows for the Kerberos and Active Directory realms enrollment +* PCP + This is a CIM interface for the PCP (Performance Co-Pilot) daemon. Allows + reading of PCP metrics on the local host. + + ******************************************************************************* * Build Dependencies * ******************************************************************************* @@ -90,6 +95,8 @@ Provider specific dependencies: * RealmD - glib2-devel - dbus-devel +* PCP + - python-pcp ******************************************************************************* * Compilation and installation * diff --git a/mof/60_LMI_PCP.mof b/mof/60_LMI_PCP.mof new file mode 100644 index 0000000..8ba122b --- /dev/null +++ b/mof/60_LMI_PCP.mof @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * Stub MOF for the CIM<->PCP bridge. Further MOF/REG clauses that + * describe the PCP PMNS details may be generated periodically using + * pcp2cim.sh. + * + * 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: Frank Ch. Eigler <fche@redhat.com> + */ +class PCP_MetricValue : CIM_StatisticalData +{ + [Description("PCP metric PMID")] + uint32 PMID; + + [Description("PMAPI indom instance number")] + uint32 InstanceNumber; + + [Description("PMAPI indom instance name")] + string InstanceName; + + [Description("The metric type, as returned by pmTypeStr(3)")] + string Type; + + [Description("The metric value, as rendered into string form by pmAtomStr() or pmPrintValue(3)")] + string ValueString; + + [Description("The metric units, as returned by pmUnitsStr(3)")] + string Units; +}; + diff --git a/openlmi-mof-register b/openlmi-mof-register index d327a1e..efc9df3 100755 --- a/openlmi-mof-register +++ b/openlmi-mof-register @@ -17,6 +17,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Authors: Radek Novacek <rnovacek@redhat.com> +# Jan Safranek <jsafrane@redhat.com> # pegasus_repository="/var/lib/Pegasus/" @@ -44,12 +45,15 @@ function stop_pegasus() function usage() { - printf "Usage: $0 [ --just-mofs ] [ -n namespace ] [ -c cimom ] + printf "Usage: $0 [ --just-mofs ] [ -n namespace ] [ -c cimom ] [-v version] CMD <mof> [mof] [...] [reg] CMD is one of [ register, unregister ] Default namespace is $default_namespace, which can be changed with '-n' option. + If a registration file is provided, '-v' parameter is mandatory and specifies + version of the provider API. + Supported cimoms are sfcbd and tog-pegasus. Without \"-c\" argument, the operation is processed for any cimom present on system (all of them). @@ -57,9 +61,9 @@ function usage() treated as mof files - no registration file is expected. usage with --just-mofs: - $0 CMD <mof> [mof] [...] + $0 --just-mofs CMD <mof> [mof] [...] usage without: - $0 CMD <mof> [mof] [...] <reg>\n" + $0 -v <version> CMD <mof> [mof] [...] <reg>\n" } @@ -91,9 +95,9 @@ function register() if [ $JUST_MOFS -eq 0 ]; then if [ -x $(dirname $0)/openlmi-register-pegasus ]; then - cat "$reg" | $(dirname $0)/openlmi-register-pegasus | $CIMMOF -uc -n root/PG_Interop + cat "$reg" | $(dirname $0)/openlmi-register-pegasus -v "$version" | $CIMMOF -uc -n root/PG_Interop else - cat "$reg" | /usr/libexec/openlmi-register-pegasus | $CIMMOF -uc -n root/PG_Interop + cat "$reg" | /usr/libexec/openlmi-register-pegasus -v "$version" | $CIMMOF -uc -n root/PG_Interop fi fi fi @@ -141,7 +145,7 @@ function unregister() } JUST_MOFS=0 -optspec=":hn:c:-:" +optspec=":hn:c:v:-:" while getopts "$optspec" optchar; do case "$optchar" in @@ -176,6 +180,9 @@ while getopts "$optspec" optchar; do usage; exit 0; ;; + v) + version="$OPTARG" + ;; *) if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then echo "Non-option argument: '-${OPTARG}'" >&2 @@ -185,10 +192,17 @@ while getopts "$optspec" optchar; do esac done + shift $(($OPTIND - 1)) namespace=${namespace:-$default_namespace} cimom=${cimom:-all} +if [ "$JUST_MOFS" -eq 0 -a -z "$version" ]; then + echo "Missing -v option" + usage + exit 1 +fi + if [ $# -lt 2 ]; then usage diff --git a/openlmi-providers.spec b/openlmi-providers.spec index 6e6d8b7..2d181bf 100644 --- a/openlmi-providers.spec +++ b/openlmi-providers.spec @@ -1,6 +1,6 @@ Name: openlmi-providers Version: 0.0.25 -Release: 5%{?dist} +Release: 6%{?dist} Summary: Set of basic CIM providers License: LGPLv2+ @@ -177,6 +177,21 @@ Requires: %{name}%{?_isa} = %{version}-%{release} %description -n openlmi-indicationmanager-libs-devel %{summary}. +%package -n openlmi-pcp +Summary: pywbem providers for accessing PCP metrics +Requires: %{name} = %{version}-%{release} +BuildArch: noarch +Requires: python-setuptools +Requires: cmpi-bindings-pywbem +Requires: python-pcp + +%description -n openlmi-pcp +openlmi-pcp exposes metrics from a local PMCD (Performance Co-Pilot server) +to the CIMOM. They appear as potentially hundreds of MOF classes, e.g. +class "PCP_Metric_kernel__pernode__cpu__use", with instances for each PCP +metric instance, e.g. "node0". PCP metric values and metadata are transcribed +into strings on demand. + %prep %setup -q @@ -186,7 +201,7 @@ pushd %{_target_platform} %{cmake} .. popd -make %{?_smp_mflags} -C %{_target_platform} +make -k %{?_smp_mflags} -C %{_target_platform} pushd src/python %{__python} setup.py build @@ -195,6 +210,9 @@ popd # src/python pushd src/software %{__python} setup.py build popd # src/software +pushd src/pcp +%{__python} setup.py build +popd %install make install/fast DESTDIR=$RPM_BUILD_ROOT -C %{_target_platform} @@ -217,6 +235,21 @@ install -m 755 pycmpiLMI_Software-cimprovagt $RPM_BUILD_ROOT/%{_libexecdir}/pega popd # src/software cp mof/LMI_Software.reg $RPM_BUILD_ROOT/%{_datadir}/%{name}/ +# pcp +pushd src/pcp +%{__python} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT +popd +cp -p src/pcp/openlmi-pcp-generate $RPM_BUILD_ROOT/%{_bindir}/openlmi-pcp-generate +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily +cp -p src/pcp/openlmi-pcp.cron $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily/openlmi-pcp +sed -i -e 's,^_LOCALSTATEDIR=.*,_LOCALSTATEDIR="%{_localstatedir}",' \ + -e 's,^_DATADIR=.*,_DATADIR="%{_datadir}",' \ + -e 's,^NAME=.*,NAME="%{name}",' \ + -e 's,^PYTHON2_SITELIB=.*,PYTHON2_SITELIB="%{python2_sitelib}",' \ + $RPM_BUILD_ROOT/%{_bindir}/openlmi-pcp-generate \ + $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily/openlmi-pcp +mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/lib/%{name} + %files %doc README COPYING @@ -290,6 +323,19 @@ cp mof/LMI_Software.reg $RPM_BUILD_ROOT/%{_datadir}/%{name}/ %{_datadir}/%{name}/70_LMI_SoftwareIndicationFilters.mof %{_datadir}/%{name}/LMI_Software.reg +%files -n openlmi-pcp +%doc README COPYING +%{_datadir}/%{name}/60_LMI_PCP.mof +%dir %{python_sitelib}/lmi/pcp +%{python_sitelib}/lmi/pcp/* +%{python_sitelib}/lmi_pcp-* +%attr(755, root, root) %{_bindir}/openlmi-pcp-generate +%attr(755, root, root) %{_sysconfdir}/cron.daily/openlmi-pcp +%dir %{_localstatedir}/lib/%{name} +%ghost %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.mof +%ghost %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.reg +%ghost %{_localstatedir}/lib/%{name}/stamp + %files -n openlmi-logicalfile %doc README COPYING %{_libdir}/cmpi/libcmpiLMI_LogicalFile.so @@ -435,6 +481,7 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi + %pre -n openlmi-hardware if [ "$1" -gt 1 ]; then %{_bindir}/openlmi-mof-register unregister \ @@ -443,6 +490,15 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi +%pre -n openlmi-pcp +if [ "$1" -gt 1 ]; then + %{_bindir}/openlmi-mof-register unregister \ + %{_datadir}/%{name}/60_LMI_PCP.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.reg \ + > /dev/null 2>&1 || :; +fi + %post -n openlmi-fan # Register Schema and Provider if [ "$1" -ge 1 ]; then @@ -518,6 +574,15 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi +%post -n openlmi-pcp +if [ "$1" -gt 1 ]; then + %{_bindir}/openlmi-mof-register register \ + %{_datadir}/%{name}/60_LMI_PCP.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.reg \ + > /dev/null 2>&1 || :; +fi + %preun -n openlmi-fan # Deregister only if not upgrading if [ "$1" -eq 0 ]; then @@ -585,6 +650,7 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi + %preun -n openlmi-hardware if [ "$1" -gt 1 ]; then %{_bindir}/openlmi-mof-register unregister \ @@ -593,7 +659,19 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi +%preun -n openlmi-pcp +if [ "$1" -gt 1 ]; then + %{_bindir}/openlmi-mof-register unregister \ + %{_datadir}/%{name}/60_LMI_PCP.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.reg \ + > /dev/null 2>&1 || :; +fi + %changelog +* Mon Jul 08 2013 Frank Ch. Eigler <fche@redhat.com> 0.0.25-6 +- Added PCP provider in optional openlmi-pcp subrpm. + * Mon Jul 15 2013 Jan Synáček <jsynacek@redhat.com> - 0.0.25-5 - Added libselinux-devel to BuildRequires. diff --git a/openlmi-register-pegasus b/openlmi-register-pegasus index d314d27..149af59 100755 --- a/openlmi-register-pegasus +++ b/openlmi-register-pegasus @@ -17,10 +17,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Authors: Radek Novacek <rnovacek@redhat.com> +# Jan Safranek <jsafrane@redhat.com> # import sys import re +import argparse reg_parse = re.compile(r"\[([^\]]+)\]\s+" "provider: ([^\s]+)\s+" @@ -38,18 +40,18 @@ Types = { 'instanceQuery': '7' } -def define_module(location, group): +def define_module(location, group, version): return """instance of PG_ProviderModule { Name = "%(group)s"; Location = "%(location)s"; Vendor = "OpenLMI"; - Version = "0.0.1"; + Version = "%(version)s"; InterfaceType = "CMPI"; InterfaceVersion = "2.0.0"; ModuleGroupName = "%(group)s"; }; -""" % { 'location': location, 'group': group } +""" % { 'location': location, 'group': group, 'version': version } def getTypes(types): l = [] @@ -78,6 +80,11 @@ instance of PG_ProviderCapabilities }; """ % { 'location': location, 'provider': provider, 'class': cls, 'types': getTypes(types), 'group': group } +parser = argparse.ArgumentParser(description='Create MOF files with registration of providers for Pegasus CIMOM.') +parser.add_argument('-v', '--version', action='store', required=True, help="Version of the provider API.") +args = parser.parse_args() +version = args.version + modules_defined = {} for record in reg_parse.findall(sys.stdin.read()): cls, provider, location, types, namespace, _unused, group = record @@ -86,7 +93,7 @@ for record in reg_parse.findall(sys.stdin.read()): group = location if group not in modules_defined: - print define_module(location, group) + print define_module(location, group, version) modules_defined[group] = True print define_capability(location, provider, cls, types, group) diff --git a/src/account/test/TestAccount.py b/src/account/test/TestAccount.py index e6f0c14..5763c4d 100644 --- a/src/account/test/TestAccount.py +++ b/src/account/test/TestAccount.py @@ -55,7 +55,7 @@ class TestAccount(AccountBase): self.wbemconnection.InvokeMethod("CreateAccount", lams.path, Name=self.user_name, System=computer_system.path) # The user now should be created, check it - subprocess.check_call(["id", self.user_name]) + self.assertTrue(user_exists(self.user_name)) # now delete that user clean_account(self.user_name) @@ -69,9 +69,7 @@ class TestAccount(AccountBase): 'select * from LMI_Account where Name = "%s"' % self.user_name)[0] self.wbemconnection.DeleteInstance(i.path) # check if it was really deleted - c = subprocess.Popen(["id", self.user_name]) - c.communicate() - self.assertEqual(c.returncode, 1) + self.assertFalse(user_exists(self.user_name)) clean_account(self.user_name) def test_modify_account(self): @@ -82,9 +80,7 @@ class TestAccount(AccountBase): i = self.wbemconnection.ExecQuery('WQL', 'select * from LMI_Account where Name = "%s"' % self.user_name)[0] # gecos - print i["ElementName"] i["ElementName"] = "GECOS" - print i["ElementName"] self.wbemconnection.ModifyInstance(i) self.assertEqual(field_in_passwd(self.user_name, 4), "GECOS") # login shell diff --git a/src/account/test/TestIndications.py b/src/account/test/TestIndications.py new file mode 100644 index 0000000..7b34b2e --- /dev/null +++ b/src/account/test/TestIndications.py @@ -0,0 +1,100 @@ +# Copyright (C) 2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Roman Rakus <rrakus@redhat.com> +# + +from common import AccountBase +import time +from methods import * + +class TestIndications(AccountBase): + """ + Class for testing LMI_Account indications + """ + def test_check_good_filter(self): + """ + Account: Test good indication filter + """ + filter_name = "test_good_filter_%d" % time.time() * 1000 + sub = self.subscribe(filter_name, "select * from LMI_AccountInstanceCreationIndication where SourceInstance isa LMI_Account") + self.assertIsNotNone(sub) + self.unsubscribe(filter_name); + + def test_check_bad_filter(self): + """ + Account: Test bad indication filter + """ + pass + + def test_group_deletion_indication(self): + """ + Account: Test indication of group deletion + """ + create_group(self.group_name) + filter_name = "test_delete_group_%d" % time.time() * 1000 + sub = self.subscribe(filter_name, "select * from LMI_AccountInstanceDeletionIndication where SourceInstance isa LMI_Group") + clean_group(self.group_name) + indication = self.get_indication(10) + self.assertEqual(indication.classname, "LMI_AccountInstanceDeletionIndication") + self.assertIn("SourceInstance", indication.keys()) + self.assertTrue(indication["SourceInstance"] is not None) + self.assertEqual(indication["SourceInstance"]["Name"], self.group_name) + + def test_group_creation_indication(self): + """ + Account: Test indication of group creation + """ + clean_group(self.group_name) + filter_name = "test_create_group_%d" % time.time() * 1000 + sub = self.subscribe(filter_name, "select * from LMI_AccountInstanceCreationIndication where SourceInstance isa LMI_Group") + create_group(self.group_name) + indication = self.get_indication(10) + self.assertEqual(indication.classname, "LMI_AccountInstanceCreationIndication") + self.assertIn("SourceInstance", indication.keys()) + self.assertTrue(indication["SourceInstance"] is not None) + self.assertEqual(indication["SourceInstance"]["Name"], self.group_name) + clean_group(self.group_name) + + def test_account_deletion_indication(self): + """ + Account: Test indication of account deletion + """ + create_account(self.user_name) + filter_name = "test_delete_account_%d" % time.time() * 1000 + sub = self.subscribe(filter_name, "select * from LMI_AccountInstanceDeletionIndication where SourceInstance isa LMI_Account") + clean_account(self.user_name) + indication = self.get_indication(10) + self.assertEqual(indication.classname, "LMI_AccountInstanceDeletionIndication") + self.assertIn("SourceInstance", indication.keys()) + self.assertTrue(indication["SourceInstance"] is not None) + self.assertEqual(indication["SourceInstance"]["Name"], self.user_name) + + def test_account_creation_indication(self): + """ + Account: Test indication of account creation + """ + clean_account(self.user_name) + filter_name = "test_create_account_%d" % time.time() * 1000 + sub = self.subscribe(filter_name, "select * from LMI_AccountInstanceCreationIndication where SourceInstance isa LMI_Account") + create_account(self.user_name) + indication = self.get_indication(10) + self.assertEqual(indication.classname, "LMI_AccountInstanceCreationIndication") + self.assertIn("SourceInstance", indication.keys()) + self.assertTrue(indication["SourceInstance"] is not None) + self.assertEqual(indication["SourceInstance"]["Name"], self.user_name) + clean_account(self.user_name) + diff --git a/src/account/test/TestMemberOfGroup.py b/src/account/test/TestMemberOfGroup.py index 635ac24..211244e 100644 --- a/src/account/test/TestMemberOfGroup.py +++ b/src/account/test/TestMemberOfGroup.py @@ -53,9 +53,7 @@ class TestMemberOfGroup(AccountBase): 'select * from LMI_Account where Name = "%s"' % self.user_name)[0] self.wbemconnection.DeleteInstance(i.path) # check if it was really deleted - c = subprocess.Popen(["id", self.user_name]) - c.communicate() - self.assertEqual(c.returncode, 1) + self.assertFalse(user_exists(self.user_name)) clean_account(self.user_name) def test_user_in_groups(self): diff --git a/src/account/test/common.py b/src/account/test/common.py index c56e94d..39442af 100644 --- a/src/account/test/common.py +++ b/src/account/test/common.py @@ -21,11 +21,56 @@ import pywbem import os import unittest +import Queue +import random +import BaseHTTPServer +import socket +import threading """ Base class for all tests """ +class CIMListener(object): + """ CIM Listener + """ + class CIMHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_POST(self): + data = self.rfile.read(int(self.headers['Content-Length'])) + tt = pywbem.parse_cim(pywbem.xml_to_tupletree(data)) + # Get the instance from CIM-XML, copied from + # http://sf.net/apps/mediawiki/pywbem/?title=Indications_Tutorial + insts = [x[1] for x in tt[2][2][0][2][2]] + for inst in insts: + self.callback(inst) + self.send_response(200) + self.end_headers() + + def log_message(self, format, *p): + # suppress log messages + pass + + def __init__(self, callback, http_port=5988): + self.address = ('', http_port) + self.CIMHandler.callback = callback + self.thread = None + self.server = None + + def start(self): + BaseHTTPServer.HTTPServer.allow_reuse_address = True + self.server = BaseHTTPServer.HTTPServer(self.address, self.CIMHandler) + self.thread = threading.Thread(target=self.server.serve_forever) + self.thread.start() + + def stop(self): + if self.server is not None: + self.server.shutdown() + self.server.socket.close() + if self.thread is not None: + self.thread.join() + + def running(self): + return self.thread is not None class AccountBase(unittest.TestCase): @@ -44,3 +89,116 @@ class AccountBase(unittest.TestCase): self.wbemconnection = pywbem.WBEMConnection(self.url, (self.username, self.password)) + # for indications + self.indication_port = random.randint(12000, 13000) + self.indication_queue = Queue.Queue() + self.listener = CIMListener( + callback=self._process_indication, + http_port=self.indication_port) + + self.subscribed = {} + + def tearDown(self): + self.listener.stop() + if self.subscribed: + for name in self.subscribed.keys(): + self.unsubscribe(name) + + def get_indication(self, timeout): + """ Wait for an indication for given nr. of seconds and return it.""" + try: + indication = self.indication_queue.get(timeout=timeout) + except Queue.Empty: + raise AssertionError("Timeout when waiting for indicaiton") + self.indication_queue.task_done() + return indication + + def subscribe(self, filter_name, query=None, querylang="DMTF:CQL"): + """ + Create indication subscription for given filter name. + """ + namespace = "root/PG_interop" + hostname = socket.gethostname() + + if query is not None: + # Create filter first + filterinst = pywbem.CIMInstance('CIM_IndicationFilter') + filterinst['CreationClassName'] = 'CIM_IndicationFilter' + filterinst['SystemCreationClassName'] = 'CIM_ComputerSystem' + filterinst['SystemName'] = hostname + filterinst['Name'] = filter_name + filterinst['Query'] = query + filterinst['QueryLanguage'] = querylang + filterinst['SourceNamespace'] = "root/cimv2"#namespace + cop = pywbem.CIMInstanceName('CIM_IndicationFilter') + cop.keybindings = { 'CreationClassName': 'CIM_IndicationFilter', + 'SystemClassName': 'CIM_ComputerSystem', + 'SystemName': hostname, + 'Name': filter_name + } + cop.namespace=namespace + filterinst.path = cop + indfilter = self.wbemconnection.CreateInstance(filterinst) + else: + # the filter is already created, assemble its name + indfilter = pywbem.CIMInstanceName( + classname="CIM_IndicationFilter", + namespace=namespace, + keybindings={ + 'CreationClassName': 'CIM_IndicationFilter', + 'SystemClassName': 'CIM_ComputerSystem', + 'SystemName': hostname, + 'Name': filter_name}) + + # create destination + destinst = pywbem.CIMInstance('CIM_ListenerDestinationCIMXML') + destinst['CreationClassName'] = 'CIM_ListenerDestinationCIMXML' + destinst['SystemCreationClassName'] = 'CIM_ComputerSystem' + destinst['SystemName'] = hostname + destinst['Name'] = filter_name + destinst['Destination'] = "http://localhost:%d" % (self.indication_port) + destinst['PersistenceType'] = pywbem.Uint16(3) # Transient + cop = pywbem.CIMInstanceName('CIM_ListenerDestinationCIMXML') + cop.keybindings = { 'CreationClassName':'CIM_ListenerDestinationCIMXML', + 'SystemClassName':'CIM_ComputerSystem', + 'SystemName':hostname, + 'Name':filter_name } + cop.namespace = namespace + destinst.path = cop + destname = self.wbemconnection.CreateInstance(destinst) + + # create the subscription + subinst = pywbem.CIMInstance('CIM_IndicationSubscription') + subinst['Filter'] = indfilter + subinst['Handler'] = destname + cop = pywbem.CIMInstanceName('CIM_IndicationSubscription') + cop.keybindings = { 'Filter': indfilter, + 'Handler': destname } + cop.namespace = namespace + subinst.path = cop + subscription = self.wbemconnection.CreateInstance(subinst) + + self.subscribed[filter_name] = [subscription, destname] + + # start listening + if not self.listener.running(): + self._start_listening() + return subscription + + def unsubscribe(self, filter_name): + """ + Unsubscribe fron given filter. + """ + _list = self.subscribed.pop(filter_name) + for instance in _list: + self.wbemconnection.DeleteInstance(instance) + + def _start_listening(self): + """ Start listening for incoming indications. """ + self.listener.start() + + def _process_indication(self, indication): + """ Callback to process one indication.""" + self.indication_queue.put(indication) + + diff --git a/src/account/test/methods.py b/src/account/test/methods.py index c0407df..ffb9f7d 100644 --- a/src/account/test/methods.py +++ b/src/account/test/methods.py @@ -20,6 +20,20 @@ import subprocess +def user_exists(username): + """ + Return true/false if user does/does not exists + """ + got = field_in_passwd(username, 0) + return got == username + +def group_exists(groupname): + """ + Return true/false if user does/does not exists + """ + got = field_in_group(groupname, 0) + return got == groupname + def field_in_passwd(username, number): """ Return numberth field in /etc/passwd for given username @@ -48,33 +62,36 @@ def clean_account(user_name): """ Force to delete testing account and remove home dir """ - subprocess.call(["userdel", "-fr", user_name]) - # groups should be expicitely deleted - subprocess.call(["groupdel", user_name]) - subprocess.call(["rm", "-fr", "/home/%s" %user_name]) + if user_exists(user_name): + subprocess.check_call(["userdel", "-fr", user_name]) + if group_exists(user_name): + # groups should be expicitely deleted + subprocess.check_call(["groupdel", user_name]) def add_user_to_group(user_name, group_name): """ Will add user to group """ - subprocess.call(["usermod", "-a", "-G", group_name, user_name]) + subprocess.check_call(["usermod", "-a", "-G", group_name, user_name]) def create_account(user_name): """ Force to create account; run clean_account before creation """ - clean_account(user_name) - subprocess.call(["useradd", user_name]) + if not user_exists(user_name): + subprocess.check_call(["useradd", user_name]) def clean_group(group_name): """ Force to delete testing group """ - subprocess.call(["groupdel", group_name]) + if group_exists(group_name): + subprocess.check_call(["groupdel", group_name]) def create_group(group_name): """ Force to create group """ - clean_group(group_name) - subprocess.call(["groupadd", group_name]) + if not group_exists(group_name): + subprocess.check_call(["groupadd", group_name]) + diff --git a/src/indmanager/ind_manager.c b/src/indmanager/ind_manager.c index d3a73e8..7e2cda3 100644 --- a/src/indmanager/ind_manager.c +++ b/src/indmanager/ind_manager.c @@ -582,6 +582,9 @@ char *get_classname(CMPISelectExp *se) #else char *select = (char*)CMGetCharsPtr(CMGetSelExpString(se, NULL), NULL); char *rest = NULL, *token = NULL, *ptr = select; + if (!ptr) { + return NULL; + } for (; ptr || strcasecmp(token, "from") != 0; ptr = NULL) { token = strtok_r(ptr, " ", &rest); if (!token) { diff --git a/src/logicalfile/LMI_DirectoryContainsFileProvider.c b/src/logicalfile/LMI_DirectoryContainsFileProvider.c index 1794ea8..46db891 100644 --- a/src/logicalfile/LMI_DirectoryContainsFileProvider.c +++ b/src/logicalfile/LMI_DirectoryContainsFileProvider.c @@ -127,7 +127,6 @@ static CMPIStatus associators( CMPIObjectPath *o; CMPIInstance *ci; CMPIStatus st; - CMPIData pathd; const char *ns = KNameSpace(cop); st = lmi_check_required(_cb, cop, LOGICALFILE); @@ -139,8 +138,7 @@ static CMPIStatus associators( CMReturn(CMPI_RC_OK); } - pathd = CMGetKey(cop, "Name", &st); - const char *path = KChars(pathd.value.string); + const char *path = get_string_property_from_op(cop, "Name"); CMPIObjectPath *refs[MAX_REFS]; unsigned int count; @@ -200,8 +198,6 @@ static CMPIStatus references( { LMI_DirectoryContainsFile lmi_dcf; CMPIStatus st; - CMPIData pathd; - CMPIData cd; const char *ns = KNameSpace(cop); /* GroupComponent */ @@ -225,10 +221,8 @@ static CMPIStatus references( return st; } - cd = CMGetKey(cop, "CreationClassName", &st); - const char *ccname = KChars(cd.value.string); - pathd = CMGetKey(cop, "Name", &st); - const char *path = KChars(pathd.value.string); + const char *ccname = get_string_property_from_op(cop, "CreationClassName"); + const char *path = get_string_property_from_op(cop, "Name"); char *fsname; if (get_fsname_from_path(path, &fsname) < 0) { CMReturnWithChars(_cb, CMPI_RC_ERR_FAILED, "Can't get filesystem name"); diff --git a/src/logicalfile/LMI_FileIdentityProvider.c b/src/logicalfile/LMI_FileIdentityProvider.c index fe3324f..d31c305 100644 --- a/src/logicalfile/LMI_FileIdentityProvider.c +++ b/src/logicalfile/LMI_FileIdentityProvider.c @@ -65,8 +65,6 @@ static CMPIStatus associators( CMPIInstance *ci; CMPIObjectPath *o; const char *ns = KNameSpace(cop); - CMPIData pathd; - CMPIData cd; char fileclass[BUFLEN]; char *fsname; @@ -83,10 +81,8 @@ static CMPIStatus associators( return st; } - pathd = CMGetKey(cop, "LFName", &st); - cd = CMGetKey(cop, "LFCreationClassName", &st); - const char *path = KChars(pathd.value.string); - const char *ccname = KChars(cd.value.string); + const char *path = get_string_property_from_op(cop, "LFName"); + const char *ccname = get_string_property_from_op(cop, "LFCreationClassName"); get_class_from_path(path, fileclass); if (get_fsname_from_path(path, &fsname) < 0) { CMReturnWithChars(_cb, CMPI_RC_ERR_FAILED, "Can't get filesystem name"); @@ -111,9 +107,7 @@ static CMPIStatus associators( return st; } - pathd = CMGetKey(cop, "Name", &st); - cd = CMGetKey(cop, "CreationClassName", &st); - const char *path = KChars(pathd.value.string); + const char *path = get_string_property_from_op(cop, "Name"); get_class_from_path(path, fileclass); if (get_fsname_from_path(path, &fsname) < 0) { CMReturnWithChars(_cb, CMPI_RC_ERR_FAILED, "Can't get filesystem name"); @@ -152,8 +146,6 @@ static CMPIStatus references( { LMI_FileIdentity lmi_fi; CMPIStatus st, res; - CMPIData pathd; - CMPIData cd; const char *ns = KNameSpace(cop); CMPIInstance *ci; CMPIObjectPath *o; @@ -181,10 +173,8 @@ static CMPIStatus references( return st; } - pathd = CMGetKey(cop, "LFName", &st); - cd = CMGetKey(cop, "LFCreationClassName", &st); - const char *path = KChars(pathd.value.string); - const char *ccname = KChars(cd.value.string); + const char *path = get_string_property_from_op(cop, "LFName"); + const char *ccname = get_string_property_from_op(cop, "LFCreationClassName"); get_class_from_path(path, fileclass); if (get_fsname_from_path(path, &fsname) < 0) { CMReturnWithChars(_cb, CMPI_RC_ERR_FAILED, "Can't get filesystem name"); @@ -212,10 +202,8 @@ static CMPIStatus references( return st; } - pathd = CMGetKey(cop, "Name", &st); - cd = CMGetKey(cop, "CreationClassName", &st); - const char *path = KChars(pathd.value.string); - const char *ccname = KChars(cd.value.string); + const char *path = get_string_property_from_op(cop, "Name"); + const char *ccname = get_string_property_from_op(cop, "CreationClassName"); if (get_fsname_from_path(path, &fsname) < 0) { CMReturnWithChars(_cb, CMPI_RC_ERR_FAILED, "Can't get filesystem name"); } diff --git a/src/logicalfile/LMI_RootDirectoryProvider.c b/src/logicalfile/LMI_RootDirectoryProvider.c index dc6cd6b..600f7a5 100644 --- a/src/logicalfile/LMI_RootDirectoryProvider.c +++ b/src/logicalfile/LMI_RootDirectoryProvider.c @@ -59,9 +59,7 @@ static CMPIStatus associators( const char *ns = KNameSpace(cop); const char *comp_ccname = get_system_creation_class_name(); - CMPIData pathd; - pathd = CMGetKey(cop, "Name", &st); - const char *path = KChars(pathd.value.string); + const char *path = get_string_property_from_op(cop, "Name"); char *fsname; if (get_fsname_from_path("/", &fsname) < 0) { CMReturnWithChars(_cb, CMPI_RC_ERR_FAILED, "Can't get filesystem name"); @@ -128,12 +126,8 @@ static CMPIStatus references( const char *comp_ccname = get_system_creation_class_name(); const char *ns = KNameSpace(cop); - CMPIData pathd; - CMPIData cd; - pathd = CMGetKey(cop, "Name", &st); - cd = CMGetKey(cop, "CreationClassName", &st); - const char *path = KChars(pathd.value.string); - const char *ccname = KChars(cd.value.string); + const char *path = get_string_property_from_op(cop, "Name"); + const char *ccname = get_string_property_from_op(cop, "CreationClassName"); char *fsname; if (get_fsname_from_path("/", &fsname) < 0) { CMReturnWithChars(_cb, CMPI_RC_ERR_FAILED, "Can't get filesystem name"); diff --git a/src/logicalfile/LMI_UnixDirectoryProvider.c b/src/logicalfile/LMI_UnixDirectoryProvider.c index 2628982..7face91 100644 --- a/src/logicalfile/LMI_UnixDirectoryProvider.c +++ b/src/logicalfile/LMI_UnixDirectoryProvider.c @@ -78,8 +78,7 @@ static CMPIStatus LMI_UnixDirectoryCreateInstance( LMI_UnixDirectory_InitFromInstance(&lmi_ud, _cb, ci); CMPIStatus st; CMPIObjectPath *iop = CMGetObjectPath(ci, &st); - CMPIData pathd = CMGetKey(iop, "Name", &st); - const char *path = KChars(pathd.value.string); + const char *path = get_string_property_from_op(iop, "Name"); if (mkdir(path, 0777) < 0) { char errmsg[BUFLEN]; @@ -107,9 +106,7 @@ static CMPIStatus LMI_UnixDirectoryDeleteInstance( const CMPIResult* cr, const CMPIObjectPath* cop) { - CMPIStatus st; - CMPIData pathd = CMGetKey(cop, "Name", &st); - const char *path = KChars(pathd.value.string); + const char *path = get_string_property_from_op(cop, "Name"); if (rmdir(path) < 0) { char errmsg[BUFLEN]; diff --git a/src/logicalfile/LMI_UnixFileProvider.c b/src/logicalfile/LMI_UnixFileProvider.c index ff9423b..97f651e 100644 --- a/src/logicalfile/LMI_UnixFileProvider.c +++ b/src/logicalfile/LMI_UnixFileProvider.c @@ -121,6 +121,7 @@ static CMPIStatus LMI_UnixFileGetInstance( snprintf(aux, BUFLEN, "Can't stat file: %s", path); CMReturnWithChars(_cb, CMPI_RC_ERR_NOT_FOUND, aux); } + LMI_UnixFile_Set_Name(&lmi_file, path); sprintf(aux, "%u", sb.st_uid); LMI_UnixFile_Set_UserID(&lmi_file, aux); sprintf(aux, "%u", sb.st_gid); diff --git a/src/logicalfile/file.c b/src/logicalfile/file.c index 64b7d52..e3be07f 100644 --- a/src/logicalfile/file.c +++ b/src/logicalfile/file.c @@ -19,52 +19,112 @@ */ #include "file.h" -static const char *LOGICALFILE_REQUIRED_NAMES[] = { - "CSCreationClassName", - "CSName", - "CreationClassName", - "FSCreationClassName", - "FSName", - "Name", - NULL -}; - -static const char *UNIXFILE_REQUIRED_NAMES[] = { - "CSCreationClassName", - "CSName", - "LFCreationClassName", - "FSCreationClassName", - "FSName", - "LFName", - NULL -}; - CMPIStatus lmi_check_required( const CMPIBroker *b, const CMPIObjectPath *o, const enum RequiredNames rn) { - const char **names; - - switch (rn) { - case LOGICALFILE: - names = LOGICALFILE_REQUIRED_NAMES; - break; - case UNIXFILE: - names = UNIXFILE_REQUIRED_NAMES; - break; - default: + const char *prop; + const char *errmsg = NULL; + char *path = NULL; + + /* check computer system creation class name */ + if (CMIsNullValue(CMGetKey(o, "CSCreationClassName", NULL))) { + errmsg = "CSCreationClassName is empty"; + goto done; + } + prop = get_string_property_from_op(o, "CSCreationClassName"); + if (strcmp(prop, lmi_get_system_creation_class_name())) { + errmsg = "Wrong CSCreationClassName"; + goto done; + } + + /* check fqdn */ + if (CMIsNullValue(CMGetKey(o, "CSName", NULL))) { + errmsg = "CSName is empty"; + goto done; + } + prop = get_string_property_from_op(o, "CSName"); + if (strcmp(prop, lmi_get_system_name())) { + errmsg = "Wrong CSName"; + goto done; + } + + if (rn == UNIXFILE) { + /* check creation class name */ + char fileclass[BUFLEN]; + if (CMIsNullValue(CMGetKey(o, "LFCreationClassName", NULL))) { + errmsg = "LFCreationClassName is empty"; + goto done; + } + prop = get_string_property_from_op(o, "LFCreationClassName"); + if (get_class_from_path(get_string_property_from_op(o, "LFName"), fileclass) != 0) { + errmsg = "Can't get class from path"; + goto done; + } + if (strcmp(prop, fileclass)) { + errmsg = "LFCreationClassName doesn't match the file's type"; + goto done; + } + if (CMIsNullValue(CMGetKey(o, "LFName", NULL))) { + errmsg = "LFName is empty"; + goto done; + } + if (get_fsname_from_path(get_string_property_from_op(o, "LFName"), &path) < 0) { + errmsg = "Can't get FSName from path"; + goto done; + } + } else if (rn == LOGICALFILE) { + /* check creation class name */ + if (CMIsNullValue(CMGetKey(o, "CreationClassName", NULL))) { + errmsg = "CreationClassName is empty"; + goto done; + } + prop = get_string_property_from_op(o, "CreationClassName"); + if (!CMClassPathIsA(b, o, prop, NULL)) { + errmsg = "CreationClassName and the class name don't match"; + goto done; + } + if (CMIsNullValue(CMGetKey(o, "Name", NULL))) { + errmsg = "Name is empty"; + goto done; + } + if (get_fsname_from_path(get_string_property_from_op(o, "Name"), &path) < 0) { + errmsg = "Can't get FSName from path"; + goto done; + } + } else { /* not possible! */ assert(0); - break; } - for (int i = 0; names[i]; i++) { - if (CMIsNullValue(CMGetKey(o, names[i], NULL))) { - char errmsg[BUFLEN]; - snprintf(errmsg, BUFLEN, "No '%s' specified", names[i]); - CMReturnWithChars(b, CMPI_RC_ERR_FAILED, errmsg); - } + /* check fs creation class name and fsname */ + if (CMIsNullValue(CMGetKey(o, "FSCreationClassName", NULL))) { + errmsg = "FSCreationClassName is empty"; + goto done; + } + prop = get_string_property_from_op(o, "FSCreationClassName"); + if (strcmp(prop, FSCREATIONCLASSNAME)) { + errmsg = "Wrong FSCreationClassName"; + goto done; + } + + if (CMIsNullValue(CMGetKey(o, "FSName", NULL))) { + errmsg = "FSName is empty"; + goto done; + } + prop = get_string_property_from_op(o, "FSName"); + if (strcmp(prop, path)) { + errmsg = "Wrong FSName"; + goto done; + } + +done: + if (path) { + free(path); + } + if (errmsg) { + CMReturnWithChars(b, CMPI_RC_ERR_FAILED, errmsg); } CMReturn(CMPI_RC_OK); } @@ -137,6 +197,13 @@ int get_fsname_from_path(const char *path, char **fsname) return rc; } +const char *get_string_property_from_op(const CMPIObjectPath *o, const char *prop) +{ + CMPIData d; + d = CMGetKey(o, prop, NULL); + return KChars(d.value.string); +} + void _dump_objectpath(const CMPIObjectPath *o) { printf("OP: %s\n", CMGetCharsPtr(o->ft->toString(o, NULL), NULL)); diff --git a/src/logicalfile/file.h b/src/logicalfile/file.h index df906c2..b7f8c50 100644 --- a/src/logicalfile/file.h +++ b/src/logicalfile/file.h @@ -105,6 +105,7 @@ void get_class_from_stat(const struct stat *, char *); int get_class_from_path(const char *, char *); int get_fsname_from_stat(const struct stat *, char **); int get_fsname_from_path(const char *, char **); +const char *get_string_property_from_op(const CMPIObjectPath *, const char *); void _dump_objectpath(const CMPIObjectPath *); diff --git a/src/logicalfile/test/README b/src/logicalfile/test/README index 54c7c92..3c5a32e 100644 --- a/src/logicalfile/test/README +++ b/src/logicalfile/test/README @@ -9,6 +9,10 @@ associations-related methods, they should all be tested from both sides, meaning that the respective associations should be called twice with both of their "arguments". +Dependencies +------------ +$ yum install python-pyudev + Usage ----- The tests must be run on the same machine as the CIMOM! @@ -25,4 +29,4 @@ LMI_LOGICALFILE_TESTDIR - Testing directory where all the files and testing will Running the tests: LMI_CIMOM_PASSWORD=opensesame \ LMI_LOGICALFILE_TESTDIR="/home/user/my-testing-dir" \ - python test/test_basic.py
\ No newline at end of file + python test/test_basic.py diff --git a/src/logicalfile/test/test_base.py b/src/logicalfile/test/test_base.py index 6d2f2a1..5c71196 100644 --- a/src/logicalfile/test/test_base.py +++ b/src/logicalfile/test/test_base.py @@ -21,6 +21,7 @@ import os import socket import unittest import subprocess +import pyudev class LogicalFileTestBase(unittest.TestCase): """ @@ -43,6 +44,14 @@ class LogicalFileTestBase(unittest.TestCase): if rc != 0: cls.selinux_enabled = False except: cls.selinux_enabled = False + ctx = pyudev.Context() + sb = os.stat(os.path.realpath(cls.testdir + "/..")) + device = pyudev.Device.from_device_number(ctx, "block", sb.st_dev) + dev_name = device.get("ID_FS_UUID_ENC") + if not dev_name: + cls.fsname = "DEVICE=" + device.get("DEVNAME") + else: + cls.fsname = "UUID=" + dev_name def setUp(self): pass diff --git a/src/logicalfile/test/test_basic.py b/src/logicalfile/test/test_basic.py index f14de6e..fc4d336 100644 --- a/src/logicalfile/test/test_basic.py +++ b/src/logicalfile/test/test_basic.py @@ -71,7 +71,7 @@ class TestLogicalFile(LogicalFileTestBase): 'CSCreationClassName':self.SYSTEM_CLASS_NAME, 'CSName':self.SYSTEM_NAME, 'FSCreationClassName':'LMI_LocalFileSystem', - 'FSName':'NotImportant', + 'FSName':self.fsname, 'CreationClassName':'LMI_UnixDirectory', 'Name':self.testdir }) @@ -152,7 +152,7 @@ class TestLogicalFile(LogicalFileTestBase): 'CSCreationClassName':self.SYSTEM_CLASS_NAME, 'CSName':self.SYSTEM_NAME, 'FSCreationClassName':'LMI_LocalFileSystem', - 'FSName':'NotImportant', + 'FSName':self.fsname, 'CreationClassName':f['class'], 'Name':f['path'] }) @@ -179,7 +179,7 @@ class TestLogicalFile(LogicalFileTestBase): 'CSCreationClassName':self.SYSTEM_CLASS_NAME, 'CSName':self.SYSTEM_NAME, 'FSCreationClassName':'LMI_LocalFileSystem', - 'FSName':'NotImportant', + 'FSName':self.fsname, 'CreationClassName':f['class'], 'Name':f['path'] }) @@ -218,7 +218,7 @@ class TestLogicalFile(LogicalFileTestBase): 'CSCreationClassName':self.SYSTEM_CLASS_NAME, 'CSName':self.SYSTEM_NAME, 'FSCreationClassName':'LMI_LocalFileSystem', - 'FSName':'NotImportant', + 'FSName':self.fsname, 'LFCreationClassName':f['class'], 'LFName':f['path'] }) @@ -249,7 +249,7 @@ class TestLogicalFile(LogicalFileTestBase): 'CSCreationClassName':self.SYSTEM_CLASS_NAME, 'CSName':self.SYSTEM_NAME, 'FSCreationClassName':'LMI_LocalFileSystem', - 'FSName':'NotImportant', + 'FSName':self.fsname, 'LFCreationClassName':f['class'], 'LFName':f['path'] }) @@ -320,5 +320,92 @@ class TestLogicalFile(LogicalFileTestBase): rmdir, '/cant/remove/me') + def _test_missing_or_wrong_properties(self, is_unixfile): + testfile = self.files['data'] + if is_unixfile: + prefix = 'LF' + clsname = 'LMI_UnixFile' + else: + prefix = '' + clsname = 'LMI_DataFile' + cop = pywbem.CIMInstanceName(classname=clsname, + namespace='root/cimv2', + keybindings={}) + + prop = 'CSCreationClassName' + self.assertRaisesRegexp(pywbem.CIMError, + '%s is empty' % prop, + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = 'BadClass' + self.assertRaisesRegexp(pywbem.CIMError, + 'Wrong %s' % prop, + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = self.SYSTEM_CLASS_NAME + + prop = 'CSName' + self.assertRaisesRegexp(pywbem.CIMError, + '%s is empty' % prop, + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = 'BadClass' + self.assertRaisesRegexp(pywbem.CIMError, + 'Wrong %s' % prop, + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = self.SYSTEM_NAME + + prop = prefix + 'CreationClassName' + self.assertRaisesRegexp(pywbem.CIMError, + '%s is empty' % prop, + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = testfile['class'] + prop = prefix + 'Name' + cop.keybindings[prop] = self.files['dir']['path'] + if is_unixfile: + self.assertRaisesRegexp(pywbem.CIMError, + 'LFCreationClassName doesn\'t match', + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = testfile['path'] + + prop = 'FSCreationClassName' + self.assertRaisesRegexp(pywbem.CIMError, + '%s is empty' % prop, + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = 'BadFS' + self.assertRaisesRegexp(pywbem.CIMError, + 'Wrong %s' % prop, + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = 'LMI_LocalFileSystem' + + prop = 'FSName' + self.assertRaisesRegexp(pywbem.CIMError, + '%s is empty' % prop, + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = 'BadFSName' + self.assertRaisesRegexp(pywbem.CIMError, + 'Wrong %s' % prop, + self.wbemconnection.GetInstance, + cop) + cop.keybindings[prop] = self.fsname + + # finally, test GetInstance on the correct object path + try: + self.wbemconnection.GetInstance(cop) + except pywbem.CIMError as pe: + self.fail(pe[1]) + + def test_unixfile_missing_or_wrong_properties(self): + self._test_missing_or_wrong_properties(True) + + def test_logicalfile_missing_or_wrong_properties(self): + self._test_missing_or_wrong_properties(False) + if __name__ == '__main__': unittest.main() diff --git a/src/pcp/README b/src/pcp/README new file mode 100644 index 0000000..1f5e225 --- /dev/null +++ b/src/pcp/README @@ -0,0 +1,52 @@ +CIM <-> PCP (http://oss.sgi.com/projects/pcp) bridge +<fche@redhat.com> + +The idea of the bridge is to create a suite of CIM classes (derived from +CIM_StatisticsData), each of which represents one PCP metric. There are +hundreds of these. (Since PCP metrics can come and go, the MOF and REG +files may be regenerated by a simple shell script at any with an enclosed +shell script.) + +Install PCP and start the pmcd service to start. We assume you're +already running OpenLMI / Pegasus with a client like YAWN and are far +more familiar with this WBEM business than the author. Install the +CIM <-> PCP bridge thusly: + + vi PCP_pmns2mofreg.sh # to fix the path to pcp-metric.py + sh PCP_pmns2mofreg.sh mof > PCP_Metric_PMNS.mof + sh PCP_pmns2mofreg.sh reg > PCP_Metric_PMNS.reg + openlmi-mof-register register *.mof *.reg + +For example, whereas PCP command line tools may show these sorts of results: + + % pminfo -dTf hinv.machine + + hinv.machine + Data Type: string InDom: PM_INDOM_NULL 0xffffffff + Semantics: discrete Units: none + Help: + machine name, IP35 if SGI SNIA, else simply linux + value "linux" + + % pminfo -dTf network.interface.total.bytes + network.interface.total.bytes + Data Type: 64-bit unsigned int InDom: 60.3 0xf000003 + Semantics: counter Units: byte + Help: + network total (in+out) bytes from /proc/net/dev per network interface + inst [0 or "eth0"] value 585236365 + inst [1 or "lo"] value 85894766 + +These same metrics would show up in the CIM namespace as classes + + PCP_Metric_hinv__machine +and PCP_Metric_network__interface__total__bytes + +with instances with InstanceIDs such as + + PCP:hinv.machine + PCP:network.interface.total.bytes:lo + PCP:network.interface.total.bytes:eth0 + +These currently supply a string-formatted value of the respective +metric measurement, an accurate StatisticTime, and other metadata. diff --git a/src/pcp/lmi/pcp/__init__.py b/src/pcp/lmi/pcp/__init__.py new file mode 100644 index 0000000..81de2e5 --- /dev/null +++ b/src/pcp/lmi/pcp/__init__.py @@ -0,0 +1,25 @@ +# PCP bridge Providers +# +# Copyright (C) 2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Frank Ch. Eigler <fche@redhat.com> +# + +""" +CIM providers for PCP metrics. +Part of OpenLMI project. +""" diff --git a/src/pcp/lmi/pcp/metric.py b/src/pcp/lmi/pcp/metric.py new file mode 100644 index 0000000..454f54a --- /dev/null +++ b/src/pcp/lmi/pcp/metric.py @@ -0,0 +1,167 @@ +"""Python Provider for PCP_Metric_* +Instruments the CIM class family PCP_Metric_* +""" + +from pywbem.cim_provider2 import CIMProvider2 +import pywbem +from pcp import pmapi +import cpmapi as c_api +import datetime + +context = None # persistent pcp context + + +# Since we serve a whole slew of PCP_Metric_** subclasses, we can't +# use a straight classname->provider-class dictionary. +# +#def get_providers(env): +# return {'PCP_Metric_****': PCP_MetricProvider} +# +# Instead, we implement the one-hop-higher proxy-level function calls, +# namely the IM_* functions at the bottom. + + +# Undo mangling done by PCP_pmns2mofreg.sh +def MOFname_to_PCPmetric (op): + assert (op.namespace == 'root/cimv2') + assert (op.classname[0:11] == 'PCP_Metric_') + return op.classname[11:].replace('__', '.') + + +# Search the given PM_ResultSet for a given instance number (if any); +# return formatted CIM of the value (or CIM None) +def PCP_CIMValueString (context, result, desc, inst): + for i in range (0,result.contents.get_numval(0)): + value = result.contents.get_vlist(0,i) + iv = value.inst + if (inst is not None and inst != iv): + continue + atom = context.pmExtractValue (result.contents.get_valfmt(0), + value, + desc.contents.type, + desc.contents.type) + atomValue = atom.dref(desc.contents.type) # nb: atomValue could be numeric etc. + return pywbem.CIMProperty(name='ValueString', + value=str(atomValue), # stringify it here + type='string') + + return pywbem.CIMProperty(name='ValueString', + value=None, + type='string') + + +def PCP_CIMStatisticTime (result): + dt = datetime.datetime.fromtimestamp(float(str(result.contents.timestamp))) + return pywbem.CIMDateTime(dt) + + +# generic payload generator, used for +# - iterating across instance domains (keys_only=1) +# - fetching metric values +# - fetching metric metadata (for those model/filter fields set) +def get_instance (env, op, model, keys_only): + metric = MOFname_to_PCPmetric (op) + global context + + try: + if (context == None): + context = pmapi.pmContext() # localhost or equivalent + context.pmReconnectContext() # in case it was nuked recently + except pmapi.pmErr, e: + context = None + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, "Unable to connect to local PMCD:" + str(e)) + + pmids = context.pmLookupName(metric) + pmid = pmids[0] + desc = context.pmLookupDesc(pmid) + + if ('InstanceID' in model): + selected_instanceid = model['InstanceID'] + else: + selected_instanceid = None + + model.path.update({'InstanceID':None}) + + if (not keys_only): + model['PMID'] = pywbem.Uint32(pmid) + model['ElementName'] = metric + # must not fail, or else we have no metric value data worth sharing + try: + results = context.pmFetch(pmids) + except pmapi.pmErr, e: + # fatal + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, "PCP pmFetch failed:" + str(e)) + # cannot fail + model['Units'] = context.pmUnitsStr(desc.contents.units) + model['Type'] = context.pmTypeStr(desc.contents.type) + # these may fail, but not fatally + try: + model['Caption'] = context.pmLookupText(pmid) + except pmapi.pmErr: + pass + try: + model['Description'] = context.pmLookupText(pmid, c_api.PM_TEXT_HELP) + except pmapi.pmErr: + pass + + try: + instL, nameL = context.pmGetInDom(desc) + for iL, nL in zip(instL, nameL): + new_instanceid = 'PCP:'+metric+':'+nL + if (selected_instanceid is None or + new_instanceid == selected_instanceid): + model['InstanceNumber'] = pywbem.Uint32(iL) + model['InstanceName'] = nL + model['InstanceID'] = new_instanceid + if (not keys_only): + model['StatisticTime'] = PCP_CIMStatisticTime (results) + model['ValueString'] = PCP_CIMValueString (context, results, desc, iL) + yield model + except pmapi.pmErr: # pmGetInDom is expected to fail for non-instance (PM_INDOM_NULL) metrics + new_instanceid = 'PCP:'+metric + if (selected_instanceid is None or + new_instanceid == selected_instanceid): + model['InstanceNumber'] = pywbem.CIMProperty(name='InstanceNumber', + value=None,type='uint32') + model['InstanceName'] = pywbem.CIMProperty(name='InstanceName', + value=None,type='string') + model['InstanceID'] = new_instanceid + if (not keys_only): + model['StatisticTime'] = PCP_CIMStatisticTime (results) + model['ValueString'] = PCP_CIMValueString (context, results, desc, None) + yield model + +# hooks for impersonating CIMProvider2 functions + + +def MI_enumInstanceNames (env, op): + model = pywbem.CIMInstance(classname = op.classname, path=op) + for x in get_instance (env, op, model, True): + yield x.path + +def MI_enumInstances (env, op, plist): + model = pywbem.CIMInstance(classname = op.classname, path=op) + return get_instance (env, op, model, False) + +def MI_getInstance (env, op, plist): + proplist = None + if plist is not None: + proplist = [s.lower() for s in propertyList] + proplist+= [s.lower() for s in op.keybindings.keys()] + model = pywbem.CIMInstance(classname=op.classname, path=op, + property_list=proplist) + model.update(model.path.keybindings) + for x in get_instance (env, op, model, False): + return x # XXX: first one + +def MI_createInstance (env, pinst): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + +def MI_modifyInstance (env, pinst, plist): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + +def MI_deleteInstance (env, piname): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + +# See also extra MI_* functions for associations, etc.; +# cmpi-bindings.git swig/python/cmpi_pywbem_bindings.py diff --git a/src/pcp/openlmi-pcp-generate b/src/pcp/openlmi-pcp-generate new file mode 100644 index 0000000..8c53512 --- /dev/null +++ b/src/pcp/openlmi-pcp-generate @@ -0,0 +1,68 @@ +#! /bin/sh + +# This script refreshes WBEM/CIM MOF & REG files from the current PCP PMNS, +# if necessary, and reloads the new MOF/REGs into the CIMMON. +# +# The PCP PMNS changes infrequently (when the sysadmin manuall installs or +# removes pcp PMDA (agent) modules in /var/lib/pcp/pmdas/*). +# +# This script encodes the PCP->CIM metric name-mapping convention of replacing +# dots with double-underscores, which lmi/pcp/metric.py will dutifully undo. + +_LOCALSTATEDIR=/var +_DATADIR=/usr/share +NAME=openlmi-providers +PYTHON2_SITELIB=/usr/lib/python2.7/site-packages + +PCP_PMNS=$_LOCALSTATEDIR/lib/pcp/pmns/root +PCP_HOST=${1-localhost} # or local:// for pcp 3.9+ +BASEMOFFILE=$_DATADIR/$NAME/60_LMI_PCP.mof +MOFREGDIR=$_LOCALSTATEDIR/lib/$NAME +STAMPFILE=$MOFREGDIR/stamp +MOFFILE=$MOFREGDIR/60_LMI_PCP_PMNS.mof +REGFILE=$MOFREGDIR/60_LMI_PCP_PMNS.reg +PROVIDER=$PYTHON2_SITELIB/lmi/pcp/metric.py + +if [ ! -f $PROVIDER ]; then + echo "Cannot find $PROVIDER" 1>&2 + exit 1 +fi + +set -e + +echo Refreshing PCP_Metric CIMMON classes from current PCP PMNS + +# quick liveness test +pcp -h $PCP_HOST + +if [ -s $PCP_PMNS -a $PCP_PMNS -nt $STAMPFILE ]; then + if [ -f $MOFFILE -a -f $REGFILE ]; then + echo Unregistering $BASEMOFFILE + echo Unregistering previous $MOFFILE + echo Unregistering previous $REGFILE + openlmi-mof-register unregister $BASEMOFFILE $MOFFILE $REGFILE || : + fi + + echo Generating $MOFFILE + pminfo -h $PCP_HOST | sed -e 's,\.,__,g' | + awk '{print "class PCP_Metric_" $1 " : PCP_MetricValue { } ;" }' > $MOFFILE + + echo Generating $REGFILE + pminfo -h $PCP_HOST | sed -e 's,\.,__,g' | + awk '{print "[PCP_Metric_" $1 "]" + print " provider: '$PROVIDER'" + print " location: pyCmpiProvider" + print " type: instance" + print " namespace: root/cimv2" + print " group: pcp" + print ""}' > $REGFILE + + echo Registering $BASEMOFFILE + echo Registering new $MOFFILE + echo Registering new $REGFILE + openlmi-mof-register register $BASEMOFFILE $MOFFILE $REGFILE 2>&1 | # filter out two noise diagnostics + egrep -v 'Warning: the instance already exists.|In this implementation, that means it cannot be changed.' || : + touch $STAMPFILE +else + echo Doing nothing, $PCP_PMNS older than $STAMPFILE +fi diff --git a/src/pcp/openlmi-pcp.cron b/src/pcp/openlmi-pcp.cron new file mode 100644 index 0000000..a9691f0 --- /dev/null +++ b/src/pcp/openlmi-pcp.cron @@ -0,0 +1,3 @@ +#! /bin/sh + +openlmi-pcp-generate localhost >/dev/null 2>&1 diff --git a/src/pcp/setup.py b/src/pcp/setup.py new file mode 100644 index 0000000..9ffa320 --- /dev/null +++ b/src/pcp/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup +setup( + name='lmi-pcp', + description='PCP metric providers', + author='Frank Ch. Eigler', + author_email='fche@redhat.com', + url='https://fedorahosted.org/openlmi/', + version='0.1', + namespace_packages=['lmi'], + packages=[ + 'lmi.pcp'], + install_requires=['lmi', 'pcp'], + license="LGPLv2+", + classifiers=[ + 'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)', + 'Operating System :: POSIX :: Linux', + 'Topic :: System :: Systems Administration', + ] + ) |