Hello community,

here is the log from the commit of package crmsh for openSUSE:Factory checked 
in at 2014-06-05 10:50:03
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/crmsh (Old)
 and      /work/SRC/openSUSE:Factory/.crmsh.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "crmsh"

Changes:
--------
--- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes      2014-06-01 
19:40:23.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.crmsh.new/crmsh.changes 2014-06-05 
10:50:09.000000000 +0200
@@ -1,0 +2,17 @@
+Tue Jun  3 21:06:00 UTC 2014 - kgronl...@suse.com
+
+- high: parse: Try to retain ordering if possible (bnc#880371)
+- high: cibconfig: Enable use of v2 patches in Pacemaker (bnc#880371)
+- medium: resource: modify some command wait options (bnc#880982) 
+- upstream: 2.0.0-109-g0b2645b 
+
+-------------------------------------------------------------------
+Mon Jun  2 17:33:11 UTC 2014 - kgronl...@suse.com
+
+- medium: ui_resource: trace promote/demote for multistate resources
+- medium: parse: Allow empty property sets (bnc#880632)
+- high: parse: support for ACL schema 2.0 (bnc#880371)
+- medium: schema: Fix typo in test_schema()
+- upstream: 2.0.0-101-gbb441f1 
+
+-------------------------------------------------------------------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ crmsh.spec ++++++
--- /var/tmp/diff_new_pack.6ZRKah/_old  2014-06-05 10:50:12.000000000 +0200
+++ /var/tmp/diff_new_pack.6ZRKah/_new  2014-06-05 10:50:12.000000000 +0200
@@ -41,7 +41,7 @@
 Summary:        High Availability cluster command-line interface
 License:        GPL-2.0+
 Group:          %{pkg_group}
-Version:        2.0+git93
+Version:        2.0+git109
 Release:        %{?crmsh_release}%{?dist}
 Url:            http://crmsh.github.io
 Source0:        crmsh.tar.bz2

++++++ crmsh.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/doc/crm.8.txt new/crmsh/doc/crm.8.txt
--- old/crmsh/doc/crm.8.txt     2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/doc/crm.8.txt     2014-06-03 22:39:19.000000000 +0200
@@ -525,6 +525,15 @@
 [[topics_Security,Access Control Lists (ACL)]]
 == Access Control Lists (ACL)
 
+.Note on ACLs in Pacemaker 1.1.12
+****************************
+The support for ACLs has been revised in Pacemaker version 1.1.12 and
+up. Depending on which version you are using, the information in this
+section may no longer be accurate. Look for the `acl_target` and
+`acl_group` configuration elements for more details on the new
+syntax.
+****************************
+
 By default, the users from the `haclient` group have full access
 to the cluster (or, more precisely, to the CIB). Access control
 lists allow for finer access control to the cluster.
@@ -1510,27 +1519,34 @@
 monitor, note that the number of trace files can grow very
 quickly.
 
+If no operation name is given, crmsh will attempt to trace all
+operations for the RA. This includes any configured operations, start
+and stop as well as promote/demote for multistate resources.
+
 Usage:
 ...............
-trace <rsc> <op> [<interval>]
+trace <rsc> [<op> [<interval>] ]
 ...............
 Example:
 ...............
 trace fs start
+trace webserver
 ...............
 
 [[cmdhelp_resource_untrace,stop RA tracing]]
 ==== `untrace`
 
-Stop tracing RA for the given operation.
+Stop tracing RA for the given operation. If no operation name is
+given, crmsh will attempt to stop tracing all operations in resource.
 
 Usage:
 ...............
-untrace <rsc> <op> [<interval>]
+untrace <rsc> [<op> [<interval>] ]
 ...............
 Example:
 ...............
 untrace fs start
+untrace webserver
 ...............
 
 [[cmdhelp_resource_scores,Display resource scores]]
@@ -2786,6 +2802,35 @@
     role:read_all
 ...............
 
+[[cmdhelp_configure_acl_target,Define target access rights]]
+==== `acl_target`
+
+Defines an ACL target.
+
+Usage:
+................
+acl_target <tid> [<role> ...]
+................
+Example:
+................
+acl_target joe resource_admin constraint_editor
+................
+
+[[cmdhelp_configure_acl_group,Define group access rights]]
+==== `acl_group`
+
+Defines an ACL group.
+
+Usage:
+................
+acl_group <gid> [<role> ...]
+................
+Example:
+................
+acl_group hacluster operator
+................
+
+
 [[cmdhelp_configure_op_defaults,set resource operations defaults]]
 ==== `op_defaults`
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/cibconfig.py 
new/crmsh/modules/cibconfig.py
--- old/crmsh/modules/cibconfig.py      2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/cibconfig.py      2014-06-03 22:39:19.000000000 +0200
@@ -58,6 +58,7 @@
 from cliformat import get_score, nvpairs2list, abs_pos_score, cli_acl_roleref, 
nvpair_format
 from cliformat import cli_nvpair, cli_acl_rule, rsc_set_constraint, get_kind, 
head_id_format
 from cliformat import cli_operations, simple_rsc_constraint, cli_rule, 
cli_format
+from cliformat import cli_acl_role, cli_acl_permission
 
 
 def show_unrecognized_elems(cib_elem):
@@ -1990,6 +1991,9 @@
 class CibAcl(CibObject):
     '''
     User and role ACL.
+
+    Now with support for 1.1.12 style ACL rules.
+
     '''
 
     def _repr_cli_head(self, format):
@@ -2000,7 +2004,12 @@
     def _repr_cli_child(self, c, format):
         if c.tag in constants.acl_rule_names:
             return cli_acl_rule(c, format)
-        return cli_acl_roleref(c, format)
+        elif c.tag == "role_ref":
+            return cli_acl_roleref(c, format)
+        elif c.tag == "role":
+            return cli_acl_role(c)
+        elif c.tag == "acl_permission":
+            return cli_acl_permission(c)
 
 
 class CibTag(CibObject):
@@ -2060,9 +2069,12 @@
     "fencing-topology": ("fencing_topology", CibFencingOrder, "configuration"),
     "acl_role": ("role", CibAcl, "acls"),
     "acl_user": ("user", CibAcl, "acls"),
+    "acl_target": ("acl_target", CibAcl, "acls"),
+    "acl_group": ("acl_group", CibAcl, "acls"),
     "tag": ("tag", CibTag, "tags"),
 }
 
+
 # generate a translation cli -> tag
 backtrans = odict((item[0], key) for key, item in cib_object_map.iteritems())
 
@@ -3028,7 +3040,8 @@
         for cli in processing_sort([edit_d[x] for x in mk_set]):
             obj = self.create_from_cli(cli)
             if not obj:
-                common_debug("create_from_cli '%s' failed" % 
(etree.tostring(cli)))
+                common_debug("create_from_cli '%s' failed" %
+                             (etree.tostring(cli, pretty_print=True)))
                 return False
             test_l.append(obj)
         for id in upd_set:
@@ -3036,7 +3049,7 @@
             if not obj:
                 common_debug("%s not found!" % (id))
                 return False
-            node, _, _ = postprocess_cli(edit_d[id])
+            node, _, _ = postprocess_cli(edit_d[id], oldnode=obj.node)
             if node is None:
                 common_debug("postprocess_cli failed: %s" % (id))
                 return False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/cliformat.py 
new/crmsh/modules/cliformat.py
--- old/crmsh/modules/cliformat.py      2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/cliformat.py      2014-06-03 22:39:19.000000000 +0200
@@ -379,6 +379,36 @@
     return "%s:%s" % (clidisplay.keyword("role"),
                       clidisplay.attr_value(node.get("id")))
 
+
+def cli_acl_role(node):
+    return clidisplay.attr_value(node.get("id"))
+
+
+def cli_acl_spec2_format(xml_spec, v):
+    key_f = clidisplay.keyword(xml_spec)
+    if xml_spec == "xpath":
+        (shortcut, spec_l) = find_acl_shortcut(v)
+        if shortcut:
+            key_f = clidisplay.keyword(shortcut)
+            v_f = ':'.join([clidisplay.attr_value(x) for x in spec_l])
+        else:
+            v_f = '"%s"' % clidisplay.attr_value(v)
+    else:  # ref, type and attr
+        v_f = '%s' % clidisplay.attr_value(v)
+    return v_f and '%s:%s' % (key_f, v_f) or key_f
+
+
+def cli_acl_permission(node):
+    s = [clidisplay.keyword(node.get('kind'))]
+    if node.get('id'):
+        s.append(head_id_format(node.get('id')))
+    if node.get('description'):
+        s.append(nvpair_format('description', node.get('description')))
+    for attrname, cliname in constants.acl_spec_map_2_rev:
+        if attrname in node.attrib:
+            s.append(cli_acl_spec2_format(cliname, node.get(attrname)))
+    return ' '.join(s)
+
 #
 ################################################################
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/command.py new/crmsh/modules/command.py
--- old/crmsh/modules/command.py        2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/command.py        2014-06-03 22:39:19.000000000 +0200
@@ -20,7 +20,6 @@
 #   Mostly, what these functions do is store extra metadata
 #   inside the functions.
 
-#from functools import wraps
 import inspect
 import help as help_module
 import ui_utils
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/constants.py 
new/crmsh/modules/constants.py
--- old/crmsh/modules/constants.py      2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/constants.py      2014-06-03 22:39:19.000000000 +0200
@@ -77,6 +77,23 @@
     "tag": "tag",
     "attribute": "attribute",
 }
+# ACLs were rewritten in pacemaker 1.1.12
+# this is the new acl syntax
+acl_spec_map_2 = {
+    "xpath": "xpath",
+    "ref": "reference",
+    "reference": "reference",
+    "tag": "object-type",
+    "type": "object-type",
+    "attr": "attribute",
+    "attribute": "attribute"
+}
+
+acl_spec_map_2_rev = (('xpath', 'xpath'),
+                      ('reference', 'ref'),
+                      ('attribute', 'attr'),
+                      ('object-type', 'type'))
+
 acl_shortcuts = {
     "meta":
     (r"//primitive\[@id='@@'\]/meta_attributes", r"/nvpair\[@name='@@'\]"),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/idmgmt.py new/crmsh/modules/idmgmt.py
--- old/crmsh/modules/idmgmt.py 2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/idmgmt.py 2014-06-03 22:39:19.000000000 +0200
@@ -17,9 +17,8 @@
 
 import constants
 import copy
-from msg import common_error, common_debug, id_used_err
+from msg import common_error, id_used_err
 import xmlutil
-from lxml import etree
 
 
 '''
@@ -59,7 +58,6 @@
     '''
     Create a unique id for the xml node.
     '''
-    #common_debug("idmgmt.new: node=%s, pfx=%s" % (etree.tostring(node), pfx))
     name = node.get("name")
     if node.tag == "nvpair":
         node_id = "%s-%s" % (pfx, name)
@@ -151,7 +149,6 @@
 def save(node_id):
     if not node_id:
         return
-    #common_debug("id_store: saved %s" % node_id)
     _id_store[node_id] = 1
 
 
@@ -171,7 +168,6 @@
         return
     try:
         del _id_store[node_id]
-        #common_debug("id_store: removed %s" % node_id)
     except KeyError:
         pass
 
@@ -194,8 +190,6 @@
     '''
     old_id = oldnode.get("id") if oldnode is not None else None
     new_id = node.get("id") or old_id or node.get("uname")
-    #common_debug("idmgmt.set: node=%s, new_id=%s, old_id=%s, id_hint=%s" %
-    #             (etree.tostring(node), new_id, old_id, id_hint))
     if new_id:
         save(new_id)
     elif id_required:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/pacemaker.py 
new/crmsh/modules/pacemaker.py
--- old/crmsh/modules/pacemaker.py      2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/pacemaker.py      2014-06-03 22:39:19.000000000 +0200
@@ -18,6 +18,7 @@
 import os
 import tempfile
 import copy
+import re
 from lxml import etree
 
 
@@ -25,16 +26,6 @@
     '''PacemakerError exceptions'''
 
 
-known_schemas = {
-    "pacemaker-0.7": ("rng", "pacemaker-1.0.rng"),
-    "pacemaker-1.0": ("rng", "pacemaker-1.0.rng"),
-    "pacemaker-1.1": ("rng", "pacemaker-1.1.rng"),
-    "pacemaker-1.2": ("rng", "pacemaker-1.2.rng"),
-    "pacemaker-1.3": ("rng", "pacemaker-1.3.rng"),
-    "pacemaker-2.0": ("rng", "pacemaker-2.0.rng"),
-}
-
-
 def get_validate_name(cib_elem):
     if cib_elem is not None:
         return cib_elem.get("validate-with")
@@ -44,17 +35,15 @@
 
 def get_validate_type(cib_elem):
     validate_name = get_validate_name(cib_elem)
-    if validate_name is None or known_schemas.get(validate_name) is None:
-        return None
-    else:
-        return known_schemas.get(validate_name)[0]
+    if re.match(r"pacemaker-\d+\.\d+", validate_name):
+        return "rng"
+    return None
 
 
 def get_schema_filename(validate_name):
-    if validate_name is None or known_schemas.get(validate_name) is None:
-        return None
-    else:
-        return known_schemas.get(validate_name)[1]
+    if re.match(r"pacemaker-\d+\.\d+", validate_name):
+        return "%s.rng" % (validate_name)
+    return None
 
 
 def read_schema_local(validate_name, file_path):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/parse.py new/crmsh/modules/parse.py
--- old/crmsh/modules/parse.py  2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/parse.py  2014-06-03 22:39:19.000000000 +0200
@@ -662,12 +662,11 @@
             out = xmlbuilder.new('master')
         out.set('id', self.match_identifier())
 
-        # FIXME: as a post-processing step in cib-factory, reshuffle entities
-        # so that the things that should be inside the container node are
-        xmlbuilder.child(out, 'crmsh-ref', id=self.match_resource())
+        child = xmlbuilder.new('crmsh-ref', id=self.match_resource())
         xmlbuilder.maybe_set(out, 'description', self.try_match_description())
         self.match_arguments(out, {'params': 'instance_attributes',
                                    'meta': 'meta_attributes'})
+        out.append(child)
         return out
 
     parse_master = _master_or_clone
@@ -690,11 +689,11 @@
                 self.err("child %s listed more than once in group %s" %
                          (child, out.get('id')))
             children.append(child)
-        for child in children:
-            xmlbuilder.child(out, 'crmsh-ref', id=child)
         xmlbuilder.maybe_set(out, 'description', self.try_match_description())
         self.match_arguments(out, {'params': 'instance_attributes',
                                    'meta': 'meta_attributes'})
+        for child in children:
+            xmlbuilder.child(out, 'crmsh-ref', id=child)
         return out
 
 
@@ -917,7 +916,7 @@
             attrs.set(idkey, idval)
         for rule in self.match_rules():
             attrs.append(rule)
-        for nvp in self.match_nvpairs():
+        for nvp in self.match_nvpairs(minpairs=0):
             attrs.append(nvp)
         return root
 
@@ -1005,18 +1004,11 @@
     _ROLE_REF_RE = re.compile(r'role:(.+)$', re.IGNORECASE)
 
     def can_parse(self):
-        return ('user', 'role')
+        return ('user', 'role', 'acl_target', 'acl_group')
 
     def parse(self, cmd):
         return self.begin_dispatch(cmd, min_args=2)
 
-    def parse_role(self):
-        out = xmlbuilder.new('acl_role')
-        out.set('id', self.match_identifier())
-        while self.has_tokens():
-            out.append(self._add_rule())
-        return out
-
     def parse_user(self):
         out = xmlbuilder.new('acl_user')
         out.set('id', self.match_identifier())
@@ -1029,6 +1021,61 @@
                 out.append(self._add_rule())
         return out
 
+    def parse_acl_target(self):
+        out = xmlbuilder.new('acl_target')
+        out.set('id', self.match_identifier())
+        while self.has_tokens():
+            xmlbuilder.child(out, 'role', id=self.match_identifier())
+        return out
+
+    def parse_acl_group(self):
+        out = xmlbuilder.new('acl_group')
+        out.set('id', self.match_identifier())
+        while self.has_tokens():
+            xmlbuilder.child(out, 'role', id=self.match_identifier())
+        return out
+
+    def parse_role(self):
+        out = xmlbuilder.new('acl_role')
+        out.set('id', self.match_identifier())
+
+        if self.validation.acl_2_0():
+            xmlbuilder.maybe_set(out, "description", 
self.try_match_description())
+            while self.has_tokens():
+                out.append(self._add_permission())
+        else:
+            while self.has_tokens():
+                out.append(self._add_rule())
+        return out
+
+    _PERM_RE = re.compile(r"([^:]+)(?::(.+))?$", re.I)
+
+    def _is_permission(self, val):
+        def permission(x):
+            return x in constants.acl_spec_map_2 or x in 
constants.acl_shortcuts
+        x = val.split(':', 1)
+        return len(x) > 0 and permission(x[0])
+
+    def _add_permission(self):
+        rule = xmlbuilder.new('acl_permission')
+        rule.set('kind', self.match(self._ACL_RIGHT_RE).lower())
+        if self.try_match_initial_id():
+            rule.set('id', self.matched(1))
+        xmlbuilder.maybe_set(rule, "description", self.try_match_description())
+        while self.has_tokens():
+            if not self._is_permission(self.current_token()):
+                break
+            self.match(self._PERM_RE, errmsg="Expected <type>:<spec>")
+            typ = self.matched(1)
+            typ = constants.acl_spec_map_2.get(typ, typ)
+            val = self.matched(2)
+            if typ in constants.acl_shortcuts:
+                typ, val = self._expand_shortcuts_2(typ, val)
+            elif val is None:
+                self.err("Expected <type>:<spec>")
+            rule.set(typ, val)
+        return rule
+
     def _add_rule(self):
         rule = xmlbuilder.new(self.match(self._ACL_RIGHT_RE).lower())
         eligible_specs = constants.acl_spec_map.values()
@@ -1060,6 +1107,53 @@
             pass
         return False
 
+    def _remove_spec_2(self, speclist, spec):
+        """
+        Remove spec from list of eligible specs.
+        Returns true if spec parse is complete.
+        """
+        try:
+            speclist.remove(spec)
+            if spec == 'xpath':
+                speclist.remove('reference')
+                speclist.remove('object-type')
+            elif spec in ('reference', 'object-type'):
+                speclist.remove('xpath')
+            else:
+                return True
+        except ValueError:
+            pass
+        return False
+
+    def _expand_shortcuts_2(self, typ, val):
+        '''
+        expand xpath shortcuts: the typ prefix names the shortcut
+        '''
+        expansion = constants.acl_shortcuts[typ]
+        if val is None:
+            if '@@' in expansion[0]:
+                self.err("Missing argument to ACL shortcut %s" % (typ))
+            return 'xpath', expansion[0]
+        a = val.split(':')
+        xpath = ""
+        exp_i = 0
+        for tok in a:
+            try:
+                # some expansions may contain no id placeholders
+                # of course, they don't consume input tokens
+                if '@@' not in expansion[exp_i]:
+                    xpath += expansion[exp_i]
+                    exp_i += 1
+                xpath += expansion[exp_i].replace('@@', tok)
+                exp_i += 1
+            except:
+                return []
+        # need to remove backslash chars which were there to escape
+        # special characters in expansions when used as regular
+        # expressions (mainly '[]')
+        val = xpath.replace("\\", "")
+        return 'xpath', val
+
     def _expand_shortcuts(self, l):
         '''
         Expand xpath shortcuts. The input list l contains the user
@@ -1346,6 +1440,14 @@
     def op_attributes(self):
         return olist(schema.get('attr', 'op', 'a'))
 
+    def acl_2_0(self):
+        vname = schema.validate_name()
+        sp = vname.split('-')
+        try:
+            return sp[0] == 'pacemaker' and float(sp[1]) >= 2.0
+        except Exception:
+            return False
+
 
 class CliParser(object):
     parsers = {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/ra.py new/crmsh/modules/ra.py
--- old/crmsh/modules/ra.py     2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/ra.py     2014-06-03 22:39:19.000000000 +0200
@@ -155,9 +155,9 @@
         rc, l = stdout2list("crm_resource %s" % opts, stderr_on=False)
         # not clear when/why crm_resource exits with non-zero
         # code
-        if rc != 0:
-            common_debug("crm_resource %s exited with code %d" %
-                         (opts, rc))
+        #if rc != 0:
+        #    common_debug("crm_resource %s exited with code %d" %
+        #                 (opts, rc))
         return l
 
     def meta(self, ra_class, ra_type, ra_provider):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/schema.py new/crmsh/modules/schema.py
--- old/crmsh/modules/schema.py 2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/schema.py 2014-06-03 22:39:19.000000000 +0200
@@ -90,11 +90,17 @@
     reset()
 
 
-def test_schema(self, cib):
+def test_schema(cib):
     crm_schema = _load_schema(cib)
     return crm_schema.validate_name
 
 
+def validate_name():
+    if _crm_schema is None:
+        return 'pacemaker-2.0'
+    return _crm_schema.validate_name
+
+
 def get(t, name, set=None):
     if _crm_schema is None:
         return []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/ui_configure.py 
new/crmsh/modules/ui_configure.py
--- old/crmsh/modules/ui_configure.py   2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/ui_configure.py   2014-06-03 22:39:19.000000000 +0200
@@ -742,6 +742,14 @@
                     status"""
         return self.__conf_object(context.get_command_name(), *args)
 
+    @command.skill_level('expert')
+    def do_acl_target(self, context, *args):
+        return self.__conf_object(context.get_command_name(), *args)
+
+    @command.skill_level('expert')
+    def do_acl_group(self, context, *args):
+        return self.__conf_object(context.get_command_name(), *args)
+
     @command.skill_level('administrator')
     @command.completers_repeating(compl.null, top_rsc_tmpl_id_list)
     def do_tag(self, context, *args):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/ui_resource.py 
new/crmsh/modules/ui_resource.py
--- old/crmsh/modules/ui_resource.py    2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/ui_resource.py    2014-06-03 22:39:19.000000000 +0200
@@ -25,7 +25,7 @@
 import ui_utils
 import options
 
-from msg import common_error, common_err, common_info, common_debug, 
common_warn
+from msg import common_error, common_err, common_info, common_debug
 from msg import no_prog_err
 from cibconfig import cib_factory
 
@@ -311,6 +311,7 @@
 
     @command.alias('move')
     @command.skill_level('administrator')
+    @command.wait
     @command.completers_repeating(compl.resources, compl.nodes,
                                   compl.choice(['reboot', 'forever', 'force']))
     def do_migrate(self, context, rsc, *args):
@@ -336,6 +337,7 @@
 
     @command.alias('unmove')
     @command.skill_level('administrator')
+    @command.wait
     @command.completers(compl.resources)
     def do_unmigrate(self, context, rsc):
         "usage: unmigrate <rsc>"
@@ -352,6 +354,7 @@
         # all live nodes.
         return cleanup_resource(resource, node)
 
+    @command.wait
     @command.completers(compl.resources, _attrcmds, compl.nodes)
     def do_failcount(self, context, rsc, cmd, node, value=None):
         """usage:
@@ -373,7 +376,6 @@
                                     rsc, cmd, param, value)
 
     @command.skill_level('administrator')
-    @command.wait
     @command.completers(compl.resources,
                         compl.choice(['set', 'stash', 'unstash', 'delete', 
'show', 'check']))
     def do_secret(self, context, rsc, cmd, param, value=None):
@@ -455,54 +457,105 @@
             return None
         return rsc
 
-    @command.wait
+    def _add_trace_op(self, rsc, op, interval):
+        from lxml import etree
+        n = etree.Element('op')
+        n.set('name', op)
+        n.set('interval', interval)
+        n.set(constants.trace_ra_attr, '1')
+        return rsc.add_operation(n)
+
+    def _trace_resource(self, context, rsc_id, rsc):
+        op_nodes = rsc.node.xpath('.//op')
+
+        def trace(name):
+            if not any(o for o in op_nodes if o.get('name') == name):
+                if not self._add_trace_op(rsc, name, '0'):
+                    context.err("Failed to add trace for %s:%s" % (rsc_id, 
name))
+        trace('start')
+        trace('stop')
+        if xmlutil.is_ms(rsc.node):
+            trace('promote')
+            trace('demote')
+        for op_node in op_nodes:
+            rsc.set_op_attr(op_node, constants.trace_ra_attr, "1")
+
+    def _trace_op(self, context, rsc_id, rsc, op):
+        op_nodes = rsc.node.xpath('.//op[@name="%s"]' % (op))
+        if not op_nodes:
+            if op == 'monitor':
+                context.err("No monitor operation configured for %s" % 
(rsc_id))
+            if not self._add_trace_op(rsc, op, '0'):
+                context.err("Failed to add trace for %s:%s" % (rsc_id, op))
+        for op_node in op_nodes:
+            rsc.set_op_attr(op_node, constants.trace_ra_attr, "1")
+
+    def _trace_op_interval(self, context, rsc_id, rsc, op, interval):
+        op_node = xmlutil.find_operation(rsc.node, op, interval)
+        if op_node is None and utils.crm_msec(interval) != 0:
+            context.err("Operation %s with interval %s not found in %s" % (op, 
interval, rsc_id))
+        if op_node is None:
+            if not self._add_trace_op(rsc, op, interval):
+                context.err("Failed to add trace for %s:%s" % (rsc_id, op))
+        else:
+            rsc.set_op_attr(op_node, constants.trace_ra_attr, "1")
+
     @command.completers(compl.primitives, _raoperations)
-    def do_trace(self, context, rsc_id, op, interval=None):
-        'usage: trace <rsc> <op> [<interval>]'
+    def do_trace(self, context, rsc_id, op=None, interval=None):
+        'usage: trace <rsc> [<op>] [<interval>]'
         rsc = self._get_trace_rsc(rsc_id)
         if not rsc:
             return False
-        if not interval:
-            interval = op == "monitor" and "non-0" or "0"
         if op == "probe":
             op = "monitor"
-        op_node = xmlutil.find_operation(rsc.node, op, interval)
-        if op_node is None and utils.crm_msec(interval) != 0:
-            common_err("not allowed to create non-0 interval operation %s" % 
op)
-            return False
-        if op_node is None:
-            from lxml import etree
-            n = etree.Element('op')
-            n.set('name', op)
-            n.set('interval', interval)
-            n.set(constants.trace_ra_attr, '1')
-            if not rsc.add_operation(n):
-                return False
+        if op is None:
+            self._trace_resource(context, rsc_id, rsc)
+        elif interval is None:
+            self._trace_op(context, rsc_id, rsc, op)
         else:
-            op_node = rsc.set_op_attr(op_node, constants.trace_ra_attr, "1")
+            self._trace_op_interval(context, rsc_id, rsc, op, interval)
         if not cib_factory.commit():
             return False
-        if op == "monitor" and utils.crm_msec(interval) != 0:
-            common_warn("please CLEANUP the RA trace directory %s regularly!" %
-                        config.path.heartbeat_dir)
+        if op is not None:
+            common_info("Trace for %s:%s is written to %s/trace_ra/" %
+                        (rsc_id, op, config.path.heartbeat_dir))
+        else:
+            common_info("Trace for %s is written to %s/trace_ra/" %
+                        (rsc_id, config.path.heartbeat_dir))
+        if op is not None and op != "monitor":
+            common_info("Trace set, restart %s to trace the %s operation" % 
(rsc_id, op))
         else:
-            common_info("restart %s to get the trace" % rsc_id)
+            common_info("Trace set, restart %s to trace non-monitor 
operations" % (rsc_id))
         return True
 
-    @command.wait
+    def _remove_trace(self, rsc, op_node):
+        from lxml import etree
+        common_debug("op_node: %s" % (etree.tostring(op_node)))
+        op_node = rsc.del_op_attr(op_node, constants.trace_ra_attr)
+        if rsc.is_dummy_operation(op_node):
+            rsc.del_operation(op_node)
+
     @command.completers(compl.primitives, _raoperations)
-    def do_untrace(self, context, rsc_id, op, interval=None):
-        'usage: untrace <rsc> <op> [<interval>]'
+    def do_untrace(self, context, rsc_id, op=None, interval=None):
+        'usage: untrace <rsc> [<op>] [<interval>]'
         rsc = self._get_trace_rsc(rsc_id)
         if not rsc:
             return False
         if op == "probe":
             op = "monitor"
-        op_node = xmlutil.find_operation(rsc.node, op, interval=interval)
-        if op_node is None:
-            common_err("operation %s does not exist in %s" % (op, rsc.obj_id))
-            return False
-        op_node = rsc.del_op_attr(op_node, constants.trace_ra_attr)
-        if rsc.is_dummy_operation(op_node):
-            rsc.del_operation(op_node)
+        if op is None:
+            n = 0
+            for tn in rsc.node.xpath('.//*[@%s]' % (constants.trace_ra_attr)):
+                self._remove_trace(rsc, tn)
+                n += 1
+            for tn in rsc.node.xpath('.//*[@name="%s"]' % 
(constants.trace_ra_attr)):
+                if tn.getparent().getparent().tag == 'op':
+                    self._remove_trace(rsc, tn.getparent().getparent())
+                    n += 1
+        else:
+            op_node = xmlutil.find_operation(rsc.node, op, interval=interval)
+            if op_node is None:
+                common_err("operation %s does not exist in %s" % (op, 
rsc.obj_id))
+                return False
+            self._remove_trace(rsc, op_node)
         return cib_factory.commit()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/utils.py new/crmsh/modules/utils.py
--- old/crmsh/modules/utils.py  2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/utils.py  2014-06-03 22:39:19.000000000 +0200
@@ -1307,10 +1307,10 @@
     try:
         rc, outp = stdout2list(['crm_node', '-l'], stderr_on=False, 
shell=False)
         if rc != 0:
-            raise IOError("crm_node failed (RC=%s): %s" % (rc, outp))
+            raise ValueError("Error listing cluster nodes: crm_node (rc=%d)" % 
(rc))
         return [x for x in [getname(line.split()) for line in outp] if x and x 
!= '(null)']
     except OSError, msg:
-        raise ValueError("Error getting list of nodes from crm_node: %s" % 
(msg))
+        raise ValueError("Error listing cluster nodes: %s" % (msg))
 
 
 def service_info(name):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/modules/xmlutil.py new/crmsh/modules/xmlutil.py
--- old/crmsh/modules/xmlutil.py        2014-05-27 20:11:56.000000000 +0200
+++ new/crmsh/modules/xmlutil.py        2014-06-03 22:39:19.000000000 +0200
@@ -29,7 +29,7 @@
 from msg import common_err, common_error, common_debug, cib_parse_err, err_buf
 import userdir
 import utils
-from utils import add_sudo, str2file, str2tmp, pipe_string, get_boolean
+from utils import add_sudo, str2file, str2tmp, get_boolean
 from utils import get_stdout, stdout2list, crm_msec, crm_time_cmp
 from utils import olist, get_cib_in_use, get_tempdir
 
@@ -693,7 +693,6 @@
 
 
 def set_id_used_attr(e):
-    common_debug("setting id used: %s" % (etree.tostring(e)))
     e.set("__id_used", "Yes")
 
 
@@ -707,7 +706,6 @@
 
 
 def remove_id_used_attributes(e):
-    common_debug("clearing id used: %s" % (etree.tostring(e)))
     if e is not None:
         xmltraverse(e, remove_id_used_attr)
 
@@ -744,17 +742,23 @@
     '''
     Setting interval to "non-0" means get the first op with interval
     different from 0.
+    Not setting interval at all means get the only matching op, or the
+    0 op (if any)
     '''
+    matching_name = []
+    for ops in rsc_node.findall("operations"):
+        matching_name.extend([op for op in ops.iterchildren("op")
+                              if op.get("name") == name])
+    if interval is None and len(matching_name) == 1:
+        return matching_name[0]
     interval = interval or "0"
-    op_node_l = rsc_node.findall("operations")
-    for ops in op_node_l:
-        for c in ops.iterchildren("op"):
-            if c.get("name") != name:
-                continue
-            if (interval == "non-0" and
-                    crm_msec(c.get("interval")) > 0) or \
-                    crm_time_cmp(c.get("interval"), interval) == 0:
-                return c
+    for op in matching_name:
+        opint = op.get("interval")
+        if interval == "non-0" and crm_msec(opint) > 0:
+            return op
+        if crm_time_cmp(opint, interval) == 0:
+            return op
+    return None
 
 
 def get_op_timeout(rsc_node, op, default_timeout):
@@ -1176,6 +1180,8 @@
         return isinstance(x.tag, basestring) and x.tag or x.text
 
     def sortby(v):
+        if v.tag == 'primitive':
+            return v.tag
         return tagflat(v) + ''.join(sorted(v.attrib.keys() + 
v.attrib.values()))
 
     def safe_strip(text):
@@ -1193,6 +1199,9 @@
         return fail("number of children differ")
     elif len(a) == 0:
         return True
+
+    # order matters here, but in a strange way:
+    # all primitive tags should sort the same..
     sorted_children = zip(sorted(a, key=sortby), sorted(b, key=sortby))
     return all(xml_equals_unordered(a, b) for a, b in sorted_children)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/test/unittests/test_cliformat.py 
new/crmsh/test/unittests/test_cliformat.py
--- old/crmsh/test/unittests/test_cliformat.py  2014-05-27 20:11:56.000000000 
+0200
+++ new/crmsh/test/unittests/test_cliformat.py  2014-06-03 22:39:19.000000000 
+0200
@@ -195,3 +195,7 @@
               'params 3: rule #uname eq node1 interface=eth1 ' +
               'params 2: rule #uname eq node2 interface=eth2 port=8888 ' +
               'params 1: interface=eth0 port=9999')
+
+
+def test_new_acls():
+    roundtrip('role fum description=test read a: description=test2 
xpath:"*[@name=karl]"')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh/test/unittests/test_parse.py 
new/crmsh/test/unittests/test_parse.py
--- old/crmsh/test/unittests/test_parse.py      2014-05-27 20:11:56.000000000 
+0200
+++ new/crmsh/test/unittests/test_parse.py      2014-06-03 22:39:19.000000000 
+0200
@@ -44,6 +44,9 @@
                 'start-delay', 'interval-origin', 'timeout', 'enabled',
                 'record-pending', 'role', 'requires', 'on-fail']
 
+    def acl_2_0(self):
+        return True
+
 
 class TestBaseParser(unittest.TestCase):
     def setUp(self):
@@ -151,19 +154,17 @@
         out = self.parser.parse('ms m0 resource params a=b')
         self.assertEqual(out.get('id'), 'm0')
         print etree.tostring(out)
-        self.assertEqual(out[0].tag, 'crmsh-ref')
-        self.assertEqual(out[0].get('id'), 'resource')
+        self.assertEqual(['resource'], out.xpath('./crmsh-ref/@id'))
         self.assertEqual(['b'], 
out.xpath('instance_attributes/nvpair[@name="a"]/@value'))
 
         out = self.parser.parse('master ma resource meta a=b')
         self.assertEqual(out.get('id'), 'ma')
-        self.assertEqual(out[0].tag, 'crmsh-ref')
-        self.assertEqual(out[0].get('id'), 'resource')
+        self.assertEqual(['resource'], out.xpath('./crmsh-ref/@id'))
         self.assertEqual(['b'], 
out.xpath('meta_attributes/nvpair[@name="a"]/@value'))
 
         out = self.parser.parse('clone clone-1 resource meta a=b')
         self.assertEqual(out.get('id'), 'clone-1')
-        self.assertEqual(out[0].get('id'), 'resource')
+        self.assertEqual(['resource'], out.xpath('./crmsh-ref/@id'))
         self.assertEqual(['b'], 
out.xpath('meta_attributes/nvpair[@name="a"]/@value'))
 
         out = self.parser.parse('group group-1 a')
@@ -349,6 +350,18 @@
                                 "read ref:bigdb")
         self.assertEqual(4, len(out))
 
+        # new type of acls
+        out = self.parser.parse("acl_target foo a b c")
+        self.assertEqual('acl_target', out.tag)
+        self.assertEqual('foo', out.get('id'))
+        self.assertEqual(['a', 'b', 'c'], out.xpath('./role/@id'))
+        out = self.parser.parse("acl_group fee a b c")
+        self.assertEqual('acl_group', out.tag)
+        self.assertEqual('fee', out.get('id'))
+        self.assertEqual(['a', 'b', 'c'], out.xpath('./role/@id'))
+        out = self.parser.parse('role fum description="test" read a: 
description="test2" xpath:*[@name=\\"karl\\"]')
+        self.assertEqual(['*[@name="karl"]'], 
out.xpath('/acl_role/acl_permission/@xpath'))
+
     def test_xml(self):
         out = self.parser.parse('xml <node uname="foo-1"/>')
         self.assertEqual('node', out.tag)
@@ -380,6 +393,15 @@
         out = self.parser.parse('rsc_defaults failure-timeout=3m foo:')
         self.assertFalse(out)
 
+    def test_empty_property_sets(self):
+        out = self.parser.parse('rsc_defaults defaults:')
+        self.assertEqual('<rsc_defaults><meta_attributes 
id="defaults"/></rsc_defaults>',
+                         etree.tostring(out))
+
+        out = self.parser.parse('op_defaults defaults:')
+        self.assertEqual('<op_defaults><meta_attributes 
id="defaults"/></op_defaults>',
+                         etree.tostring(out))
+
     def test_fencing(self):
         # num test nodes are 3
 
@@ -553,7 +575,7 @@
             '<op name="monitor" role="Started" rsc="d2" interval="60s" 
timeout="30s"/>',
             '<group id="g1"><crmsh-ref id="d1"/><crmsh-ref id="d2"/></group>',
             '<primitive id="d3" class="ocf" provider="pacemaker" 
type="Dummy"/>',
-            '<clone id="c"><crmsh-ref id="d3"/><meta_attributes><nvpair 
name="clone-max" value="1"/></meta_attributes></clone>',
+            '<clone id="c"><meta_attributes><nvpair name="clone-max" 
value="1"/></meta_attributes><crmsh-ref id="d3"/></clone>',
             '<primitive id="d4" class="ocf" provider="pacemaker" 
type="Dummy"/>',
             '<master id="m"><crmsh-ref id="d4"/></master>',
             '<primitive id="s5" class="ocf" provider="pacemaker" 
type="Stateful"><operations id-ref="d1-ops"/></primitive>',

-- 
To unsubscribe, e-mail: opensuse-commit+unsubscr...@opensuse.org
For additional commands, e-mail: opensuse-commit+h...@opensuse.org

Reply via email to