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 - kgronlund@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 - kgronlund@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('