def ask(msg): """ Ask for user confirmation. If core.force is true, always return true. If not interactive and core.force is false, always return false. """ if config.core.force: common_info("%s [YES]" % (msg)) return True if not can_ask(): return False msg += ' ' if msg.endswith('? '): msg = msg[:-2] + ' (y/n)? ' while True: try: ans = raw_input(msg) except EOFError: ans = 'n' if ans: ans = ans[0].lower() if ans in 'yn': return ans == 'y'
def _pe2shadow(self, f, argl): try: name = argl[0] except: name = os.path.basename(f).replace(".bz2", "") common_info("transition %s saved to shadow %s" % (f, name)) return xmlutil.pe2shadow(f, name)
def save(self, dest=None): ''' Save the modified status section to a file/shadow. If the file exists, then it must be a cib file and the status section is replaced with our status section. If the file doesn't exist, then our section and some (?) configuration is saved. ''' if not self.modified: common_info("apparently you didn't modify status") return False if (not dest and self.origin == "live") or dest == "live": common_warn("cannot save status to the cluster") return False cib = self.cib if dest: dest_path = cib_path(dest) if os.path.isfile(dest_path): cib = self._load_cib(dest) if cib is None: common_err("%s exists, but no cib inside" % dest) return False else: dest_path = cib_path(self.origin) if cib != self.cib: status = cib.find("status") xmlutil.rmnode(status) cib.append(self.status_node) xml = etree.tostring(cib) try: f = open(dest_path, "w") except IOError, msg: common_err(msg) return False
def do_transition(self, context, *args): """usage: transition [<number>|<index>|<file>] [nograph] [v...] [scores] [actions] [utilization] transition showdot [<number>|<index>|<file>] transition log [<number>|<index>|<file>] transition save [<number>|<index>|<file> [name]]""" self._init_source() argl = list(args) subcmd = "show" if argl and argl[0] in ("showdot", "log", "save"): subcmd = argl[0] del argl[0] if subcmd == "show": opt_l = utils.fetch_opts(argl, ptest_options) if argl: f = self._get_pe_input(argl[0]) del argl[0] else: f = self._get_pe_byidx(-1) if (subcmd == "save" and len(argl) > 1) or \ (subcmd in ("show", "showdot", "log") and argl): syntax_err(args, context="transition") return False if not f: return False if subcmd == "show": common_info("running ptest with %s" % f) rc = self._show_pe(f, opt_l) elif subcmd == "showdot": rc = self._display_dot(f) elif subcmd == "save": rc = self._pe2shadow(f, argl) else: rc = crm_report.show_transition_log(f, True) return rc
def do_trace(self, context, rsc_id, op, 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: head_pl = ["op", []] head_pl[1].append(["name", op]) head_pl[1].append(["interval", interval]) head_pl[1].append([vars.trace_ra_attr, "1"]) cli_list = [] cli_list.append(head_pl) if not rsc.add_operation(cli_list): return False else: op_node = rsc.set_op_attr(op_node, vars.trace_ra_attr, "1") 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) else: common_info("restart %s to get the trace" % rsc_id) return True
def do_setnodes(self, context, *args): "usage: setnodes <node> [<node> ...]" if options.history != "live": common_info( "setting nodes not necessary for existing reports, proceeding anyway" ) return crm_report.set_nodes(*args)
def do_transition(self, context, *args): """usage: transition [<number>|<index>|<file>] [nograph] [v...] [scores] [actions] [utilization] transition showdot [<number>|<index>|<file>] transition log [<number>|<index>|<file>] transition save [<number>|<index>|<file> [name]]""" self._init_source() argl = list(args) subcmd = "show" if argl and argl[0] in ("showdot", "log", "save"): subcmd = argl[0] del argl[0] if subcmd == "show": opt_l = utils.fetch_opts(argl, ptest_options) if argl: f = self._get_pe_input(argl[0]) del argl[0] else: f = self._get_pe_byidx(-1) if (subcmd == "save" and len(argl) > 1) or \ (subcmd in ("show", "showdot", "log") and argl): syntax_err(args, context="transition") return False if not f: return False if subcmd == "show": common_info("running ptest with %s" % f) rc = self._show_pe(f, opt_l) elif subcmd == "showdot": rc = self._display_dot(f) elif subcmd == "save": rc = self._pe2shadow(f, argl) else: rc = crm_report().show_transition_log(f, True) return rc
def _call_delnode(self, node): "Remove node (how depends on cluster stack)" rc = True if utils.cluster_stack() == "heartbeat": cmd = (self.hb_delnode % node) else: ec, s = utils.get_stdout("%s -p" % self.crm_node) if not s: common_err('%s -p could not list any nodes (rc=%d)' % (self.crm_node, ec)) rc = False else: partition_l = s.split() if node in partition_l: common_err("according to %s, node %s is still active" % (self.crm_node, node)) rc = False cmd = "%s --force -R %s" % (self.crm_node, node) if not rc: if config.core.force: common_info('proceeding with node %s removal' % node) else: return False ec = utils.ext_cmd(cmd) if ec != 0: common_warn('"%s" failed, rc=%d' % (cmd, ec)) return False return True
def show_dot_graph(dotfile, keep_file=False, desc="transition graph"): cmd = "%s %s" % (config.core.dotty, dotfile) if not keep_file: cmd = "(%s; rm -f %s)" % (cmd, dotfile) if options.regression_tests: print ".EXT", cmd subprocess.Popen(cmd, shell=True, bufsize=0, stdin=None, stdout=None, stderr=None, close_fds=True) common_info("starting %s to show %s" % (config.core.dotty, desc))
def do_restart(self, context, rsc): "usage: restart <rsc>" common_info("ordering %s to stop" % rsc) if not self._commit_meta_attr(context, rsc, "target-role", "Stopped"): return False if not utils.wait4dc("stop", not options.batch): return False common_info("ordering %s to start" % rsc) return self._commit_meta_attr(context, rsc, "target-role", "Started")
def do_refresh(self, context, force=''): "usage: refresh" if options.history != "live": common_info("nothing to refresh if source isn't live") return False if force: if force != "force" and force != "--force": context.fatal_error("Expected 'force' or '--force' (was '%s')" % (force)) force = True return crm_report.refresh_source(force)
def do_restart(self, context, rsc): "usage: restart <rsc>" if not utils.is_name_sane(rsc): return False common_info("ordering %s to stop" % rsc) if not self.do_stop(context, rsc): return False if not utils.wait4dc("stop", not options.batch): return False common_info("ordering %s to start" % rsc) return self.do_start(context, rsc)
def do_refresh(self, context, force=''): "usage: refresh" if options.history != "live": common_info("nothing to refresh if source isn't live") return False if force: if force != "force" and force != "--force": context.fatal_error( "Expected 'force' or '--force' (was '%s')" % (force)) force = True return crm_report.refresh_source(force)
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 op == "probe": op = "monitor" if op is None: self._trace_resource(context, rsc_id, rsc) elif interval is None: self._trace_op(context, rsc_id, rsc, op) else: self._trace_op_interval(context, rsc_id, rsc, op, interval) if not cib_factory.commit(): return False 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("Trace set, restart %s to trace non-monitor operations" % (rsc_id)) return True
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 op == "probe": op = "monitor" if interval is None: interval = "0" if op is None: self._trace_resource(context, rsc_id, rsc) elif interval is None: self._trace_op(context, rsc_id, rsc, op) else: self._trace_op_interval(context, rsc_id, rsc, op, interval) if not cib_factory.commit(): return False 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("Trace set, restart %s to trace non-monitor operations" % (rsc_id)) return True
def do_delete(self, context, node): 'usage: delete <node>' if not utils.is_name_sane(node): return False if not xmlutil.is_our_node(node): common_err("node %s not found in the CIB" % node) return False if not self._call_delnode(node): return False if utils.ext_cmd(self.node_delete % node) != 0 or \ utils.ext_cmd(self.node_delete_status % node) != 0: common_err("%s removed from membership, but not from CIB!" % node) return False common_info("node %s deleted" % node) return True
def check_locker(dir): if not os.path.isdir(os.path.join(dir, _LOCKDIR)): return s = file2str(os.path.join(dir, _LOCKDIR, _PIDF)) pid = convert2ints(s) if not isinstance(pid, int): common_warn("history: removing malformed lock") rmdir_r(os.path.join(dir, _LOCKDIR)) return try: os.kill(pid, 0) except OSError, (errno, strerror): if errno == os.errno.ESRCH: common_info("history: removing stale lock") rmdir_r(os.path.join(dir, _LOCKDIR)) else: common_err("%s: %s" % (_LOCKDIR, strerror))
def run_ptest(graph_s, nograph, scores, utilization, actions, verbosity): ''' Pipe graph_s thru ptest(8). Show graph using dotty if requested. ''' actions_filter = "grep LogActions: | grep -vw Leave" ptest = "2>&1 %s -x -" % config.core.ptest if re.search("simulate", ptest) and \ not re.search("-[RS]", ptest): ptest = "%s -S" % ptest if verbosity: if actions: verbosity = 'v' * max(3, len(verbosity)) ptest = "%s -%s" % (ptest, verbosity.upper()) if scores: ptest = "%s -s" % ptest if utilization: ptest = "%s -U" % ptest if config.core.dotty and not nograph: fd, dotfile = mkstemp() ptest = "%s -D %s" % (ptest, dotfile) else: dotfile = None # ptest prints to stderr if actions: ptest = "%s | %s" % (ptest, actions_filter) if options.regression_tests: ptest = ">/dev/null %s" % ptest common_debug("invoke: %s" % ptest) rc, s = get_stdout(ptest, input_s=graph_s) if rc != 0: common_debug("%s exited with %d" % (ptest, rc)) if actions and rc == 1: common_warn("No actions found.") else: common_warn("Simulation was unsuccessful (RC=%d)." % (rc)) if dotfile: if os.path.getsize(dotfile) > 0: show_dot_graph(dotfile) else: common_warn("ptest produced empty dot file") else: if not nograph: common_info("install graphviz to see a transition graph") if s: page_string(s) return True
def _xdg_file(name, xdg_name, chk_fun, directory): from msg import common_warn, common_info, common_debug if not name: return name if not os.path.isdir(directory): os.makedirs(directory, 0700) new = os.path.join(directory, xdg_name) if directory == CONFIG_HOME and chk_fun(new) and chk_fun(name): common_warn("both %s and %s exist, please cleanup" % (name, new)) return name if chk_fun(name): if directory == CONFIG_HOME: common_info("moving %s to %s" % (name, new)) else: common_debug("moving %s to %s" % (name, new)) os.rename(name, new) return new
def edit_node(self, node, state): ''' Modify crmd, expected, and join attributes of node_state to set the node's state to online, offline, or unclean. ''' if self.get_status() is None: return False if not state in self.node_ops: common_err("unknown state %s" % state) return False node_node = get_tag_by_id(self.status_node, "node_state", node) if node_node is None: common_info("node %s created" % node) return False rc = self.inject("%s %s" % (self.node_ops[state], node)) if rc != 0: return False self._load(self.origin) self.node_changes[node] = state self.modified = True return True
def do_session(self, context, subcmd=None, name=None): "usage: session [{save|load|delete} <name> | pack [<name>] | update | list]" self._init_source() if not subcmd: print "current session: %s" % self.current_session return True # verify arguments if subcmd not in ("save", "load", "pack", "delete", "list", "update"): common_err("unknown history session subcmd: %s" % subcmd) return False if name: if subcmd not in ("save", "load", "pack", "delete"): syntax_err(subcmd, context='session') return False if not utils.is_filename_sane(name): return False elif subcmd not in ("list", "update", "pack"): syntax_err(subcmd, context='session') return False elif subcmd in ("update", "pack") and not self.current_session: common_err("need to load a history session before update/pack") return False # do work if not name: # some commands work on the existing session name = self.current_session rc = crm_report().manage_session(subcmd, name) # set source appropriately if rc and subcmd in ("save", "load"): options.history = crm_report().get_source() crm_report().prepare_source() self.current_session = name elif rc and subcmd == "delete": if name == self.current_session: common_info( "current history session deleted, setting source to live") self._set_source("live") return rc
def do_session(self, context, subcmd=None, name=None): "usage: session [{save|load|delete} <name> | pack [<name>] | update | list]" self._init_source() if not subcmd: print "current session: %s" % self.current_session return True # verify arguments if subcmd not in ("save", "load", "pack", "delete", "list", "update"): common_err("unknown history session subcmd: %s" % subcmd) return False if name: if subcmd not in ("save", "load", "pack", "delete"): syntax_err(subcmd, context='session') return False if not utils.is_filename_sane(name): return False elif subcmd not in ("list", "update", "pack"): syntax_err(subcmd, context='session') return False elif subcmd in ("update", "pack") and not self.current_session: common_err("need to load a history session before update/pack") return False # do work if not name: # some commands work on the existing session name = self.current_session rc = crm_report.manage_session(subcmd, name) # set source appropriately if rc and subcmd in ("save", "load"): options.history = crm_report.get_source() crm_report.prepare_source() self.current_session = name elif rc and subcmd == "delete": if name == self.current_session: common_info("current history session deleted, setting source to live") self._set_source("live") return rc
def filter_string(cmd, s, stderr_on=True): rc = -1 # command failed outp = '' if stderr_on: stderr = None else: stderr = subprocess.PIPE cmd = add_sudo(cmd) common_debug("pipe through %s" % cmd) if options.regression_tests: print ".EXT", cmd p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr) try: outp = p.communicate(s)[0] p.wait() rc = p.returncode except OSError, (errno, strerror): if errno != os.errno.EPIPE: common_err(strerror) common_info("from: %s" % cmd)
def _commit(self, force=None): if force and force != "force": syntax_err(('configure.commit', force)) return False if not cib_factory.has_cib_changed(): common_info("apparently there is nothing to commit") common_info("try changing something first") return True rc1 = True if not (force or utils.cibadmin_can_patch()): rc1 = cib_factory.is_current_cib_equal() rc2 = cib_factory.has_no_primitives() or \ self._verify(mkset_obj("xml", "changed"), mkset_obj("xml")) if rc1 and rc2: return cib_factory.commit() if force or config.core.force: common_info("commit forced") return cib_factory.commit(force=True) if utils.ask("Do you still want to commit?"): return cib_factory.commit(force=True) return False
def _commit(self, force=False, replace=False): if force: syntax_err(('configure.commit', force)) return False if not cib_factory.has_cib_changed(): common_info("apparently there is nothing to commit") common_info("try changing something first") return True replace = replace or not utils.cibadmin_can_patch() rc1 = True if replace and not force: rc1 = cib_factory.is_current_cib_equal() rc2 = cib_factory.has_no_primitives() or \ self._verify(mkset_obj("xml", "changed"), mkset_obj("xml")) if rc1 and rc2: return cib_factory.commit(replace=replace) if force or config.core.force: common_info("commit forced") return cib_factory.commit(force=True, replace=replace) if utils.ask("Do you still want to commit?"): return cib_factory.commit(force=True, replace=replace) return False
def info(self, s): common_info("%s: %s" % (self.ra_string(), s))
def info(self, s): common_info("%s: %s" % (self.id_str(), s))
common_err("open: %s" % msg) return False print >>f, self.generate() f.close() return True def load_template(self, tmpl): try: f = open(os.path.join(config.path.sharedir, 'templates', tmpl)) except IOError, msg: common_err("open: %s" % msg) return '' l = (''.join(f)).split('\n') if not validate_template(l): return '' common_info("pulling in template %s" % tmpl) g = l.index('%generate') pre_gen = l[0:g] post_gen = l[g+1:] name = get_var(pre_gen, '%name') for s in l[0:g]: if s.startswith('%depends_on'): a = s.split() if len(a) != 2: common_warn("%s: wrong usage" % s) continue tmpl_id = a[1] tmpl_pfx = self.load_template(a[1]) if tmpl_pfx: fix_tmpl_refs(post_gen, '%'+tmpl_id, '%'+tmpl_pfx) pfx = self.new_pfx(name)
def info(self, msg): common_info("%s: %s" % (self.get_qualified_name(), msg))
p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr) try: outp = p.communicate(s)[0] p.wait() rc = p.returncode except OSError, (errno, strerror): if errno != os.errno.EPIPE: common_err(strerror) common_info("from: %s" % cmd) except Exception, msg: common_err(msg) common_info("from: %s" % cmd) return rc, outp def str2tmp(s, suffix=".pcmk"): ''' Write the given string to a temporary file. Return the name of the file. ''' fd, tmp = mkstemp(suffix=suffix) try: f = os.fdopen(fd, "w") except IOError, msg: common_err(msg) return f.write(s)
def wait4dc(what="", show_progress=True): ''' Wait for the DC to get into the S_IDLE state. This should be invoked only after a CIB modification which would exercise the PE. Parameter "what" is whatever the caller wants to be printed if showing progress. It is assumed that the DC is already in a different state, usually it should be either PENGINE or TRANSITION. This assumption may not be true, but there's a high chance that it is since crmd should be faster to move through states than this shell. Further, it may also be that crmd already calculated the new graph, did transition, and went back to the idle state. This may in particular be the case if the transition turned out to be empty. Tricky. Though in practice it shouldn't be an issue. There's no timeout, as we expect the DC to eventually becomes idle. ''' dc = get_dc() if not dc: common_warn("can't find DC") return False cmd = "crm_attribute -Gq -t crm_config -n crmd-transition-delay 2> /dev/null" delay = get_stdout(add_sudo(cmd))[1] if delay: delaymsec = crm_msec(delay) if 0 < delaymsec: common_info( "The crmd-transition-delay is configured. Waiting %d msec before check DC status." % delaymsec) time.sleep(delaymsec / 1000) cmd = "crmadmin -S %s" % dc cnt = 0 output_started = 0 init_sleep = 0.25 max_sleep = 1.00 sleep_time = init_sleep while True: rc, s = get_stdout(add_sudo(cmd)) if not s.startswith("Status"): common_warn("%s unexpected output: %s (exit code: %d)" % (cmd, s, rc)) return False try: dc_status = s.split()[-2] except: common_warn("%s unexpected output: %s" % (cmd, s)) return False if dc_status == "S_IDLE": if output_started: sys.stderr.write(" done\n") return True time.sleep(sleep_time) if sleep_time < max_sleep: sleep_time *= 2 if show_progress: if not output_started: output_started = 1 sys.stderr.write("waiting for %s to finish ." % what) cnt += 1 if cnt % 5 == 0: sys.stderr.write(".")
common_err("open: %s" % msg) return False print >> f, self.generate() f.close() return True def load_template(self, tmpl): try: l = open(os.path.join(config.path.sharedir, 'templates', tmpl)).read().split('\n') except IOError, msg: common_err("open: %s" % msg) return '' if not validate_template(l): return '' common_info("pulling in template %s" % tmpl) g = l.index('%generate') pre_gen = l[0:g] post_gen = l[g + 1:] name = get_var(pre_gen, '%name') for s in l[0:g]: if s.startswith('%depends_on'): a = s.split() if len(a) != 2: common_warn("%s: wrong usage" % s) continue tmpl_id = a[1] tmpl_pfx = self.load_template(a[1]) if tmpl_pfx: fix_tmpl_refs(post_gen, '%' + tmpl_id, '%' + tmpl_pfx) pfx = self.new_pfx(name)
def do_setnodes(self, context, *args): "usage: setnodes <node> [<node> ...]" self._init_source() if options.history != "live": common_info("setting nodes not necessary for existing reports, proceeding anyway") return crm_report.set_nodes(*args)
def wait4dc(what="", show_progress=True): ''' Wait for the DC to get into the S_IDLE state. This should be invoked only after a CIB modification which would exercise the PE. Parameter "what" is whatever the caller wants to be printed if showing progress. It is assumed that the DC is already in a different state, usually it should be either PENGINE or TRANSITION. This assumption may not be true, but there's a high chance that it is since crmd should be faster to move through states than this shell. Further, it may also be that crmd already calculated the new graph, did transition, and went back to the idle state. This may in particular be the case if the transition turned out to be empty. Tricky. Though in practice it shouldn't be an issue. There's no timeout, as we expect the DC to eventually becomes idle. ''' dc = get_dc() if not dc: common_warn("can't find DC") return False cmd = "crm_attribute -Gq -t crm_config -n crmd-transition-delay 2> /dev/null" delay = get_stdout(add_sudo(cmd))[1] if delay: delaymsec = crm_msec(delay) if 0 < delaymsec: common_info("The crmd-transition-delay is configured. Waiting %d msec before check DC status." % delaymsec) time.sleep(delaymsec / 1000) cmd = "crmadmin -S %s" % dc cnt = 0 output_started = 0 init_sleep = 0.25 max_sleep = 1.00 sleep_time = init_sleep while True: rc, s = get_stdout(add_sudo(cmd)) if not s.startswith("Status"): common_warn("%s unexpected output: %s (exit code: %d)" % (cmd, s, rc)) return False try: dc_status = s.split()[-2] except: common_warn("%s unexpected output: %s" % (cmd, s)) return False if dc_status == "S_IDLE": if output_started: sys.stderr.write(" done\n") return True time.sleep(sleep_time) if sleep_time < max_sleep: sleep_time *= 2 if show_progress: if not output_started: output_started = 1 sys.stderr.write("waiting for %s to finish ." % what) cnt += 1 if cnt % 5 == 0: sys.stderr.write(".")