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 - [email protected] + +- 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 - [email protected] + +- 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: [email protected] For additional commands, e-mail: [email protected]
