pacemaker/crm_history_3_b3a014c0f85b.patch
Tim Serong 0440703030 - Upgrade to 1.1.6.
- PE: Demote from Master does not clear previous errors
- crmd: Prevent secondary DC fencing resulting from CIB updates
  that are lost due to elections
- crmd: Log duplicate DC detection as a WARNING not ERROR
- crmd: Bug lf#2632 - Correctly handle nodes that return faster
  than stonith
- Core: Treat GNUTLS_E_UNEXPECTED_PACKET_LENGTH as normal
  termination of a TLS session
- cib: Call gnutls_bye() and shutdown() when disconnecting from
  remote TLS connections
- cib: Remove disconnected remote connections from mainloop
- cib: Attempt a graceful sign-off for remote TLS connections
- Core: Ensure there is sufficient space for EOS when building
  short-form option strings (prevents segfault)
- Core: Fix variable expansion in pkg-config files
- PE: Resolve memory leak reported by valgrind
- PE: Fix memory leak for re-allocated resources reported by
  valgrind
- PE: Improve the merging with template's operations
- crmd: Allow nodes to fence themselves if they're the last one
  standing (lf#2584)
- stonith: Add an API call for listing installed agents
- stonith: Allow the fencing history to be queried
- stonith: Ensure completed operations are recorded as such in
  the history
- stonith: Support --quiet to display just the seconds since
  epoch at which a node was last shot
- stonith: Serialize actions for a given device
- stonith: Add missing entries to stonith_error2string() (missing

OBS-URL: https://build.opensuse.org/package/show/network:ha-clustering:Factory/pacemaker?expand=0&rev=18
2011-09-20 14:36:23 +00:00

559 lines
21 KiB
Diff

# HG changeset patch
# User Dejan Muhamedagic <dejan@hello-penguin.com>
# Date 1312993121 -7200
# Node ID b3a014c0f85b2bbe1e6a2360c44fbbfc7ac27b73
# Parent a09974a06cdf6a3d73c3cdfa6e4d89d41e2ca9f0
Medium: Shell: improve peinputs and transition interface (bnc#710655,711060)
- allow specifying PE files as relative paths in order to
disambiguate between PE inputs with the same number
- remove peinputs "get" and "list" subcommands, just use 'v' for the
long listing
- remove transition "show" subcommand, if there is no subcommand
it is assumed that the user wants to do "show"
- detect (and ignore) empty transitions
- update completion tables
diff --git a/doc/crm.8.txt b/doc/crm.8.txt
--- a/doc/crm.8.txt
+++ b/doc/crm.8.txt
@@ -2560,55 +2560,62 @@ Example:
Every event in the cluster results in generating one or more
Policy Engine (PE) files. These files describe future motions of
-resources. The files are listed along with the node where they
-were created (the DC at the time). The `get` subcommand will copy
-all PE input files to the current working directory (and use ssh
-if necessary).
+resources. The files are listed as full paths in the current
+report directory. Add `v` to also see the creation time stamps.
Usage:
...............
- peinputs list [{<range>|<number>} ...]
- peinputs get [{<range>|<number>} ...]
+ peinputs [{<range>|<number>} ...] [v]
range :: <n1>:<n2>
...............
Example:
...............
- peinputs get 440:444 446
+ peinputs
+ peinputs 440:444 446
+ peinputs v
...............
[[cmdhelp_history_transition,show transition]]
==== `transition`
-The `show` subcommand will print actions planned by the PE and
-run graphviz (`dotty`) to display a graphical representation. Of
-course, for the latter an X11 session is required. This command
-invokes `ptest(8)` in background.
+This command will print actions planned by the PE and run
+graphviz (`dotty`) to display a graphical representation of the
+transition. Of course, for the latter an X11 session is required.
+This command invokes `ptest(8)` in background.
The `showdot` subcommand runs graphviz (`dotty`) to display a
graphical representation of the `.dot` file which has been
included in the report. Essentially, it shows the calculation
produced by `pengine` which is installed on the node where the
-report was produced.
+report was produced. In optimal case this output should not
+differ from the one produced by the locally installed `pengine`.
If the PE input file number is not provided, it defaults to the
last one, i.e. the last transition. If the number is negative,
then the corresponding transition relative to the last one is
chosen.
+If there are warning and error PE input files or different nodes
+were the DC in the observed timeframe, it may happen that PE
+input file numbers collide. In that case provide some unique part
+of the path to the file.
+
After the `ptest` output, logs about events that happened during
the transition are printed.
Usage:
...............
- transition show [<number>] [nograph] [v...] [scores] [actions] [utilization]
- transition showdot [<number>]
+ transition [<number>|<file>] [nograph] [v...] [scores] [actions] [utilization]
+ transition showdot [<number>|<file>]
...............
Examples:
...............
- transition show
- transition show 444
- transition show -1
+ transition
+ transition 444
+ transition -1
+ transition pe-error-3.bz2
+ transition node-a/pengine/pe-input-2.bz2
transition showdot 444
...............
diff --git a/shell/modules/completion.py b/shell/modules/completion.py
--- a/shell/modules/completion.py
+++ b/shell/modules/completion.py
@@ -165,14 +165,14 @@ def report_node_list(idx,delimiter = Fal
if delimiter:
return ' '
return crm_report.node_list()
-def report_pe_cmd_list(idx,delimiter = False):
+def report_pe_list_transition(idx,delimiter = False):
if delimiter:
return ' '
- return ["list","get","show","showdot"]
-def report_pe_list(idx,delimiter = False):
+ return crm_report.peinputs_list() + ["showdot"]
+def report_pe_list_peinputs(idx,delimiter = False):
if delimiter:
return ' '
- return crm_report.peinputs_list()
+ return crm_report.peinputs_list() + ["v"]
#
# completion for primitives including help for parameters
@@ -484,7 +484,8 @@ completer_lists = {
"resource" : (report_rsc_list,loop),
"node" : (report_node_list,loop),
"log" : (report_node_list,loop),
- "peinputs" : (report_pe_cmd_list,report_pe_list,loop),
+ "peinputs" : (report_pe_list_peinputs,loop),
+ "transition" : (report_pe_list_transition,),
},
}
def get_completer_list(level,cmd):
diff --git a/shell/modules/crm_pssh.py b/shell/modules/crm_pssh.py
--- a/shell/modules/crm_pssh.py
+++ b/shell/modules/crm_pssh.py
@@ -156,6 +156,9 @@ def next_peinputs(node_pe_l, outdir, err
myopts = ["-q", "-o", outdir, "-e", errdir]
opts, args = parse_args(myopts)
l.append([node, cmdline])
+ if not l:
+ # is this a failure?
+ return True
return do_pssh(l, opts)
# vim:ts=4:sw=4:et:
diff --git a/shell/modules/log_patterns.py b/shell/modules/log_patterns.py
--- a/shell/modules/log_patterns.py
+++ b/shell/modules/log_patterns.py
@@ -62,8 +62,3 @@ log_patterns = {
),
),
}
-
-transition_patt = (
- "crmd: .* Processing graph.*derived from .*/pe-[^-]+-(%%)[.]bz2", # transition start
- "crmd: .* Transition.*Source=.*/pe-[^-]+-(%%)[.]bz2.: (Stopped|Complete|Terminated)", # and stop
-)
diff --git a/shell/modules/report.py b/shell/modules/report.py
--- a/shell/modules/report.py
+++ b/shell/modules/report.py
@@ -31,7 +31,7 @@ from term import TerminalController
from xmlutil import *
from utils import *
from msg import *
-from log_patterns import log_patterns, transition_patt
+from log_patterns import log_patterns
_NO_PSSH = False
try:
from crm_pssh import next_loglines, next_peinputs
@@ -297,8 +297,8 @@ def human_date(dt):
def is_log(p):
return os.path.isfile(p) and os.path.getsize(p) > 0
-def pe_file_in_range(pe_f, a, ext):
- r = re.search("pe-[^-]+-([0-9]+)[.]%s$" % ext, pe_f)
+def pe_file_in_range(pe_f, a):
+ r = re.search("pe-[^-]+-([0-9]+)[.]bz2$", pe_f)
if not r:
return None
if not a or (a[0] <= int(r.group(1)) <= a[1]):
@@ -325,6 +325,17 @@ def update_loginfo(rptlog, logfile, oldp
except IOError, msg:
common_err("couldn't the update %s.info: %s" % (rptlog, msg))
+# r.group(1) transition number (a different thing from file number)
+# r.group(2) contains full path
+# r.group(3) file number
+transition_patt = (
+ "crmd: .* do_te_invoke: Processing graph ([0-9]+) .*derived from (.*/pe-[^-]+-(%%)[.]bz2)", # transition start
+ "crmd: .* run_graph: Transition ([0-9]+).*Source=(.*/pe-[^-]+-(%%)[.]bz2).: (Stopped|Complete|Terminated)", # and stop
+# r.group(1) transition number
+# r.group(2) number of actions
+ "crmd: .* unpack_graph: Unpacked transition (%%): ([0-9]+) actions", # number of actions
+)
+
class Report(Singleton):
'''
A hb_report class.
@@ -346,6 +357,7 @@ class Report(Singleton):
self.desc = None
self.log_l = []
self.central_log = None
+ self.peinputs_l = []
self.cibgrp_d = {}
self.cibrsc_l = []
self.cibnode_l = []
@@ -363,7 +375,7 @@ class Report(Singleton):
return self.cibnode_l
def peinputs_list(self):
return [re.search("pe-[^-]+-([0-9]+)[.]bz2$", x).group(1)
- for x in self._file_list("bz2")]
+ for x in self.peinputs_l]
def unpack_report(self, tarball):
'''
Unpack hb_report tarball.
@@ -495,28 +507,26 @@ class Report(Singleton):
continue
u_dir = os.path.join(self.loc, node)
rc = ext_cmd_nosudo("tar -C %s -x < %s" % (u_dir,fl[0]))
- def find_new_peinputs(self, a):
+ def find_new_peinputs(self, node_l):
'''
- Get a list of pe inputs appearing in logs.
+ Get a list of pe inputs appearing in new logs.
+ The log is put in self.outdir/node by pssh.
'''
if not os.path.isdir(self.outdir):
return []
l = []
- trans_re_l = [x.replace("%%","") for x in transition_patt]
- for node,rptlog,logfile,nextpos in a:
- node_l = []
+ for node in node_l:
fl = glob.glob("%s/*%s*" % (self.outdir,node))
if not fl:
continue
- for s in file2list(fl[0]):
- r = re.search(trans_re_l[0], s)
- if not r:
- continue
- node_l.append(r.group(1))
- if node_l:
- common_debug("found new PE inputs %s at %s" %
- ([os.path.basename(x) for x in node_l], node))
- l.append([node,node_l])
+ try:
+ f = open(fl[0])
+ except IOError,msg:
+ common_err("open %s: %s"%(fl[0],msg))
+ continue
+ pe_l = self.get_transitions([x for x in f], keep_pe_path = True)
+ if pe_l:
+ l.append([node,pe_l])
return l
def update_live(self):
'''
@@ -544,7 +554,7 @@ class Report(Singleton):
rmdir_r(self.errdir)
rc1 = next_loglines(a, self.outdir, self.errdir)
self.append_newlogs(a)
- pe_l = self.find_new_peinputs(a)
+ pe_l = self.find_new_peinputs([x[0] for x in a])
rmdir_r(self.outdir)
rmdir_r(self.errdir)
rc2 = True
@@ -677,6 +687,55 @@ class Report(Singleton):
for n in self.cibnode_l:
self.nodecolor[n] = self.nodecolors[i]
i = (i+1) % len(self.nodecolors)
+ def get_transitions(self, msg_l = None, keep_pe_path = False):
+ '''
+ Get a list of transitions.
+ Empty transitions are skipped.
+ We use the unpack_graph message to see the number of
+ actions.
+ Some callers need original PE file path (keep_pe_path),
+ otherwise we produce the path within the report.
+ If the caller doesn't provide the message list, then we
+ build it from the collected log files (self.logobj).
+ Otherwise, we get matches for transition patterns.
+ '''
+ trans_re_l = [x.replace("%%", "[0-9]+") for x in transition_patt]
+ if not msg_l:
+ msg_l = self.logobj.get_matches(trans_re_l)
+ else:
+ re_s = '|'.join(trans_re_l)
+ msg_l = [x for x in msg_l if re.search(re_s, x)]
+ pe_l = []
+ for msg in msg_l:
+ msg_a = msg.split()
+ if len(msg_a) < 8:
+ # this looks too short
+ common_warn("log message <%s> unexpected format, please report a bug" % msg)
+ continue
+ if msg_a[7] in ("unpack_graph:","run_graph:"):
+ continue # we want another message
+ node = msg_a[3]
+ pe_file = msg_a[-1]
+ pe_base = os.path.basename(pe_file)
+ # check if there were any actions in this transition
+ r = re.search(trans_re_l[0], msg)
+ trans_num = r.group(1)
+ unpack_patt = transition_patt[2].replace("%%", trans_num)
+ num_actions = 0
+ for t in msg_l:
+ try:
+ num_actions = int(re.search(unpack_patt, t).group(2))
+ break
+ except: pass
+ if num_actions == 0: # empty transition
+ common_debug("skipping empty transition %s (%s)" % (trans_num, pe_base))
+ continue
+ common_debug("found PE input at %s: %s" % (node, pe_file))
+ if keep_pe_path:
+ pe_l.append(pe_file)
+ else:
+ pe_l.append(os.path.join(self.loc, node, "pengine", pe_base))
+ return pe_l
def report_setup(self):
if not self.loc:
return
@@ -687,6 +746,11 @@ class Report(Singleton):
self.set_node_colors()
self.logobj = LogSyslog(self.central_log, self.log_l, \
self.from_dt, self.to_dt)
+ self.peinputs_l = self.get_transitions()
+ for pe_input in self.peinputs_l:
+ if not os.path.isfile(pe_input):
+ warn_once("%s in the logs, but not in the report" % pe_input)
+ self.peinputs_l.remove(pe_input)
def prepare_source(self):
'''
Unpack a hb_report tarball.
@@ -821,16 +885,16 @@ class Report(Singleton):
Search for events within the given transition.
'''
pe_base = os.path.basename(pe_file)
- r = re.search("pe-[^-]+-([0-9]+)[.]bz2", pe_base)
+ r = re.search("pe-[^-]+-([0-9]+)[.]", pe_base)
pe_num = r.group(1)
trans_re_l = [x.replace("%%",pe_num) for x in transition_patt]
trans_start = self.logobj.search_logs(self.log_l, trans_re_l[0])
trans_end = self.logobj.search_logs(self.log_l, trans_re_l[1])
if not trans_start:
- common_warn("transition %s start not found in logs" % pe_base)
+ common_warn("start of transition %s not found in logs" % pe_base)
return False
if not trans_end:
- common_warn("transition %s end not found in logs" % pe_base)
+ common_warn("end of transition %s not found in logs (transition not complete yet?)" % pe_base)
return False
common_debug("transition start: %s" % trans_start[0])
common_debug("transition end: %s" % trans_end[0])
@@ -891,23 +955,23 @@ class Report(Singleton):
if not l:
return False
self.show_logs(log_l = l)
- def _file_list(self, ext, a = []):
- '''
- Return list of PE (or dot) files (abs paths) sorted by
- mtime.
- Input is a number or a pair of numbers representing
- range. Otherwise, all matching files are returned.
- '''
+ def pelist(self, a = []):
if not self.prepare_source():
return []
- if not isinstance(a,(tuple,list)) and a is not None:
+ if isinstance(a,(tuple,list)):
+ if len(a) == 1:
+ a.append(a[0])
+ elif a is not None:
a = [a,a]
- return sort_by_mtime([x for x in dirwalk(self.loc) \
- if pe_file_in_range(x,a,ext)])
- def pelist(self, a = []):
- return self._file_list("bz2", a)
+ return [x for x in self.peinputs_l \
+ if pe_file_in_range(x, a)]
def dotlist(self, a = []):
- return self._file_list("dot", a)
+ l = [x.replace("bz2","dot") for x in self.pelist(a)]
+ return [x for x in l if os.path.isfile(x)]
+ def find_pe_files(self, path):
+ 'Find a PE or dot file matching part of the path.'
+ pe_l = path.endswith(".dot") and self.dotlist() or self.pelist()
+ return [x for x in pe_l if x.endswith(path)]
def find_file(self, f):
return file_find_by_name(self.loc, f)
diff --git a/shell/modules/ui.py.in b/shell/modules/ui.py.in
--- a/shell/modules/ui.py.in
+++ b/shell/modules/ui.py.in
@@ -1686,8 +1686,8 @@ Examine Pacemaker's history: node and re
self.cmd_table["resource"] = (self.resource,(1,),1,0)
self.cmd_table["node"] = (self.node,(1,),1,1)
self.cmd_table["log"] = (self.log,(0,),1,0)
- self.cmd_table["peinputs"] = (self.peinputs,(1,),1,0)
- self.cmd_table["transition"] = (self.transition,(1,),1,0)
+ self.cmd_table["peinputs"] = (self.peinputs,(0,),1,0)
+ self.cmd_table["transition"] = (self.transition,(0,),1,0)
self._set_source(options.history)
def _no_source(self):
common_error("we have no source set yet! please use the source command")
@@ -1831,64 +1831,83 @@ Examine Pacemaker's history: node and re
s = bz2.decompress(''.join(f))
f.close()
return run_ptest(s, nograph, scores, utilization, actions, verbosity)
- def peinputs(self,cmd,subcmd,*args):
- """usage: peinputs list [{<range>|<number>} ...]
- peinputs get [{<range>|<number>} ...]"""
- if subcmd not in ("get","list"):
- bad_usage(cmd,subcmd)
- return False
- if args:
+ def peinputs(self,cmd,*args):
+ """usage: peinputs [{<range>|<number>} ...] [v]"""
+ argl = list(args)
+ long = "v" in argl
+ if long:
+ argl.remove("v")
+ if argl:
l = []
- for s in args:
+ for s in argl:
a = convert2ints(s.split(':'))
- if len(a) == 2 and not check_range(a):
+ if a and len(a) == 2 and not check_range(a):
common_err("%s: invalid peinputs range" % a)
return False
l += crm_report.pelist(a)
else:
l = crm_report.pelist()
if not l: return False
- if subcmd == "list":
- s = get_stdout("ls -lrt %s" % ' '.join(l))
- page_string(s)
+ if long:
+ s = get_stdout("for f in %s; do ls -l $f; done" % ' '.join(l))
else:
- print '\n'.join(l)
- def transition(self,cmd,subcmd,*args):
- """usage: transition show [<number>] [nograph] [v...] [scores] [actions] [utilization]
- transition showdot [<number>]"""
- if subcmd not in ("show", "showdot"):
- bad_usage(cmd,subcmd)
- return False
- try: n = convert2ints(args[0])
- except: n = None
- startarg = 1
- if n is None:
- idx = -1
- startarg = 0 # peinput number missing
- elif n <= 0:
- idx = n - 1
- n = [] # to get all peinputs
- else:
- idx = 0
- if subcmd == "showdot":
+ s = '\n'.join(l)
+ page_string(s)
+ def transition(self,cmd,*args):
+ """usage: transition [<number>|<file>] [nograph] [v...] [scores] [actions] [utilization]
+ transition showdot [<number>|<file>]"""
+ argl = list(args)
+ subcmd = "show"
+ if argl and argl[0] == "showdot":
if not user_prefs.dotty:
common_err("install graphviz to draw transition graphs")
return False
- l = crm_report.dotlist(n)
+ subcmd = "showdot"
+ argl.remove(subcmd)
+ f = None
+ startarg = 1
+ if argl and re.search('pe-', argl[0]):
+ l = crm_report.find_pe_files(argl[0])
+ if len(l) == 0:
+ common_err("%s: path not found" % argl[0])
+ return False
+ elif len(l) > 1:
+ common_err("%s: path ambiguous" % argl[0])
+ return False
+ f = l[0]
else:
- l = crm_report.pelist(n)
- if len(l) < abs(idx):
- common_err("pe input or dot file not found")
+ try: n = convert2ints(argl[0])
+ except: n = None
+ if n is None:
+ idx = -1
+ startarg = 0 # peinput number missing
+ elif n <= 0:
+ idx = n - 1
+ n = [] # to get all peinputs
+ else:
+ idx = 0
+ if subcmd == "showdot":
+ l = crm_report.dotlist(n)
+ else:
+ l = crm_report.pelist(n)
+ if len(l) < abs(idx):
+ if subcmd == "show":
+ common_err("pe input file not found")
+ else:
+ common_err("dot file not found")
+ return False
+ f = l[idx]
+ if not f:
return False
rc = True
if subcmd == "show":
- self.pe_file = l[idx]
+ self.pe_file = f # self.pe_file needed by self.ptest
rc = ptestlike(self.ptest,'vv',"%s %s" % \
- (cmd, subcmd), *args[startarg:])
- if rc:
- crm_report.show_transition_log(self.pe_file)
+ (cmd, subcmd), *argl[startarg:])
else:
- show_dot_graph(l[idx])
+ show_dot_graph(f.replace("bz2","dot"))
+ if rc:
+ crm_report.show_transition_log(f)
return rc
class TopLevel(UserInterface):
diff --git a/shell/modules/utils.py b/shell/modules/utils.py
--- a/shell/modules/utils.py
+++ b/shell/modules/utils.py
@@ -392,7 +392,7 @@ def run_ptest(graph_s, nograph, scores,
Pipe graph_s thru ptest(8). Show graph using dotty if requested.
'''
actions_filter = "grep LogActions: | grep -vw Leave"
- ptest = "ptest -X"
+ ptest = "2>&1 ptest -X"
if verbosity:
if actions:
verbosity = 'v' * max(3,len(verbosity))
@@ -408,7 +408,8 @@ def run_ptest(graph_s, nograph, scores,
dotfile = None
# ptest prints to stderr
if actions:
- ptest = "%s 2>&1 | %s" % (ptest, actions_filter)
+ ptest = "%s | %s" % (ptest, actions_filter)
+ common_debug("invoke: %s" % ptest)
print get_stdout(ptest, input_s = graph_s)
#page_string(get_stdout(ptest, input_s = graph_s))
if dotfile:
@@ -443,7 +444,7 @@ def check_range(a):
return False
if not isinstance(a[0],int) or not isinstance(a[1],int):
return False
- return (int(a[0]) < int(a[1]))
+ return (int(a[0]) <= int(a[1]))
def sort_by_mtime(l):
'Sort a (small) list of files by time mod.'