summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichal Minar <miminar@redhat.com>2013-04-26 08:34:33 +0200
committerMichal Minar <miminar@redhat.com>2013-04-26 10:18:20 +0200
commit3126884ffe36e05fc4785b4cfa980a34fcae1439 (patch)
tree6a2b167e5817654c4eb896254759f148dc9d443a
parentac68269f2a2c6e072a038d758e45438a0c8a6c69 (diff)
downloadopenlmi-providers-3126884ffe36e05fc4785b4cfa980a34fcae1439.tar.gz
openlmi-providers-3126884ffe36e05fc4785b4cfa980a34fcae1439.tar.xz
openlmi-providers-3126884ffe36e05fc4785b4cfa980a34fcae1439.zip
added script for online modification of pegasus repo
This allows to create and delete instances and classes specified in mof files on-line. Complements cimmof application, which can not do "delete". With this we are able to unregister static filters and classes from pegasus on package removal - if the pegasus is running :-(.
-rwxr-xr-xopenlmi-cimmof340
1 files changed, 340 insertions, 0 deletions
diff --git a/openlmi-cimmof b/openlmi-cimmof
new file mode 100755
index 0000000..cbec8d4
--- /dev/null
+++ b/openlmi-cimmof
@@ -0,0 +1,340 @@
+#!/usr/bin/env python
+# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+Allows to modify Pegasus repository with declarations in mof files.
+Pegasus must be running for this script to work. It depends on cimmof
+binary, which is online compilator of MOF files for pegasus.
+
+It works in this way:
+ 1. cimmof is called on input mof files
+ 2. its output (xml) is then parsed by pywbem functions producing
+ CIM objects (instances of ``pywbem.CIMClass`` and
+ ``pywbem.CIMInstance``)
+ 3. these objects are then used in calls to
+ ``{Create,Modify}{Instance,Class}``
+
+*Note* that only Class and Instance declarations are supported.
+ - This is due to limitations in pywbem parser.
+ - Although this could be avoided by calling wbemexec on generated XML.
+"""
+
+import argparse
+import re
+import logging
+import subprocess
+import sys
+import pywbem
+import xml.dom.minidom as dom
+
+DEFAULT_NAMESPACE = "root/cimv2"
+DEFAULT_CIMMOF = "cimmof"
+
+RE_COMMENT = re.compile(r'\s*<!--.*?-->\s*', re.DOTALL)
+
+logging.basicConfig(level=logging.ERROR,
+ format="%(levelname)s - %(message)s")
+LOG = logging.getLogger(__name__)
+
+def die(msg, *args, **kwargs):
+ """
+ Exit with error printed to stderr.
+ """
+ LOG.error(msg, *args, **kwargs)
+ sys.exit(1)
+
+def xml_cleanup(xml_str):
+ """
+ Return xml string without comments and whitespaces.
+ """
+ # remove comments
+ without_comments = "".join(RE_COMMENT.split(xml_str))
+ # remove whitespaces
+ return "".join(l.strip() for l in without_comments.split("\n"))
+
+def get_objects_from_mofs(cimmof, namespace, *mofs):
+ """
+ Call cimmof binary with mofs as input and obtain class/instance
+ declarations in XML.
+
+ Return list of pywbem CIM abstractions for each declaration.
+ """
+ cmd = [cimmof, '--xml', '-n', namespace]
+ objects = []
+ for mof in mofs:
+ process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=sys.stderr)
+ (out, _) = process.communicate(mof.read())
+ parsed_dom = dom.parseString(xml_cleanup(out))
+ # we cannot use pywbem.parse_cim because it does not support
+ # DECLARATION element, but we can parse individual values
+ for decl in parsed_dom.getElementsByTagName('VALUE.OBJECT'):
+ # pywbem first makes tupletree from dom:
+ # (name, attributes, children)
+ # and from it generates pywbem CIM abstractions
+ (_name, _attrs, obj) = pywbem.tupleparse.parse_value_object(
+ pywbem.dom_to_tupletree(decl))
+ objects.append(obj)
+ return objects
+
+def get_instance_path(conn, namespace, instance, classes):
+ """
+ Obtains a class declaration from cimom for given instance and builds
+ a path from it.
+
+ :param conn is a wbem connection
+ :param namespace (``str``) is a target namespace of instance
+ :param instance (``CIMInstance``) contains class name and is used to get
+ values of key properties. Is modified by adding path to it.
+ :param classes (``dict``) is a cache of classes obtained from cimom.
+ Its items are in form: ``(classname, CIMClass)``.
+
+ Return ``CIMInstanceName``.
+ """
+ if not instance.classname in classes:
+ classes[instance.classname] = conn.GetClass(instance.classname,
+ IncludeQualifiers=True,
+ namespace=namespace)
+ cls = classes[instance.classname]
+ keys = [p.name for p in cls.properties.values() if "Key" in p.qualifiers]
+ path = pywbem.CIMInstanceName(instance.classname, namespace=namespace)
+ for key in keys:
+ if not key in instance:
+ die("instance of %s is missing key property \"%s\"",
+ instance.classname, key)
+ path[key] = instance[key]
+ instance.path = path
+ return path
+
+def create_class(conn, cls, namespace, classes, allow_update=False):
+ """
+ Create class or modify it if already present.
+
+ :param classes: (``dict``) a cache of classes obtained from cimom.
+ :param allow_update: (``bool``) whether to modify existing class.
+ """
+ try:
+ if not cls.classname in classes:
+ classes[cls.classname] = conn.GetClass(cls.classname,
+ IncludeQualifiers=True,
+ namespace=namespace)
+ except pywbem.CIMError as err:
+ if err.args[0] != pywbem.CIM_ERR_NOT_FOUND:
+ raise
+ if cls.classname in classes:
+ if not allow_update:
+ LOG.error("class %s already exists", cls.classname)
+ else:
+ conn.ModifyClass(cls, namespace=namespace)
+ LOG.info("modified class %s", cls.classname)
+ else:
+ conn.CreateClass(cls, namespace=namespace)
+ LOG.info("created class %s", cls.classname)
+
+def create_instance(conn, inst, namespace, classes, allow_update=False):
+ """
+ Create instance or modify it if already present.
+
+ :param classes: (``dict``) a cache of classes obtained from cimom.
+ :param allow_update: (``bool``) whether to modify existing instance.
+ """
+ path = get_instance_path(conn, namespace, inst, classes)
+ present = None
+ try:
+ present = conn.GetInstance(path)
+ except pywbem.CIMError as err:
+ if err.args[0] != pywbem.CIM_ERR_NOT_FOUND:
+ raise
+ if present is not None:
+ try:
+ if allow_update:
+ conn.ModifyInstance(inst)
+ LOG.info("modified instance for path %s", path)
+ else:
+ LOG.error("instance %s already exists", path)
+ except pywbem.CIMError as err:
+ if err.args[0] == pywbem.CIM_ERR_NOT_SUPPORTED:
+ LOG.error("ModifyInstance() is not supported for class %s,"
+ " please remove the instance first",
+ inst.classname)
+ else:
+ raise
+
+ else:
+ conn.CreateInstance(inst)
+ LOG.info("created instance for path %s", path)
+
+def reorder_objects(cmd, objects):
+ """
+ Reorder classes and instances so that dependent objects are handled
+ later.
+
+ Classes can depend between each other in two ways:
+ 1. one inherits from another
+ 2. one refers to another (associations)
+
+ The first case can be solved by counting number of parents, that are
+ also to be removed. Class with highest number will be created as last.
+
+ The second one applies only to associations, which can not refer
+ to each other. Let's just append them after non-associations.
+
+ :param cmd: (``str``) can be "create" or "delete". In latter case the
+ result is reversed, so the dependent classes/instances are removed
+ as first.
+
+ *Note* this does not handle dependencies between instances.
+ """
+ cls_list = []
+ assoc_list = []
+ cls_dict = set( c.classname.lower()
+ for c in objects if isinstance(c, pywbem.CIMClass))
+ # (class name, number of superclasses in cls_dict)
+ cls_deps = pywbem.NocaseDict()
+ inst_list = []
+ for obj in objects:
+ if isinstance(obj, pywbem.CIMClass):
+ if 'association' in obj.qualifiers:
+ assoc_list.append(obj)
+ else:
+ cls_list.append(obj)
+ cls_deps[obj.classname] = 0
+ parent = obj.superclass
+ while parent in cls_dict:
+ cls_deps[obj.classname] += 1
+ parent = cls_dict[parent].classname
+ else: # no specific reordering of instances
+ inst_list.append(obj)
+ key_func = lambda c: (cls_deps[c.classname], c.classname)
+ cls_list = sorted(cls_list, key=key_func)
+ assoc_list = sorted(assoc_list, key=key_func)
+
+ result = cls_list + assoc_list + inst_list
+ if cmd == "delete":
+ result.reverse()
+ return result
+
+def push_to_repo(namespace, cmd, objects, allow_update=False):
+ """
+ Create or delete desired objects in Pegasus repository.
+
+ :param cmd: (``string``) is one of { 'create' | 'delete' }
+ :param objects: (``list``) is a list of pywbem CIM abstractions
+ created from mofs. They will be operated upon.
+ """
+ if not isinstance(namespace, basestring):
+ raise TypeError("namespace must be string")
+ if not cmd in ('create', 'delete'):
+ raise ValueError('cmd must be either "create" or "delete"')
+ classes = pywbem.NocaseDict()
+ conn = pywbem.PegasusUDSConnection()
+ objects = reorder_objects(cmd, objects)
+ for obj in objects:
+ try:
+ if cmd == "create":
+ if isinstance(obj, pywbem.CIMClass):
+ create_class(conn, obj, namespace, classes, allow_update)
+ elif isinstance(obj, pywbem.CIMInstance):
+ create_instance(conn, obj, namespace, classes,
+ allow_update)
+ else:
+ LOG.error("unsupported object for creation: %s",
+ obj.__class__.__name__)
+
+ else:
+ try:
+ if isinstance(obj, pywbem.CIMClass):
+ conn.DeleteClass(obj.classname, namespace=namespace)
+ LOG.info("deleted class %s", obj.classname)
+ elif isinstance(obj, pywbem.CIMInstance):
+ path = get_instance_path(conn, namespace, obj, classes)
+ conn.DeleteInstance(path)
+ LOG.info("deleted instance %s", path)
+ else:
+ LOG.error("unsupported object for deletion: %s",
+ obj.__class__.__name__)
+ except pywbem.CIMError as err:
+ if err.args[0] == pywbem.CIM_ERR_NOT_FOUND:
+ LOG.warn("%s not present in repository",
+ obj if isinstance(obj, pywbem.CIMClass)
+ else path)
+ else:
+ raise
+
+ except pywbem.CIMError as err:
+ if err.args[0] in (pywbem.CIM_ERR_INVALID_PARAMETER, ):
+ LOG.warn("failed to %s %s: %s", cmd, obj, err)
+ else:
+ raise
+
+def parse_cmd_line():
+ """
+ Parse command line and return options.
+ """
+ parser = argparse.ArgumentParser(
+ usage="%(prog)s [options] {create,delete} mof [mof ...]",
+ description="Allows to create/delete instances and classes"
+ " declared in MOF files. It operates only on Pegasus broker"
+ " that needs to be up and running.")
+ parser.add_argument('--cimmof', default=DEFAULT_CIMMOF,
+ help="Path to cimmof binary to use.")
+ #parser.add_argument('--xml', action='store_true', default=False,
+ #help="Do not execute any action on cimom, just print the"
+ #" xml to stdout.")
+ parser.add_argument('-n', '--namespace', default=DEFAULT_NAMESPACE,
+ help="Target CIM Repository namespace.")
+ parser.add_argument('-v', '--verbose', action='store_true',
+ default=False, help="Be more verbosive on output.")
+
+ mof_parser = argparse.ArgumentParser(add_help=False)
+ mof_parser.add_argument('mof', nargs='+',
+ type=argparse.FileType('r'),
+ default=sys.stdin,
+ help="Mof files containing declarations of classes and instances"
+ " to be installed or removed from Pegasus broker.")
+
+ command = parser.add_subparsers(title="Operation commands",
+ dest="command",
+ help="Operation on declarations.")
+ create_cmd = command.add_parser('create', parents=[mof_parser],
+ help='Create instances and classes listed in mof files.')
+ create_cmd.add_argument('-u', '--allow-update',
+ action="store_true", default=False,
+ help="Allow update of class declaration if it already exists.")
+ command.add_parser('delete', parents=[mof_parser],
+ help="Delete instances and classes listed in mof files.")
+
+ args = parser.parse_args()
+ return args
+
+def main():
+ """
+ The main functionality of script.
+ """
+ args = parse_cmd_line()
+ if args.verbose:
+ LOG.setLevel(logging.INFO)
+ # parse mofs and build list of pywbem objects
+ objs = get_objects_from_mofs(args.cimmof, args.namespace, *args.mof)
+ if not objs:
+ die("no declarations found!")
+ push_to_repo(args.namespace, args.command, objs,
+ getattr(args, 'allow_update', False))
+
+if __name__ == '__main__':
+ main()
+