Hello community, here is the log from the commit of package crmsh for openSUSE:Factory checked in at 2017-12-14 11:03:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Thu Dec 14 11:03:28 2017 rev:135 rq:556723 version:4.0.0+git.1513179435.e1d17d7b Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2017-12-06 08:58:47.340546096 +0100 +++ /work/SRC/openSUSE:Factory/.crmsh.new/crmsh.changes 2017-12-14 11:03:29.958189541 +0100 @@ -1,0 +2,15 @@ +Wed Dec 13 16:15:40 UTC 2017 - [email protected] + +- Update to version 4.0.0+git.1513179435.e1d17d7b: + * high: scripts: Fix Python 3 migration issues in health, check-uptime (bsc#1071519) + +------------------------------------------------------------------- +Tue Dec 12 15:44:48 UTC 2017 - [email protected] + +- Update to version 4.0.0+git.1513011384.5aebf8a4: + * high: parse: Support new alert syntax (#280) (bsc#1069129) + * high: parse: Support new container bundles (fate#323415) + * low: hb_report: return "" to avoid TypeError + * low: ui_configure: use filter_keys replace any_startswith + +------------------------------------------------------------------- Old: ---- crmsh-4.0.0+git.1512406036.adc26906.tar.bz2 New: ---- crmsh-4.0.0+git.1513179435.e1d17d7b.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.LGgdfp/_old 2017-12-14 11:03:30.970140690 +0100 +++ /var/tmp/diff_new_pack.LGgdfp/_new 2017-12-14 11:03:30.970140690 +0100 @@ -36,7 +36,7 @@ Summary: High Availability cluster command-line interface License: GPL-2.0+ Group: %{pkg_group} -Version: 4.0.0+git.1512406036.adc26906 +Version: 4.0.0+git.1513179435.e1d17d7b Release: 0 Url: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.LGgdfp/_old 2017-12-14 11:03:31.006138952 +0100 +++ /var/tmp/diff_new_pack.LGgdfp/_new 2017-12-14 11:03:31.006138952 +0100 @@ -1,4 +1,4 @@ <servicedata> <service name="tar_scm"> <param name="url">git://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">adc269069314c9a6ad2b0b378745d9161604097a</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">e1d17d7b1a3b6b4a3ca0211d72d8102b150c0c65</param></service></servicedata> \ No newline at end of file ++++++ crmsh-4.0.0+git.1512406036.adc26906.tar.bz2 -> crmsh-4.0.0+git.1513179435.e1d17d7b.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/cibconfig.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/cibconfig.py --- old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/cibconfig.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/cibconfig.py 2017-12-13 16:37:15.000000000 +0100 @@ -686,7 +686,7 @@ 'alerts': 'alert', } - idless = set(['operations', 'fencing-topology']) + idless = set(['operations', 'fencing-topology', 'network', 'docker', 'rkt', 'storage']) isref = set(['resource_ref', 'obj_ref', 'crmsh-ref']) def needs_id(node): @@ -966,6 +966,17 @@ elif idref is not None: ret += "%s " % (nvpair_format("$id-ref", idref)) + if node.tag in ["docker", "network"]: + for item in node.keys(): + ret += "%s " % nvpair_format(item, node.get(item)) + if node.tag == "primitive": + ret += node.get('id') + for _type in ["port-mapping", "storage-mapping"]: + for c in node.iterchildren(_type): + ret += "%s " % _type + for item in c.keys(): + ret += "%s " % nvpair_format(item, c.get(item)) + score = node.get("score") if score: ret += "%s: " % (clidisplay.score(score)) @@ -1630,6 +1641,29 @@ child_rsc.repr_gv(sg_obj, from_grp=True) +class CibBundle(CibObject): + ''' + bundle type resource + ''' + set_names = { + "instance_attributes": "params", + "meta_attributes": "meta", + "docker": "docker", + "network": "network", + "storage": "storage", + "primitive": "primitive", + "meta": "meta" + } + + def _repr_cli_head(self, format_mode): + s = clidisplay.keyword(self.obj_type) + ident = clidisplay.ident(self.obj_id) + return "%s %s" % (s, ident) + + def _repr_cli_child(self, c, format_mode): + return self._attr_set_str(c) + + def _check_if_constraint_ref_is_child(obj): """ Used by check_sanity for constraints to verify @@ -2104,6 +2138,17 @@ def _repr_cli_child(self, c, format_mode): if c.tag in self.set_names: return self._attr_set_str(c) + elif c.tag == "select": + r = ["select"] + for sel in c.iterchildren(): + if not sel.tag.startswith('select_'): + continue + r.append(sel.tag.lstrip('select_')) + if sel.tag == 'select_attributes': + r.append('{') + r.extend(sel.xpath('attribute/@name')) + r.append('}') + return ' '.join(r) elif c.tag == "recipient": r = ["to"] is_complex = self._is_complex() @@ -2161,6 +2206,7 @@ "clone": ("clone", CibContainer, "resources"), "master": ("ms", CibContainer, "resources"), "template": ("rsc_template", CibPrimitive, "resources"), + "bundle": ("bundle", CibBundle, "resources"), "rsc_location": ("location", CibLocation, "constraints"), "rsc_colocation": ("colocation", CibSimpleConstraint, "constraints"), "rsc_order": ("order", CibSimpleConstraint, "constraints"), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/constants.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/constants.py --- old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/constants.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/constants.py 2017-12-13 16:37:15.000000000 +0100 @@ -83,6 +83,7 @@ "group": "group", "clone": "clone", "master": "ms", + "bundle": "bundle", "rsc_location": "location", "rsc_colocation": "colocation", "rsc_order": "order", @@ -223,6 +224,7 @@ "clone-max", "clone-node-max", "notify", "globally-unique", "ordered", "interleave", "master-max", "master-node-max", "description", ) +bundle_meta_attributes = common_meta_attributes alert_meta_attributes = ( "timeout", "timestamp-format" ) @@ -338,4 +340,137 @@ "cluster-name") pcmk_version = "" # set later +container_type = ["docker", "rkt"] +container_helptxt = { + "docker": { + "image": """image:(string) + Docker image tag(required)""", + + "replicas": """replicas:(integer) + Default:Value of masters if that is positive, else 1 + A positive integer specifying the number of container instances to launch""", + + "replicas-per-host": """replicas-per-host:(integer) + Default:1 + A positive integer specifying the number of container instances allowed to + run on a single node""", + + "masters": """masters:(integer) + Default:0 + A non-negative integer that, if positive, indicates that the containerized + service should be treated as a multistate service, with this many replicas + allowed to run the service in the master role""", + + "run-command": """run-command:(string) + Default:/usr/sbin/pacemaker_remoted if bundle contains a primitive, otherwise none + This command will be run inside the container when launching it ("PID 1"). + If the bundle contains a primitive, this command must start pacemaker_remoted + (but could, for example, be a script that does other stuff, too).""", + + "options": """options:(string) + Extra command-line options to pass to docker run""" + }, + + "network": { + "ip-range-start": """ip-range-start:(IPv4 address) + If specified, Pacemaker will create an implicit ocf:heartbeat:IPaddr2 resource + for each container instance, starting with this IP address, using up to replicas + sequential addresses. These addresses can be used from the host’s network to + reach the service inside the container, though it is not visible within the + container itself. Only IPv4 addresses are currently supported.""", + + "host-netmask": """host-netmask:(integer) + Default:32 + If ip-range-start is specified, the IP addresses are created with this CIDR + netmask (as a number of bits).""", + + "host-interface": """host-interface:(string) + If ip-range-start is specified, the IP addresses are created on this host + interface (by default, it will be determined from the IP address).""", + + "control-port": """control-port:(integer) + Default: 3121 + If the bundle contains a primitive, the cluster will use this integer TCP port + for communication with Pacemaker Remote inside the container. Changing this is + useful when the container is unable to listen on the default port, for example, + when the container uses the host’s network rather than ip-range-start (in which + case replicas-per-host must be 1), or when the bundle may run on a Pacemaker + Remote node that is already listening on the default port. Any PCMK_remote_port + environment variable set on the host or in the container is ignored for bundle + connections.""", + + "port-mapping": { + "id": """id:(string) + A unique name for the port mapping (required)""", + + "port": """port:(integer) + If this is specified, connections to this TCP port number on the host network + (on the container’s assigned IP address, if ip-range-start is specified) will + be forwarded to the container network. Exactly one of port or range must be + specified in a port-mapping.""", + + "internal-port": """internal-port:(integer) + Default: value of port + If port and this are specified, connections to port on the host’s network will + be forwarded to this port on the container network.""", + + "range": """range:(first_port-last_port) + If this is specified, connections to these TCP port numbers (expressed as + first_port-last_port) on the host network (on the container’s assigned IP address, + if ip-range-start is specified) will be forwarded to the same ports in the container + network. Exactly one of port or range must be specified in a port-mapping.""" + } + }, + + "storage": { + "id": """id:(string) + A unique name for the storage mapping (required)""", + + "source-dir": """source-dir:(string) + The absolute path on the host’s filesystem that will be mapped into the container. + Exactly one of source-dir and source-dir-root must be specified in a storage-mapping.""", + + "source-dir-root": """source-dir-root:(string) + The start of a path on the host’s filesystem that will be mapped into the container, + using a different subdirectory on the host for each container instance. The subdirectory + will be named the same as the bundle host name, as described in the note for ip-range-start. + Exactly one of source-dir and source-dir-root must be specified in a storage-mapping.""", + + "target-dir": """target-dir:(string) + The path name within the container where the host storage will be mapped (required)""", + + "options": """options:(string) + File system mount options to use when mapping the storage""" + }, + + "rkt": { + "image": """image:(string) + Container image tag (required)""", + + "replicas": """replicas:(integer) + Default:Value of masters if that is positive, else 1 + A positive integer specifying the number of container instances to launch""", + + "replicas-per-host": """replicas-per-host:(interval) + Default:1 + A positive integer specifying the number of container instances allowed to + run on a single node""", + + "masters": """masters:(integer) + Default:0 + A non-negative integer that, if positive, indicates that the containerized + service should be treated as a multistate service, with this many replicas + allowed to run the service in the master role""", + + "run-command": """run-command:(string) + Default:/usr/sbin/pacemaker_remoted if bundle contains a primitive, otherwise none + This command will be run inside the container when launching it ("PID 1"). + If the bundle contains a primitive, this command must start pacemaker_remoted + (but could, for example, be a script that does other stuff, too).""", + + "options": """options:(string) + Extra command-line options to pass to rkt run""" + } +} + # vim:ts=4:sw=4:et: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/parse.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/parse.py --- old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/parse.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/parse.py 2017-12-13 16:37:15.000000000 +0100 @@ -615,6 +615,10 @@ oplist = olist([op for op in name_map if op.lower() in ('operations', 'op')]) for op in oplist: del name_map[op] + bundle_list = olist([op for op in name_map if op.lower() + in ('docker', 'rkt', 'network', 'port-mapping', 'storage', 'primitive')]) + for bl in bundle_list: + del name_map[bl] initial = True while self.has_tokens(): t = self.current_token().lower() @@ -622,7 +626,11 @@ initial = False if t in oplist: self.match_operations(out, t == 'operations') + if t in bundle_list: + self.match_container(out, t) else: + if bundle_list: + terminator = ['network', 'storage', 'primitive'] for attr_list in self.match_attr_lists(name_map, terminator=terminator): out.append(attr_list) elif initial: @@ -634,6 +642,35 @@ else: break + def match_container(self, out, _type): + container_node = None + self.match(_type) + all_attrs = self.match_nvpairs(minpairs=0, terminator=['network', 'storage', 'meta', 'primitive']) + + if _type != "primitive": + exist_node = out.find(_type) + if exist_node is None: + container_node = xmlutil.new(_type) + else: + container_node = exist_node + + child_flag = False + for nvp in all_attrs: + if nvp.get('name') in ['port-mapping', 'storage-mapping']: + inst_attrs = xmlutil.child(container_node, nvp.get('name')) + child_flag = True + continue + if child_flag: + inst_attrs.set(nvp.get('name'), nvp.get('value')) + else: + container_node.set(nvp.get('name'), nvp.get('value')) + out.append(container_node) + + else: + if len(all_attrs) != 1 or all_attrs[0].get('value'): + self.err("Expected primitive reference, got {}".format(", ".join("{}={}".format(nvp.get('name'), nvp.get('value') or "") for nvp in all_attrs))) + xmlutil.child(out, 'crmsh-ref', id=all_attrs[0].get('name')) + def match_op(self, out, pfx='op'): """ op <optype> [<n>=<v> ...] @@ -733,7 +770,7 @@ return out -@parser_for('primitive', 'group', 'clone', 'ms', 'master', 'rsc_template') +@parser_for('primitive', 'group', 'clone', 'ms', 'master', 'rsc_template', 'bundle') class ResourceParser(BaseParser): def match_ra_type(self, out): "[<class>:[<provider>:]]<type>" @@ -884,6 +921,19 @@ xmlutil.child(out, 'crmsh-ref', id=child) return out + def parse_bundle(self): + out = xmlutil.new('bundle') + out.set('id', self.match_identifier()) + xmlutil.maybe_set(out, 'description', self.try_match_description()) + self.match_arguments(out, {'docker': 'docker', + 'rkt': 'rkt', + 'network': 'network', + 'port-mapping': 'port-mapping', + 'storage': 'storage', + 'meta': 'meta_attributes', + 'primitive': 'primitive'}) + return out + @parser_for('location', 'colocation', 'collocation', 'order', 'rsc_ticket') class ConstraintParser(BaseParser): @@ -1474,11 +1524,35 @@ if desc is not None: out.attrib['description'] = desc rcount = 1 + root_selector = [None] + + def wrap_select(tag): + if tag[0] is None: + tag[0] = xmlutil.child(out, 'select') + return tag[0] + while self.has_tokens(): if self.current_token() in ('attributes', 'meta'): self.match_arguments(out, {'attributes': 'instance_attributes', 'meta': 'meta_attributes'}, - terminator=['attributes', 'meta', 'to']) + terminator=['attributes', 'meta', 'to', 'select']) + continue + if self.current_token() == 'select': + selector_types = ('nodes', 'fencing', 'resources', 'attributes') + self.match('select') + root_selector[0] = None + while self.current_token() in selector_types: + selector = self.match_identifier() + if selector == 'attributes': + if not self.try_match('{'): + self.rewind() + break + seltag = xmlutil.child(wrap_select(root_selector), 'select_{}'.format(selector)) + if selector == 'attributes': + while self.current_token() != '}': + name = self.match_identifier() + xmlutil.child(seltag, 'attribute', name=name) + self.match('}') continue self.match('to') rid = '%s-recipient-%s' % (alertid, rcount) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/ui_configure.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/ui_configure.py --- old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/ui_configure.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/ui_configure.py 2017-12-13 16:37:15.000000000 +0100 @@ -55,6 +55,7 @@ _top_rsc_id_list = compl.call(cib_factory.top_rsc_id_list) _node_id_list = compl.call(cib_factory.node_id_list) _rsc_template_list = compl.call(cib_factory.rsc_template_list) +_container_type = compl.choice(constants.container_type) def _advanced_completer(args): @@ -64,18 +65,19 @@ key_words = ["meta", "params"] completing = args[-1] resource_type = args[0] + return_list = [] if completing.endswith('='): # TODO add some help messages return [] keyw = last_keyword(args, key_words) if keyw and keyw == "meta": if resource_type == "group": - return [s+'=' for s in constants.group_meta_attributes] + key_words + return_list = utils.filter_keys(constants.group_meta_attributes, args) if resource_type == "clone": - return [s+'=' for s in constants.clone_meta_attributes] + key_words + return_list = utils.filter_keys(constants.clone_meta_attributes, args) if resource_type in ["ms", "master"]: - return [s+'=' for s in constants.ms_meta_attributes] + key_words - return key_words + return_list = utils.filter_keys(constants.ms_meta_attributes, args) + return return_list + key_words def _list_resource(args): @@ -200,8 +202,7 @@ return [] elif '=' in completing: return [] - return [s+'=' for s in agent.params(completion=True) - if utils.any_startswith(args, s+'=') is None] + return utils.filter_keys(agent.params(completion=True), args) def _prim_meta_completer(agent, args): @@ -210,8 +211,7 @@ return ['meta'] if '=' in completing: return [] - return [s+'=' for s in constants.rsc_meta_attributes - if utils.any_startswith(args, s+'=') is None] + return utils.filter_keys(constants.rsc_meta_attributes, args) def _prim_op_completer(agent, args): @@ -304,6 +304,149 @@ return completers_set[last_keyw](agent, args) + keywords +def container_helptxt(params, helptxt, topic): + for item in reversed(params): + if item in ["storage", "network", "docker", "rkt"]: + return helptxt[item][topic] + "\n" + if item == "port-mapping": + return helptxt["network"][item][topic] + "\n" + + +def _container_remove_exist_keywords(args, _keywords): + for item in ["network", "primitive"]: + if item in args: + _keywords.remove(item) + + +def _container_network_completer(args, _help, _keywords): + key_words = ["network", "port-mapping"] + completing = args[-1] + token = args[-2] + if completing.endswith("="): + return [] + if completing in key_words: + return [completing] + + tmp = list(_help["network"].keys()) + # port-mapping is element, not a network option + tmp.remove("port-mapping") + network_keys = utils.filter_keys(tmp, args) + # bundle contain just one <network>/<primitive> element + _container_remove_exist_keywords(args, _keywords) + + last_keyw = last_keyword(args, key_words) + if last_keyw == "network": + if token == "network": + return network_keys + else: + # complete port-mapping or other parts + return network_keys + ["port-mapping"] + _keywords + + if last_keyw == "port-mapping": + mapping_required = ["id"] + mapping_params = args[utils.rindex(args, "port-mapping"):] + mapping_keys = utils.filter_keys(_help["network"]["port-mapping"].keys(), mapping_params) + if token == "port-mapping": + return mapping_keys + # required options must be completed + for s in mapping_required: + if utils.any_startswith(mapping_params, s+'=') is None: + return mapping_keys + # complete port-mapping or other parts + return mapping_keys + ["port-mapping"] + _keywords + + +def _container_storage_completer(args, _help, _keywords): + completing = args[-1] + if completing.endswith("="): + return [] + if completing == "storage": + return [completing] + if args[-2] == "storage": + return ["storage-mapping"] + + storage_required = ["id", "target-dir"] + # get last storage part + mapping_params = args[utils.rindex(args, "storage-mapping"):] + storage_keys = utils.filter_keys(_help["storage"].keys(), mapping_params) + + # required options must be completed + for s in storage_required: + if utils.any_startswith(mapping_params, s+"=") is None: + return storage_keys + # bundle contain just one <network>/<primitive> element + _container_remove_exist_keywords(args, _keywords) + # complete storage or other parts + return storage_keys + _keywords + + +def _container_primitive_completer(args, _help, _keywords): + completing = args[-1] + if completing == "primitive": + return [completing] + + _id_list = cib_factory.f_prim_free_id_list() + if _id_list is None: + return [] + # bundle contain just one <network>/<primitive> element + _container_remove_exist_keywords(args, _keywords) + if args[-3] == "primitive" and args[-2] in _id_list: + return _keywords + return _id_list + + +def _container_meta_completer(args, helptxt, _keywords): + completing = args[-1] + if completing.endswith("="): + return [] + if completing == "meta": + return [completing] + + # bundle contain just one <network>/<primitive> element + _container_remove_exist_keywords(args, _keywords) + + return utils.filter_keys(constants.bundle_meta_attributes, args) + _keywords + + +def container_complete_complex(args): + ''' + Complete five parts: + container options, network, storage, primitive and meta + ''' + container_options_required = ["image"] + completing = args[-1] + container_type = args[2] + + completers_set = { + "network": _container_network_completer, + "storage": _container_storage_completer, + "primitive": _container_primitive_completer, + "meta": _container_meta_completer + } + keywords = list(completers_set.keys()) + last_keyw = last_keyword(args, keywords) + + # to show help messages + if completing.endswith('='): + if len(completing) > 1 and options.interactive: + topic = completing[:-1] + CompletionHelp.help(topic, container_helptxt(args, constants.container_helptxt, topic)) + return [] + + container_options = utils.filter_keys(constants.container_helptxt[container_type].keys(), args) + + # required options must be completed + for s in container_options_required: + if utils.any_startswith(args, s+'=') is None: + return container_options + + if last_keyw is None: + return container_options + keywords + + # to complete network, storage, primitive and meta + return completers_set[last_keyw](args, constants.container_helptxt, keywords) + + class CibConfig(command.UI): ''' The configuration class @@ -761,6 +904,14 @@ tmp.insert(idx+1, "role=%s" % item.split('_')[1]) return self.__conf_object(context.get_command_name(), *tuple(tmp)) + @command.completers_repeating(compl.attr_id, _container_type, container_complete_complex) + def do_bundle(self, context, *args): + """usage: bundle <bundle id> <container type> [<container option>...] + network [<network option>...] + storage [<storage option>...] + primitive <resource id> {[<class>:[<provider>:]]<type>|@<template>}""" + return self.__conf_object(context.get_command_name(), *args) + @command.skill_level('administrator') @command.completers_repeating(compl.attr_id, _f_prim_free_id_list, _advanced_completer) def do_group(self, context, *args): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/utils.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/utils.py --- old/crmsh-4.0.0+git.1512406036.adc26906/crmsh/utils.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/crmsh/utils.py 2017-12-13 16:37:15.000000000 +0100 @@ -42,6 +42,11 @@ return s +def filter_keys(key_list, args, sign="="): + """Return list item which not be completed yet""" + return [s+sign for s in key_list if any_startswith(args, s+sign) is None] + + def any_startswith(iterable, prefix): """Return first element in iterable which startswith prefix, or None.""" for element in iterable: @@ -50,6 +55,10 @@ return None +def rindex(iterable, value): + return len(iterable) - iterable[::-1].index(value) - 1 + + def memoize(function): "Decorator to invoke a function once only for any argument" memoized = {} @@ -1676,14 +1685,8 @@ if rc == 0: return [x for x in [getname(line.split()) for line in outp] if x and x != '(null)'] - CIB_DIR = config.path.crm_config - cib_file = r"%s/%s" % (CIB_DIR, "cib.xml") - if not os.path.isfile(cib_file): - raise ValueError("Error listing cluster nodes: cib.xml not exists") - from . import xmlutil node_list = [] - os.environ['CIB_file'] = cib_file cib = xmlutil.cibdump2elem() if cib is None: return None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/data-manifest new/crmsh-4.0.0+git.1513179435.e1d17d7b/data-manifest --- old/crmsh-4.0.0+git.1512406036.adc26906/data-manifest 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/data-manifest 2017-12-13 16:37:15.000000000 +0100 @@ -77,6 +77,8 @@ test/testcases/basicset test/testcases/bugs test/testcases/bugs.exp +test/testcases/bundle +test/testcases/bundle.exp test/testcases/commit test/testcases/commit.exp test/testcases/common.excl diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/doc/crm.8.adoc new/crmsh-4.0.0+git.1513179435.e1d17d7b/doc/crm.8.adoc --- old/crmsh-4.0.0+git.1512406036.adc26906/doc/crm.8.adoc 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/doc/crm.8.adoc 2017-12-13 16:37:15.000000000 +0100 @@ -2998,6 +2998,7 @@ alert <id> <path> \ [attributes <nvpair> ...] \ [meta <nvpair> ...] \ + [select [nodes | fencing | resources | attributes '{' <attribute> ... '}' ] ...] \ [to [{] <recipient> [attributes <nvpair> ...] \ [meta <nvpair> ...] [}] \ @@ -3012,6 +3013,42 @@ alert alert-2 /srv/pacemaker/example_alert.sh \ meta timeout=60s \ to { /var/log/cluster-alerts.log } + +alert alert-3 /srv/pacemaker/example_alert.sh \ + select fencing \ + to { /var/log/fencing-alerts.log } + +............... + +[[cmdhelp_configure_bundle,Container bundle]] +==== `bundle` + +A bundle is a single resource specifying the settings, networking +requirements, and storage requirements for any number of containers +generated from the same container image. + +Pacemaker bundles support Docker (since version 1.1.17) and rkt (since +version 1.1.18) container technologies. + +A bundle must contain exactly one +docker+ or +rkt+ element. + +The bundle definition may contain a reference to a primitive +resource which defining the resource running inside the +container. + +Example: +............... + +primitive httpd-apache ocf:heartbeat:apache + +bundle httpd \ + docker image=pcmk:httpd replicas=3 \ + network ip-range-start=10.10.10.123 host-netmask=24 \ + port-mapping port=80 \ + storage \ + storage-mapping target-dir=/var/www/html source-dir=/srv/www options=rw \ + primitive httpd-apache + ............... [[cmdhelp_configure_cib,CIB shadow management]] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/doc/website-v1/scripts.adoc new/crmsh-4.0.0+git.1513179435.e1d17d7b/doc/website-v1/scripts.adoc --- old/crmsh-4.0.0+git.1512406036.adc26906/doc/website-v1/scripts.adoc 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/doc/website-v1/scripts.adoc 2017-12-13 16:37:15.000000000 +0100 @@ -477,30 +477,30 @@ [source,python] ---- -#!/usr/bin/env python +#!/usr/bin/python3 import crm_script as crm try: uptime = open('/proc/uptime').read().split()[0] crm.exit_ok(uptime) -except: - crm.exit_fail("Couldn't open /proc/uptime") +except Exception as e: + crm.exit_fail("Couldn't open /proc/uptime: %s" % (e)) ---- `report.py`: [source,python] ---- -#!/usr/bin/env python +#!/usr/bin/python3 import crm_script as crm show_all = crm.is_true(crm.param('show_all')) -uptimes = crm.output(1).items() -max_uptime = 0, '' +uptimes = list(crm.output(1).items()) +max_uptime = '', 0 for host, uptime in uptimes: - if uptime > max_uptime[0]: - max_uptime = uptime, host + if float(uptime) > max_uptime[1]: + max_uptime = host, float(uptime) if show_all: - print "Uptimes: %s" % (', '.join("%s: %s" % v for v in uptimes)) -print "Longest uptime is %s seconds on host %s" % max_uptime + print("Uptimes: %s" % (', '.join("%s: %s" % v for v in uptimes))) +print("Longest uptime is %s seconds on host %s" % (max_uptime[1], max_uptime[0])) ---- See below for more details on the helper library `crm_script`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/hb_report/utillib.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/hb_report/utillib.py --- old/crmsh-4.0.0+git.1512406036.adc26906/hb_report/utillib.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/hb_report/utillib.py 2017-12-13 16:37:15.000000000 +0100 @@ -1353,14 +1353,14 @@ if not FROM_LINE: log_warning("couldn't find line for time %d; corrupt log file?" % from_time) - return + return "" TO_LINE = "" if to_time != 0: TO_LINE = findln_by_time(sourcef, to_time) if not TO_LINE: log_warning("couldn't find line for time %d; corrupt log file?" % to_time) - return + return "" log_debug("including segment [%s-%s] from %s" % (FROM_LINE, TO_LINE, sourcef)) return dump_log(sourcef, FROM_LINE, TO_LINE) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/scripts/check-uptime/fetch.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/check-uptime/fetch.py --- old/crmsh-4.0.0+git.1512406036.adc26906/scripts/check-uptime/fetch.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/check-uptime/fetch.py 2017-12-13 16:37:15.000000000 +0100 @@ -1,5 +1,4 @@ -#!/usr/bin/env python -from __future__ import unicode_literals +#!/usr/bin/python3 import crm_script try: uptime = open('/proc/uptime').read().split()[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/scripts/check-uptime/report.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/check-uptime/report.py --- old/crmsh-4.0.0+git.1512406036.adc26906/scripts/check-uptime/report.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/check-uptime/report.py 2017-12-13 16:37:15.000000000 +0100 @@ -1,13 +1,11 @@ -#!/usr/bin/env python -from __future__ import print_function -from __future__ import unicode_literals +#!/usr/bin/python3 import crm_script show_all = crm_script.is_true(crm_script.param('show_all')) uptimes = list(crm_script.output(1).items()) -max_uptime = '', 0 +max_uptime = '', 0.0 for host, uptime in uptimes: - if uptime > max_uptime[1]: - max_uptime = host, uptime + if float(uptime) > max_uptime[1]: + max_uptime = host, float(uptime) if show_all: print("Uptimes: %s" % (', '.join("%s: %s" % v for v in uptimes))) print("Longest uptime is %s seconds on host %s" % (max_uptime[1], max_uptime[0])) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/scripts/health/collect.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/health/collect.py --- old/crmsh-4.0.0+git.1512406036.adc26906/scripts/health/collect.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/health/collect.py 2017-12-13 16:37:15.000000000 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python3 from __future__ import unicode_literals from builtins import str import os @@ -13,21 +13,25 @@ 'haproxy', 'hawk', 'libdlm', 'libqb', 'ocfs2', 'ocfs2-tools', 'pacemaker', 'pacemaker-mgmt', 'resource-agents', 'sbd'] + def rpm_info(): return crm_script.rpmcheck(PACKAGES) + def logrotate_info(): return {} + def get_user(): return pwd.getpwuid(os.getuid()).pw_name + def sys_info(): sysname, nodename, release, version, machine = os.uname() - #The first three columns measure CPU and IO utilization of the - #last one, five, and 15 minute periods. The fourth column shows - #the number of currently running processes and the total number of - #processes. The last column displays the last process ID used. + # The first three columns measure CPU and IO utilization of the + # last one, five, and 15 minute periods. The fourth column shows + # the number of currently running processes and the total number of + # processes. The last column displays the last process ID used. system, node, release, version, machine, processor = platform.uname() distname, distver, distid = platform.linux_distribution() hostname = os.uname()[1] @@ -51,6 +55,7 @@ 'loadavg': loadavg[2] # 15 minute average } + def disk_info(): rc, out, err = crm_script.call(['df'], shell=False) if rc == 0: @@ -64,6 +69,7 @@ return disk_use return [] + # configurations out of sync FILES = [ @@ -81,7 +87,7 @@ for f in FILES: if os.path.isfile(f): try: - ret[f] = hashlib.sha1(open(f).read()).hexdigest() + ret[f] = hashlib.sha1(open(f).read().encode('utf-8')).hexdigest() except IOError as e: ret[f] = "error: %s" % (e) else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/scripts/health/hahealth.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/health/hahealth.py --- old/crmsh-4.0.0+git.1512406036.adc26906/scripts/health/hahealth.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/health/hahealth.py 2017-12-13 16:37:15.000000000 +0100 @@ -1,16 +1,18 @@ -#!/usr/bin/env python -from __future__ import unicode_literals +#!/usr/bin/python3 import os import crm_script as crm -if not os.path.isfile('/usr/sbin/crm'): + +if not os.path.isfile('/usr/sbin/crm') and not os.path.isfile('/usr/bin/crm'): # crm not installed crm.exit_ok({'status': 'crm not installed'}) + def get_from_date(): rc, out, err = crm.call("date '+%F %H:%M' --date='1 day ago'", shell=True) return out.strip() + def create_report(): cmd = ['crm', 'report', '-f', get_from_date(), @@ -18,13 +20,16 @@ rc, out, err = crm.call(cmd, shell=False) return rc == 0 + if not create_report(): crm.exit_ok({'status': 'Failed to create report'}) + def extract_report(): rc, out, err = crm.call(['tar', 'xjf', 'health-report.tar.bz2'], shell=False) return rc == 0 + if not extract_report(): crm.exit_ok({'status': 'Failed to extract report'}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/scripts/health/report.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/health/report.py --- old/crmsh-4.0.0+git.1512406036.adc26906/scripts/health/report.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/scripts/health/report.py 2017-12-13 16:37:15.000000000 +0100 @@ -1,6 +1,5 @@ -#!/usr/bin/env python -from __future__ import print_function -from __future__ import unicode_literals +#!/usr/bin/python3 +import os import crm_script data = crm_script.get_input() health_report = data[1] @@ -12,12 +11,15 @@ warnings = [] errors = [] + def warn(fmt, *args): warnings.append(fmt % args) + def error(fmt, *args): errors.append(fmt % args) + # sort {package: {version: [host]}} rpm_versions = {} @@ -89,7 +91,8 @@ check('release', 'Kernel release differs') check('distname', 'Distribution differs') check('distver', 'Distribution version differs') - #check('version', 'Kernel version differs') + # check('version', 'Kernel version differs') + def compare_files(systems): keys = set() @@ -101,6 +104,7 @@ info = ', '.join('%s: %s' % (h, files.get(filename)) for h, files in systems) warn("%s: %s" % ("Files differ", info)) + compare_system((h, info['system']) for h, info in health_report.items()) compare_files((h, info['files']) for h, info in health_report.items()) @@ -126,6 +130,5 @@ if not errors and not warnings: print("No issues found.") -import os workdir = os.path.dirname(crm_script.__file__) print("\nINFO: health-report in directory \"%s\"" % workdir) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/test/list-undocumented-commands.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/list-undocumented-commands.py --- old/crmsh-4.0.0+git.1512406036.adc26906/test/list-undocumented-commands.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/list-undocumented-commands.py 2017-12-13 16:37:15.000000000 +0100 @@ -1,9 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/python3 # # Script to discover and report undocumented commands. -from __future__ import print_function -from __future__ import unicode_literals from crmsh.ui_root import Root from crmsh import help diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/test/testcases/basicset new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/testcases/basicset --- old/crmsh-4.0.0+git.1512406036.adc26906/test/testcases/basicset 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/testcases/basicset 2017-12-13 16:37:15.000000000 +0100 @@ -1,4 +1,5 @@ confbasic +bundle confbasic-xml edit rset diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/test/testcases/bundle new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/testcases/bundle --- old/crmsh-4.0.0+git.1512406036.adc26906/test/testcases/bundle 1970-01-01 01:00:00.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/testcases/bundle 2017-12-13 16:37:15.000000000 +0100 @@ -0,0 +1,20 @@ +show Basic configure +node node1 +delete node1 +node node1 \ + attributes mem=16G +node node2 utilization cpu=4 +primitive st stonith:ssh \ + params hostlist='node1 node2' \ + meta target-role="Started" \ + op start requires=nothing timeout=60s \ + op monitor interval=60m timeout=60s +primitive st2 stonith:ssh \ + params hostlist='node1 node2' +bundle id=bundle-test1 docker image=test network ip-range-start=10.10.10.123 port-mapping id=port1 port=80 storage storage-mapping id=storage1 target-dir=test source-dir=test meta target-role=Stopped +primitive id=dummy ocf:heartbeat:Dummy op monitor interval=10 meta target-role=Stopped +bundle id=bundle-test2 docker image=test network ip-range-start=10.10.10.123 primitive dummy meta target-role=Stopped priority=1 +property stonith-enabled=true +_test +verify +. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/test/testcases/bundle.exp new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/testcases/bundle.exp --- old/crmsh-4.0.0+git.1512406036.adc26906/test/testcases/bundle.exp 1970-01-01 01:00:00.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/testcases/bundle.exp 2017-12-13 16:37:15.000000000 +0100 @@ -0,0 +1,52 @@ +.TRY Basic configure +.INP: configure +.INP: _regtest on +.INP: erase +.INP: erase nodes +.INP: node node1 +.INP: delete node1 +.INP: node node1 attributes mem=16G +.INP: node node2 utilization cpu=4 +.INP: primitive st stonith:ssh params hostlist='node1 node2' meta target-role="Started" op start requires=nothing timeout=60s op monitor interval=60m timeout=60s +.INP: primitive st2 stonith:ssh params hostlist='node1 node2' +.INP: bundle id=bundle-test1 docker image=test network ip-range-start=10.10.10.123 port-mapping id=port1 port=80 storage storage-mapping id=storage1 target-dir=test source-dir=test meta target-role=Stopped +.INP: primitive id=dummy ocf:heartbeat:Dummy op monitor interval=10 meta target-role=Stopped +.INP: bundle id=bundle-test2 docker image=test network ip-range-start=10.10.10.123 primitive dummy meta target-role=Stopped priority=1 +.INP: property stonith-enabled=true +.INP: _test +ERROR: 15: object bundle-test2 does not reference its child dummy +.INP: verify +.EXT crm_resource --show-metadata stonith:ssh +.EXT stonithd metadata +.EXT crm_resource --show-metadata ocf:heartbeat:Dummy +.EXT crmd metadata +.EXT pengine metadata +.EXT cib metadata +.INP: show +node node1 \ + attributes mem=16G +node node2 \ + utilization cpu=4 +primitive dummy Dummy \ + op monitor interval=10 \ + meta target-role=Stopped +primitive st stonith:ssh \ + params hostlist="node1 node2" \ + meta target-role=Started \ + op start requires=nothing timeout=60s interval=0 \ + op monitor interval=60m timeout=60s +primitive st2 stonith:ssh \ + params hostlist="node1 node2" +property cib-bootstrap-options: \ + stonith-enabled=true +bundle bundle-test1 \ + docker image=test \ + network ip-range-start=10.10.10.123 port-mapping id=port1 port=80 \ + storage storage-mapping id=storage1 target-dir=test source-dir=test \ + meta target-role=Stopped +bundle bundle-test2 \ + docker image=test \ + network ip-range-start=10.10.10.123 \ + primitive dummy \ + meta target-role=Stopped priority=1 +.INP: commit diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/test/unittests/test_cliformat.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/unittests/test_cliformat.py --- old/crmsh-4.0.0+git.1512406036.adc26906/test/unittests/test_cliformat.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/unittests/test_cliformat.py 2017-12-13 16:37:15.000000000 +0100 @@ -330,3 +330,11 @@ @with_setup(setup_func, teardown_func) def test_alerts_5(): roundtrip('alert alert5 "/a/path" to { "/another/path" } meta timeout=30s') + +@with_setup(setup_func, teardown_func) +def test_alerts_6(): + roundtrip('alert alert6 "/a/path" select fencing attributes { standby } to { "/another/path" } meta timeout=30s') + +@with_setup(setup_func, teardown_func) +def test_alerts_7(): + roundtrip('alert alert7 "/a/path" select fencing attributes foo=bar to { "/another/path" } meta timeout=30s') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/test/unittests/test_parse.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/unittests/test_parse.py --- old/crmsh-4.0.0+git.1512406036.adc26906/test/unittests/test_parse.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/test/unittests/test_parse.py 2017-12-13 16:37:15.000000000 +0100 @@ -347,6 +347,15 @@ 'rsc_ticket ticket-B_storage ticket-B: drbd-a:Master drbd-b:Master') self.assertEqual(out.get('id'), 'ticket-B_storage') + def test_bundle(self): + out = self._parse('bundle httpd docker image=pcmk:httpd replicas=3 network ip-range-start=10.10.10.123 host-netmask=24 port-mapping port=80 storage storage-mapping target-dir=/var/www/html source-dir=/srv/www options=rw primitive httpd-apache') + self.assertEqual(out.get('id'), 'httpd') + self.assertEqual(['pcmk:httpd'], out.xpath('/bundle/docker/@image')) + self.assertEqual(['httpd-apache'], out.xpath('/bundle/crmsh-ref/@id')) + + out = self._parse('bundle httpd docker image=pcmk:httpd primitive httpd-apache apache') + self.assertFalse(out) + def test_op(self): out = self._parse('monitor apache:Master 10s:20s') self.assertEqual(out.get('rsc'), 'apache') @@ -502,6 +511,16 @@ self.assertEqual(['10s'], out.xpath('/alert/recipient/meta_attributes/nvpair[@name="timeout"]/@value')) + def test_alerts_selectors(self): + "Test alerts w/ selectors (1.1.17+)" + out = self._parse('alert alert3 /tmp/foo.sh select nodes fencing attributes { standby shutdown } to { /tmp/bar.log meta timeout=10s }') + self.assertEqual(out.get('id'), 'alert3') + self.assertEqual(1, len(out.xpath('/alert/select/select_nodes'))) + self.assertEqual(1, len(out.xpath('/alert/select/select_fencing'))) + self.assertEqual(['standby', 'shutdown'], + out.xpath('/alert/select/select_attributes/attribute/@name')) + + def _parse_lines(self, lines): out = [] for line in lines2cli(lines): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.0.0+git.1512406036.adc26906/utils/crm_script.py new/crmsh-4.0.0+git.1513179435.e1d17d7b/utils/crm_script.py --- old/crmsh-4.0.0+git.1512406036.adc26906/utils/crm_script.py 2017-12-04 17:47:16.000000000 +0100 +++ new/crmsh-4.0.0+git.1513179435.e1d17d7b/utils/crm_script.py 2017-12-13 16:37:15.000000000 +0100 @@ -96,7 +96,7 @@ debug("crm_script(call): %s" % (cmd)) p = proc.Popen(cmd, shell=shell, stdin=None, stdout=proc.PIPE, stderr=proc.PIPE) out, err = p.communicate() - return p.returncode, out.strip(), err.strip() + return p.returncode, out.decode('utf-8').strip(), err.decode('utf-8').strip() def use_sudo():
