diff --git a/acl_fix_d44ff2711662.patch b/acl_fix_d44ff2711662.patch deleted file mode 100644 index 30781d2..0000000 --- a/acl_fix_d44ff2711662.patch +++ /dev/null @@ -1,163 +0,0 @@ -# HG changeset patch -# User Andrew Beekhof -# Date 1314251944 -36000 -# Node ID d44ff2711662517d91b542b122218cffa2af3eb1 -# Parent 4cc8fdf2827a31d41b48b8c97d784c75c9418eda -Low: cib: Remove the remaining uses of the xml_child_iter() macro - -diff --git a/lib/cib/cib_acl.c b/lib/cib/cib_acl.c ---- a/lib/cib/cib_acl.c -+++ b/lib/cib/cib_acl.c -@@ -159,8 +159,7 @@ acl_check_diff(xmlNode *request, xmlNode - - orig_diff = diff_xml_object_orig(current_cib, result_cib, FALSE, diff); - -- xml_child_iter( -- orig_diff, diff_child, -+ for (diff_child = __xml_first_child(orig_diff); diff_child; diff_child = __xml_next(diff_child)) { - const char *tag = crm_element_name(diff_child); - GListPtr parsed_acl = NULL; - -@@ -176,8 +175,7 @@ acl_check_diff(xmlNode *request, xmlNode - continue; - } - -- xml_child_iter( -- diff_child, diff_cib, -+ for (diff_cib = __xml_first_child(diff_child); diff_cib; diff_child = __xml_next(diff_cib)) { - GHashTable *xml_perms = NULL; - - gen_xml_perms(diff_cib, parsed_acl, &xml_perms); -@@ -188,9 +186,9 @@ acl_check_diff(xmlNode *request, xmlNode - crm_warn("User '%s' doesn't have enough permission to modify the CIB objects", user); - goto done; - } -- ); -+ } - free_acl(parsed_acl); -- ); -+ } - - done: - free_xml(orig_diff); -@@ -264,8 +262,7 @@ unpack_user_acl(xmlNode *xml_acls, const - return FALSE; - } - -- xml_child_iter( -- xml_acls, xml_acl, -+ for (xml_acl = __xml_first_child(xml_acls); xml_acl; xml_acl = __xml_next(xml_acl)) { - const char *tag = crm_element_name(xml_acl); - const char *id = crm_element_value(xml_acl, XML_ATTR_ID); - -@@ -276,7 +273,7 @@ unpack_user_acl(xmlNode *xml_acls, const - return TRUE; - } - } -- ); -+ } - return FALSE; - } - -@@ -296,8 +293,7 @@ user_match(const char *user, const char - static gboolean - unpack_acl(xmlNode *xml_acls, xmlNode *xml_acl, GListPtr *acl) - { -- xml_child_iter( -- xml_acl, acl_child, -+ for (acl_child = __xml_first_child(xml_acl); acl_child; acl_child = __xml_next(acl_child)) { - const char *tag = crm_element_name(acl_child); - - if (crm_str_eq(XML_ACL_TAG_ROLE_REF, tag, TRUE)) { -@@ -316,8 +312,8 @@ unpack_acl(xmlNode *xml_acls, xmlNode *x - static gboolean - unpack_role_acl(xmlNode *xml_acls, const char *role, GListPtr *acl) - { -- xml_child_iter_filter( -- xml_acls, xml_acl, XML_ACL_TAG_ROLE, -+ for (xml_acl = __xml_first_child(xml_acls); xml_acl; xml_acl = __xml_next(xml_acl)) { -+ if(crm_str_eq(XML_ACL_TAG_ROLE, (const char *)child->name, TRUE)) { - const char *role_id = crm_element_value(xml_acl, XML_ATTR_ID); - - if (role_id && crm_str_eq(role, role_id, TRUE)) { -@@ -325,7 +321,8 @@ unpack_role_acl(xmlNode *xml_acls, const - unpack_acl(xml_acls, xml_acl, acl); - return TRUE; - } -- ); -+ } -+ } - return FALSE; - } - -@@ -495,12 +492,11 @@ search_xml_children(GListPtr *children, - } - - if(search_matches || match_found == 0) { -- xml_child_iter( -- root, child, -+ for (child = __xml_first_child(root); child; child = __xml_next(child)) { - match_found += search_xml_children( - children, child, tag, field, value, - search_matches); -- ); -+ } - } - - return match_found; -@@ -563,10 +559,9 @@ update_xml_perms(xmlNode *xml, acl_obj_t - crm_debug_3("Permission for element: element_mode=%s, tag=%s, id=%s", - perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); - -- xml_child_iter( -- xml, child, -+ for (child = __xml_first_child(root); child; child = __xml_next(child)) { - update_xml_children_perms(child, perm->mode, xml_perms); -- ); -+ } - - } else { - if (perm->attribute_perms == NULL -@@ -610,10 +605,9 @@ update_xml_children_perms(xmlNode *xml, - crm_debug_4("Permission for child element: element_mode=%s, tag=%s, id=%s", - mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); - -- xml_child_iter( -- xml, child, -+ for (child = __xml_first_child(root); child; child = __xml_next(child)) { - update_xml_children_perms(child, mode, xml_perms); -- ); -+ } - - return TRUE; - } -@@ -647,12 +641,11 @@ acl_filter_xml(xmlNode *xml, GHashTable - xml_perm_t *perm = NULL; - int allow_counter = 0; - -- xml_child_iter( -- xml, child, -+ for (child = __xml_first_child(xml); child; child = __xml_next(child)) { - if (acl_filter_xml(child, xml_perms) == FALSE) { - children_counter++; - } -- ); -+ } - - g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm); - -@@ -720,12 +713,11 @@ acl_check_diff_xml(xmlNode *xml, GHashTa - { - xml_perm_t *perm = NULL; - -- xml_child_iter( -- xml, child, -+ for (child = __xml_first_child(xml); child; child = __xml_next(child)) { - if (acl_check_diff_xml(child, xml_perms) == FALSE) { - return FALSE; - } -- ); -+ } - - g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm); - diff --git a/bug-728579_pacemaker-stonith-dev-id.diff b/bug-728579_pacemaker-stonith-dev-id.diff new file mode 100644 index 0000000..83a3e41 --- /dev/null +++ b/bug-728579_pacemaker-stonith-dev-id.diff @@ -0,0 +1,12 @@ +diff --git a/fencing/commands.c b/fencing/commands.c +index 8269d30..b2660ca 100644 +--- a/fencing/commands.c ++++ b/fencing/commands.c +@@ -368,6 +368,7 @@ static stonith_device_t *build_device_from_xml(xmlNode *msg) + device->agent = crm_element_value_copy(dev, "agent"); + device->namespace = crm_element_value_copy(dev, "namespace"); + device->params = xml2list(dev); ++ g_hash_table_insert(device->params, crm_strdup("CRM_" F_STONITH_DEVICE), crm_strdup(device->id)); + device->work = mainloop_add_trigger(G_PRIORITY_HIGH, stonith_device_dispatch, device); + /* TODO: Hook up priority */ + diff --git a/crm_deleteunmanaged.patch b/crm_deleteunmanaged.patch deleted file mode 100644 index 62a7cee..0000000 --- a/crm_deleteunmanaged.patch +++ /dev/null @@ -1,100 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1313755383 -7200 -# Node ID e8ea8fb95f310997995576ee831693b0d3b2736a -# Parent 0abb257259ed722abaa32a237c3c284c08ec0737 -Medium: Shell: enable removal of unmanaged resources (bnc#696506) - -diff --git a/shell/modules/cibconfig.py b/shell/modules/cibconfig.py ---- a/shell/modules/cibconfig.py -+++ b/shell/modules/cibconfig.py -@@ -2303,7 +2303,7 @@ class CibFactory(Singleton): - no_object_err(obj_id) - rc = False - continue -- if is_rsc_running(obj_id): -+ if is_rsc_managed(obj_id) and is_rsc_running(obj_id): - common_err("resource %s is running, can't delete it" % obj_id) - rc = False - else: -diff --git a/shell/modules/xmlutil.py b/shell/modules/xmlutil.py ---- a/shell/modules/xmlutil.py -+++ b/shell/modules/xmlutil.py -@@ -178,6 +178,34 @@ def shadowfile(name): - def shadow2doc(name): - return file2doc(shadowfile(name)) - -+def is_xs_boolean_true(bool): -+ return bool.lower() in ("true","1") -+def is_rsc_managed(id): -+ if not is_live_cib(): -+ return False -+ rsc_node = rsc2node(id) -+ if not rsc_node: -+ return False -+ prop_node = get_properties_node(get_conf_elem(cibdump2doc("crm_config"), "crm_config")) -+ # maintenance-mode, if true, overrides all -+ attr = get_attr_value(prop_node, "maintenance-mode") -+ if attr and is_xs_boolean_true(attr): -+ return False -+ # then check the rsc is-managed meta attribute -+ rsc_meta_node = get_rsc_meta_node(rsc_node) -+ attr = get_attr_value(rsc_meta_node, "is-managed") -+ if attr: -+ return is_xs_boolean_true(attr) -+ # then rsc_defaults is-managed attribute -+ rsc_dflt_node = get_rscop_defaults_meta_node(get_conf_elem(cibdump2doc("rsc_defaults"), "rsc_defaults")) -+ attr = get_attr_value(rsc_dflt_node, "is-managed") -+ if attr: -+ return is_xs_boolean_true(attr) -+ # finally the is-managed-default property -+ attr = get_attr_value(prop_node, "is-managed-default") -+ if attr: -+ return is_xs_boolean_true(attr) -+ return True - def is_rsc_running(id): - if not is_live_cib(): - return False -@@ -691,12 +719,20 @@ def silly_constraint(c_node,rsc_id): - def get_rsc_children_ids(node): - return [x.getAttribute("id") \ - for x in node.childNodes if is_child_rsc(x)] --def get_rscop_defaults_meta_node(node): -+def get_child_nvset_node(node, attr_set = "meta_attributes"): -+ if not node: -+ return None - for c in node.childNodes: -- if not is_element(c) or c.tagName != "meta_attributes": -+ if not is_element(c) or c.tagName != attr_set: - continue - return c - return None -+def get_rscop_defaults_meta_node(node): -+ return get_child_nvset_node(node) -+def get_rsc_meta_node(node): -+ return get_child_nvset_node(node) -+def get_properties_node(node): -+ return get_child_nvset_node(node, attr_set = "cluster_property_set") - - def new_cib(): - doc = xml.dom.minidom.Document() -@@ -727,12 +763,19 @@ def new_cib_element(node,tagname,id_pfx) - node.appendChild(newnode) - return newnode - def get_attr_in_set(node,attr): -+ if not node: -+ return None - for c in node.childNodes: - if not is_element(c): - continue - if c.tagName == "nvpair" and c.getAttribute("name") == attr: - return c - return None -+def get_attr_value(node,attr): -+ n = get_attr_in_set(node,attr) -+ if not n: -+ return None -+ return n.getAttribute("value") - def set_attr(node,attr,value): - ''' - Set an attribute in the attribute set. diff --git a/crm_history-fix-hb_report-limit.patch b/crm_history-fix-hb_report-limit.patch deleted file mode 100644 index 7d2b93a..0000000 --- a/crm_history-fix-hb_report-limit.patch +++ /dev/null @@ -1,13 +0,0 @@ -Index: pacemaker/shell/modules/report.py -=================================================================== ---- pacemaker.orig/shell/modules/report.py -+++ pacemaker/shell/modules/report.py -@@ -643,6 +643,8 @@ class Report(Singleton): - def set_source(self,src): - 'Set our source.' - self.source = src -+ if self.source != "live": -+ self.reset_period() - def set_period(self,from_dt,to_dt): - ''' - Set from/to_dt. diff --git a/crm_history.patch b/crm_history.patch deleted file mode 100644 index eb3684d..0000000 --- a/crm_history.patch +++ /dev/null @@ -1,2213 +0,0 @@ -changeset: 10787:b694b75d2e33 -user: Dejan Muhamedagic -date: Mon Jul 18 12:35:57 2011 +0200 -summary: High: Shell: set of commands to examine logs, reports, etc - -diff -r 9609937061d7 -r b694b75d2e33 Makefile.am ---- a/Makefile.am Fri Jul 15 10:51:00 2011 +1000 -+++ b/Makefile.am Mon Jul 18 12:35:57 2011 +0200 -@@ -39,8 +39,10 @@ install-exec-local: - $(INSTALL) -d $(DESTDIR)/$(LCRSODIR) - $(INSTALL) -d -m 750 $(DESTDIR)/$(CRM_CONFIG_DIR) - $(INSTALL) -d -m 750 $(DESTDIR)/$(CRM_STATE_DIR) -+ $(INSTALL) -d -m 750 $(DESTDIR)/$(CRM_CACHE_DIR) - -chown $(CRM_DAEMON_USER):$(CRM_DAEMON_GROUP) $(DESTDIR)/$(CRM_CONFIG_DIR) - -chown $(CRM_DAEMON_USER):$(CRM_DAEMON_GROUP) $(DESTDIR)/$(CRM_STATE_DIR) -+ -chown $(CRM_DAEMON_USER):$(CRM_DAEMON_GROUP) $(DESTDIR)/$(CRM_CACHE_DIR) - if BUILD_CS_SUPPORT - rm -f $(DESTDIR)$(LCRSODIR)/pacemaker.lcrso $(DESTDIR)$(LCRSODIR)/service_crm.so - cp $(DESTDIR)$(libdir)/service_crm.so $(DESTDIR)$(LCRSODIR)/pacemaker.lcrso -diff -r 9609937061d7 -r b694b75d2e33 configure.ac ---- a/configure.ac Fri Jul 15 10:51:00 2011 +1000 -+++ b/configure.ac Mon Jul 18 12:35:57 2011 +0200 -@@ -460,6 +460,10 @@ CRM_STATE_DIR=${localstatedir}/run/crm - AC_DEFINE_UNQUOTED(CRM_STATE_DIR,"$CRM_STATE_DIR", Where to keep state files and sockets) - AC_SUBST(CRM_STATE_DIR) - -+CRM_CACHE_DIR=${localstatedir}/cache/crm -+AC_DEFINE_UNQUOTED(CRM_CACHE_DIR,"$CRM_CACHE_DIR", Where crm shell keeps the cache) -+AC_SUBST(CRM_CACHE_DIR) -+ - PE_STATE_DIR="${localstatedir}/lib/pengine" - AC_DEFINE_UNQUOTED(PE_STATE_DIR,"$PE_STATE_DIR", Where to keep PEngine outputs) - AC_SUBST(PE_STATE_DIR) -diff -r 9609937061d7 -r b694b75d2e33 doc/crm.8.txt ---- a/doc/crm.8.txt Fri Jul 15 10:51:00 2011 +1000 -+++ b/doc/crm.8.txt Mon Jul 18 12:35:57 2011 +0200 -@@ -13,7 +13,7 @@ crm - Pacemaker command line interface f - - SYNOPSIS - -------- --*crm* [-D output_type] [-f file] [-hFRDw] [--version] [args] -+*crm* [-D output_type] [-f file] [-H hist_src] [-hFRDw] [--version] [args] - - - DESCRIPTION -@@ -67,6 +67,11 @@ OPTIONS - Make `crm` wait for the transition to finish. Applicable only - for commands such as "resource start." - -+*-H, --history*='DIR|FILE':: -+ The `history` commands can examine either live cluster -+ (default) or a report generated by `hb_report`. Use this -+ option to specify a directory or file containing the report. -+ - *-h, --help*:: - Print help page. - -@@ -2346,6 +2351,254 @@ Example: - simulate - ............... - -+[[cmdhelp_history,cluster history]] -+=== `history` -+ -+Examining Pacemaker's history is a particularly involved task. -+The number of subsystems to be considered, the complexity of the -+configuration, and the set of various information sources, most -+of which are not exactly human readable, keep analyzing resource -+or node problems accessible to only the most knowledgeable. Or, -+depending on the point of view, to the most persistent. The -+following set of commands has been devised in hope to make -+cluster history more accessible. -+ -+Of course, looking at _all_ history could be time consuming -+regardless of how good tools at hand are. Therefore, one should -+first say which period he or she wants to analyze. If not -+otherwise specified, the last hour is considered. Logs and other -+relevant information is collected using `hb_report`. Since this -+process takes some time and we always need fresh logs, -+information is refreshed in a much faster way using `pssh(1)`. If -+`python-pssh` is not found on the system, examining live cluster -+is still possible though not as comfortable. -+ -+Apart from examining live cluster, events may be retrieved from a -+report generated by `hb_report` (see also the `-H` option). In -+that case we assume that the period stretching the whole report -+needs to be investigated. Of course, it is still possible to -+further reduce the time range. -+ -+==== `info` -+ -+The `info` command shows most important information about the -+cluster. -+ -+Usage: -+............... -+ info -+............... -+Example: -+............... -+ info -+............... -+ -+[[cmdhelp_history_latest,show latest news from the cluster]] -+==== `latest` -+ -+The `latest` command shows a bit of recent history, more -+precisely whatever happened since the last cluster change (the -+latest transition). -+ -+Usage: -+............... -+ latest -+............... -+Example: -+............... -+ latest -+............... -+ -+[[cmdhelp_history_limit,limit timeframe to be examined]] -+==== `limit` -+ -+All history commands look at events within certain period. It -+defaults to the last hour for the live cluster source. There is -+no limit for the `hb_report` source. Use this command to set the -+timeframe. -+ -+The time period is parsed by the dateutil python module. It -+covers wide range of date formats. For instance: -+ -+- 3:00 (today at 3am) -+- 15:00 (today at 3pm) -+- 2010/9/1 2pm (September 1st 2010 at 2pm) -+ -+We won't bother to give definition of the time specification in -+usage below. Either use common sense or read the -+http://labix.org/python-dateutil[dateutil] documentation. -+ -+If dateutil is not available, then the time is parsed using -+strptime and only the kind as printed by `date(1)` is allowed: -+ -+- Tue Sep 15 20:46:27 CEST 2010 -+ -+Usage: -+............... -+ limit [] -+............... -+Examples: -+............... -+ limit 10:15 -+ limit 15h22m 16h -+ limit "Sun 5 20:46" "Sun 5 22:00" -+............... -+ -+[[cmdhelp_history_source,set source to be examined]] -+==== `source` -+ -+Events to be examined can come from the current cluster or from a -+`hb_report` report. This command sets the source. `source live` -+sets source to the running cluster and system logs. If no source -+is specified, the current source information is printed. -+ -+In case a report source is specified as a file reference, the file -+is going to be unpacked in place where it resides. This directory -+is not removed on exit. -+ -+Usage: -+............... -+ source [||live] -+............... -+Examples: -+............... -+ source live -+ source /tmp/customer_case_22.tar.bz2 -+ source /tmp/customer_case_22 -+ source -+............... -+ -+[[cmdhelp_history_refresh,refresh live report]] -+==== `refresh` -+ -+This command makes sense only for the `live` source and makes -+`crm` collect the latest logs and other relevant information from -+the logs. If you want to make a completely new report, specify -+`force`. -+ -+Usage: -+............... -+ refresh [force] -+............... -+ -+[[cmdhelp_history_detail,set the level of detail shown]] -+==== `detail` -+ -+How much detail to show from the logs. -+ -+Usage: -+............... -+ detail -+ -+ detail_level :: small integer (defaults to 0) -+............... -+Example: -+............... -+ detail 1 -+............... -+ -+[[cmdhelp_history_setnodes,set the list of cluster nodes]] -+==== `setnodes` -+ -+In case the host this program runs on is not part of the cluster, -+it is necessary to set the list of nodes. -+ -+Usage: -+............... -+ setnodes node [ ...] -+............... -+Example: -+............... -+ setnodes node_a node_b -+............... -+ -+[[cmdhelp_history_resource,resource failed actions]] -+==== `resource` -+ -+Show status changes and any failures that happened on a resource. -+ -+Usage: -+............... -+ resource [ ...] -+............... -+Example: -+............... -+ resource mydb -+............... -+ -+[[cmdhelp_history_node,node events]] -+==== `node` -+ -+Show important events that happened on a node. Important events -+are node lost and join, standby and online, and fence. -+ -+Usage: -+............... -+ node [ ...] -+............... -+Example: -+............... -+ node node1 -+............... -+ -+[[cmdhelp_history_log,log content]] -+==== `log` -+ -+Show logs for a node or combined logs of all nodes. -+ -+Usage: -+............... -+ log [] -+............... -+Example: -+............... -+ log node-a -+............... -+ -+[[cmdhelp_history_peinputs,list or get PE input files]] -+==== `peinputs` -+ -+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). -+ -+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. -+ -+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. -+ -+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. -+ -+Usage: -+............... -+ peinputs list [{|} ...] -+ peinputs get [{|} ...] -+ peinputs show [] [nograph] [v...] [scores] [actions] [utilization] -+ peinputs showdot [] -+ -+ range :: : -+............... -+Example: -+............... -+ peinputs get 440:444 446 -+ peinputs show -+ peinputs show 444 -+ peinputs show -1 -+ peinputs showdot 444 -+............... -+ - === `end` (`cd`, `up`) - - The `end` command ends the current level and the user moves to -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/Makefile.am ---- a/shell/modules/Makefile.am Fri Jul 15 10:51:00 2011 +1000 -+++ b/shell/modules/Makefile.am Mon Jul 18 12:35:57 2011 +0200 -@@ -33,6 +33,8 @@ modules = __init__.py \ - msg.py \ - parse.py \ - ra.py \ -+ report.py \ -+ log_patterns.py \ - singletonmixin.py \ - template.py \ - term.py \ -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/cibconfig.py ---- a/shell/modules/cibconfig.py Fri Jul 15 10:51:00 2011 +1000 -+++ b/shell/modules/cibconfig.py Mon Jul 18 12:35:57 2011 +0200 -@@ -20,6 +20,7 @@ import subprocess - import copy - import xml.dom.minidom - import re -+import time - - from singletonmixin import Singleton - from userprefs import Options, UserPrefs -@@ -404,7 +405,6 @@ class CibObjectSetRaw(CibObjectSet): - ''' - Edit or display one or more CIB objects (XML). - ''' -- actions_filter = "grep LogActions: | grep -vw Leave" - def __init__(self, *args): - CibObjectSet.__init__(self, *args) - self.obj_list = cib_factory.mkobj_list("xml",*args) -@@ -470,19 +470,6 @@ class CibObjectSetRaw(CibObjectSet): - def ptest(self, nograph, scores, utilization, actions, verbosity): - if not cib_factory.is_cib_sane(): - return False -- if verbosity: -- if actions: -- verbosity = 'v' * max(3,len(verbosity)) -- ptest = "ptest -X -%s" % verbosity.upper() -- if scores: -- ptest = "%s -s" % ptest -- if utilization: -- ptest = "%s -U" % ptest -- if user_prefs.dotty and not nograph: -- fd,dotfile = mkstemp() -- ptest = "%s -D %s" % (ptest,dotfile) -- else: -- dotfile = None - doc = cib_factory.objlist2doc(self.obj_list) - cib = doc.childNodes[0] - status = cib_status.get_status() -@@ -490,21 +477,9 @@ class CibObjectSetRaw(CibObjectSet): - common_err("no status section found") - return False - cib.appendChild(doc.importNode(status,1)) -- # ptest prints to stderr -- if actions: -- ptest = "%s 2>&1 | %s | %s" % \ -- (ptest, self.actions_filter, user_prefs.pager) -- else: -- ptest = "%s 2>&1 | %s" % (ptest, user_prefs.pager) -- pipe_string(ptest,doc.toprettyxml()) -+ graph_s = doc.toprettyxml() - doc.unlink() -- if dotfile: -- show_dot_graph(dotfile) -- vars.tmpfiles.append(dotfile) -- else: -- if not nograph: -- common_info("install graphviz to see a transition graph") -- return True -+ return run_ptest(graph_s, nograph, scores, utilization, actions, verbosity) - - # - # XML generate utilities -@@ -1426,6 +1401,7 @@ class CibFactory(Singleton): - def __init__(self): - self.init_vars() - self.regtest = options.regression_tests -+ self.last_commit_time = 0 - self.all_committed = True # has commit produced error - self._no_constraint_rm_msg = False # internal (just not to produce silly messages) - self.supported_cib_re = "^pacemaker-1[.][012]$" -@@ -1598,6 +1574,8 @@ class CibFactory(Singleton): - print "Remove queue:" - for obj in self.remove_queue: - obj.dump_state() -+ def last_commit_at(self): -+ return self.last_commit_time - def commit(self,force = False): - 'Commit the configuration to the CIB.' - if not self.doc: -@@ -1608,6 +1586,8 @@ class CibFactory(Singleton): - cnt = self.commit_doc(force) - if cnt: - # reload the cib! -+ if is_live_cib(): -+ self.last_commit_time = time.time() - self.reset() - self.initialize() - return self.all_committed -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/completion.py ---- a/shell/modules/completion.py Fri Jul 15 10:51:00 2011 +1000 -+++ b/shell/modules/completion.py Mon Jul 18 12:35:57 2011 +0200 -@@ -22,6 +22,7 @@ import readline - - from cibconfig import CibFactory - from cibstatus import CibStatus -+from report import Report - from levels import Levels - from ra import * - from vars import Vars -@@ -156,6 +157,22 @@ def ra_classes_list(idx,delimiter = Fals - if delimiter: - return ':' - return ra_classes() -+def report_rsc_list(idx,delimiter = False): -+ if delimiter: -+ return ' ' -+ return crm_report.rsc_list() -+def report_node_list(idx,delimiter = False): -+ if delimiter: -+ return ' ' -+ return crm_report.node_list() -+def report_pe_cmd_list(idx,delimiter = False): -+ if delimiter: -+ return ' ' -+ return ["list","get","show","showdot"] -+def report_pe_list(idx,delimiter = False): -+ if delimiter: -+ return ' ' -+ return crm_report.peinputs_list() - - # - # completion for primitives including help for parameters -@@ -463,6 +480,12 @@ completer_lists = { - "_regtest" : None, - "_objects" : None, - }, -+ "history" : { -+ "resource" : (report_rsc_list,loop), -+ "node" : (report_node_list,loop), -+ "log" : (report_node_list,loop), -+ "peinputs" : (report_pe_cmd_list,report_pe_list,loop), -+ }, - } - def get_completer_list(level,cmd): - 'Return a list of completer functions.' -@@ -474,5 +497,6 @@ user_prefs = UserPrefs.getInstance() - vars = Vars.getInstance() - cib_status = CibStatus.getInstance() - cib_factory = CibFactory.getInstance() -+crm_report = Report.getInstance() - - # vim:ts=4:sw=4:et: -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/crm_pssh.py ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/shell/modules/crm_pssh.py Mon Jul 18 12:35:57 2011 +0200 -@@ -0,0 +1,160 @@ -+# Modified pssh -+# Copyright (c) 2011, Dejan Muhamedagic -+# Copyright (c) 2009, Andrew McNabb -+# Copyright (c) 2003-2008, Brent N. Chun -+ -+"""Parallel ssh to the set of nodes in hosts.txt. -+ -+For each node, this essentially does an "ssh host -l user prog [arg0] [arg1] -+...". The -o option can be used to store stdout from each remote node in a -+directory. Each output file in that directory will be named by the -+corresponding remote node's hostname or IP address. -+""" -+ -+import fcntl -+import os -+import sys -+import glob -+import re -+ -+parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) -+if os.path.exists(os.path.join(parent, 'psshlib')): -+ sys.path.insert(0, parent) -+ -+from psshlib import psshutil -+from psshlib.manager import Manager, FatalError -+from psshlib.task import Task -+from psshlib.cli import common_parser, common_defaults -+ -+from msg import * -+ -+_DEFAULT_TIMEOUT = 60 -+_EC_LOGROT = 120 -+ -+def option_parser(): -+ parser = common_parser() -+ parser.usage = "%prog [OPTIONS] command [...]" -+ parser.epilog = "Example: pssh -h hosts.txt -l irb2 -o /tmp/foo uptime" -+ -+ parser.add_option('-i', '--inline', dest='inline', action='store_true', -+ help='inline aggregated output for each server') -+ parser.add_option('-I', '--send-input', dest='send_input', -+ action='store_true', -+ help='read from standard input and send as input to ssh') -+ parser.add_option('-P', '--print', dest='print_out', action='store_true', -+ help='print output as we get it') -+ -+ return parser -+ -+def parse_args(myargs): -+ parser = option_parser() -+ defaults = common_defaults(timeout=_DEFAULT_TIMEOUT) -+ parser.set_defaults(**defaults) -+ opts, args = parser.parse_args(myargs) -+ return opts, args -+ -+def show_errors(errdir, hosts): -+ for host in hosts: -+ fl = glob.glob("%s/*%s*" % (errdir,host)) -+ if not fl: -+ continue -+ for fname in fl: -+ try: -+ if os.stat(fname).st_size == 0: -+ continue -+ f = open(fname) -+ except: -+ continue -+ print "%s stderr:" % host -+ print ''.join(f) -+ f.close() -+ -+def do_pssh(l, opts): -+ if opts.outdir and not os.path.exists(opts.outdir): -+ os.makedirs(opts.outdir) -+ if opts.errdir and not os.path.exists(opts.errdir): -+ os.makedirs(opts.errdir) -+ if opts.send_input: -+ stdin = sys.stdin.read() -+ else: -+ stdin = None -+ manager = Manager(opts) -+ user = "" -+ port = "" -+ hosts = [] -+ for host, cmdline in l: -+ cmd = ['ssh', host, '-o', 'PasswordAuthentication=no', -+ '-o', 'SendEnv=PSSH_NODENUM'] -+ if opts.options: -+ for opt in opts.options: -+ cmd += ['-o', opt] -+ if user: -+ cmd += ['-l', user] -+ if port: -+ cmd += ['-p', port] -+ if opts.extra: -+ cmd.extend(opts.extra) -+ if cmdline: -+ cmd.append(cmdline) -+ hosts.append(host) -+ t = Task(host, port, user, cmd, opts, stdin) -+ manager.add_task(t) -+ try: -+ statuses = manager.run() -+ except FatalError: -+ common_err("pssh to nodes failed") -+ show_errors(opts.errdir, hosts) -+ return False -+ -+ if min(statuses) < 0: -+ # At least one process was killed. -+ common_err("ssh process was killed") -+ show_errors(opts.errdir, hosts) -+ return False -+ # The any builtin was introduced in Python 2.5 (so we can't use it yet): -+ #elif any(x==255 for x in statuses): -+ for status in statuses: -+ if status == 255: -+ common_warn("ssh processes failed") -+ show_errors(opts.errdir, hosts) -+ return False -+ for status in statuses: -+ if status not in (0, _EC_LOGROT): -+ common_warn("some ssh processes failed") -+ show_errors(opts.errdir, hosts) -+ return False -+ return True -+ -+def next_loglines(a, outdir, errdir): -+ ''' -+ pssh to nodes to collect new logs. -+ ''' -+ l = [] -+ for node,rptlog,logfile,nextpos in a: -+ common_debug("updating %s from %s (pos %d)" % (logfile, node, nextpos)) -+ cmdline = "perl -e 'exit(%d) if (stat(\"%s\"))[7]<%d' && tail -c +%d %s" % (_EC_LOGROT, logfile, nextpos-1, nextpos, logfile) -+ myopts = ["-q", "-o", outdir, "-e", errdir] -+ opts, args = parse_args(myopts) -+ l.append([node, cmdline]) -+ return do_pssh(l, opts) -+ -+def next_peinputs(node_pe_l, outdir, errdir): -+ ''' -+ pssh to nodes to collect new logs. -+ ''' -+ l = [] -+ for node,pe_l in node_pe_l: -+ r = re.search("(.*)/pengine/", pe_l[0]) -+ if not r: -+ common_err("strange, %s doesn't contain string pengine" % pe_l[0]) -+ continue -+ dir = "/%s" % r.group(1) -+ red_pe_l = [x.replace("%s/" % r.group(1),"") for x in pe_l] -+ common_debug("getting new PE inputs %s from %s" % (red_pe_l, node)) -+ cmdline = "tar -C %s -cf - %s" % (dir, ' '.join(red_pe_l)) -+ myopts = ["-q", "-o", outdir, "-e", errdir] -+ opts, args = parse_args(myopts) -+ l.append([node, cmdline]) -+ return do_pssh(l, opts) -+ -+# vim:ts=4:sw=4:et: -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/log_patterns.py ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/shell/modules/log_patterns.py Mon Jul 18 12:35:57 2011 +0200 -@@ -0,0 +1,69 @@ -+# Copyright (C) 2011 Dejan Muhamedagic -+# -+# log pattern specification -+# -+# patterns are grouped one of several classes: -+# - resources: pertaining to a resource -+# - node: pertaining to a node -+# - quorum: quorum changes -+# - events: other interesting events (core dumps, etc) -+# -+# paterns are grouped based on a detail level -+# detail level 0 is the lowest, i.e. should match the least -+# number of relevant messages -+ -+# NB: If you modify this file, you must follow python syntax! -+ -+log_patterns = { -+ "resource": ( -+ ( # detail 0 -+ "lrmd:.*rsc:%%.*(start|stop)", -+ "lrmd:.*RA output:.*%%.*stderr", -+ "lrmd:.*WARN:.*Managed.*%%.*exited", -+ ), -+ ( # detail 1 -+ "lrmd:.*rsc:%%.*probe", -+ "lrmd:.*info:.*Managed.*%%.*exited", -+ ), -+ ), -+ "node": ( -+ ( # detail 0 -+ "%%.*Corosync.Cluster.Engine", -+ "%%.*Executive.Service.RELEASE", -+ "%%.*crm_shutdown:.Requesting.shutdown", -+ "%%.*pcmk_shutdown:.Shutdown.complete", -+ "%%.*Configuration.validated..Starting.heartbeat", -+ "pengine.*Scheduling Node %%", -+ "te_fence_node.*Exec.*%%", -+ "stonith-ng.*log_oper.*reboot.*%%", -+ "stonithd.*to STONITH.*%%", -+ "stonithd.*fenced node %%", -+ "pcmk_peer_update.*(lost|memb): %%", -+ "crmd.*ccm_event.*(NEW|LOST) %%", -+ ), -+ ( # detail 1 -+ ), -+ ), -+ "quorum": ( -+ ( # detail 0 -+ "crmd.*crm_update_quorum:.Updating.quorum.status", -+ "crmd.*ais.disp.*quorum.(lost|ac?quir)", -+ ), -+ ( # detail 1 -+ ), -+ ), -+ "events": ( -+ ( # detail 0 -+ "CRIT:", -+ "ERROR:", -+ ), -+ ( # detail 1 -+ "WARN:", -+ ), -+ ), -+} -+ -+transition_patt = ( -+ "crmd: .* Processing graph.*derived from (.*bz2)", # transition start -+ "crmd: .* Transition.*Source=(.*bz2): (Stopped|Complete|Terminated)", # and stop -+) -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/main.py ---- a/shell/modules/main.py Fri Jul 15 10:51:00 2011 +1000 -+++ b/shell/modules/main.py Mon Jul 18 12:35:57 2011 +0200 -@@ -172,7 +172,7 @@ def usage(rc): - f = sys.stdout - print >> f, """ - usage: -- crm [-D display_type] [-f file] [-hF] [args] -+ crm [-D display_type] [-f file] [-H hist_src] [-hFRDw] [--version] [args] - - Use crm without arguments for an interactive session. - Supply one or more arguments for a "single-shot" use. -@@ -226,8 +226,8 @@ def run(): - - try: - opts, args = getopt.getopt(sys.argv[1:], \ -- 'whdf:FRD:', ("wait","version","help","debug","file=",\ -- "force","regression-tests","display=")) -+ 'whdf:FRD:H:', ("wait","version","help","debug","file=",\ -+ "force","regression-tests","display=","history=")) - for o,p in opts: - if o in ("-h","--help"): - usage(0) -@@ -250,6 +250,8 @@ Written by Dejan Muhamedagic - options.interactive = False - err_buf.reset_lineno() - inp_file = p -+ elif o in ("-H","--history"): -+ options.history = p - elif o in ("-w","--wait"): - user_prefs.wait = "yes" - except getopt.GetoptError,msg: -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/msg.py ---- a/shell/modules/msg.py Fri Jul 15 10:51:00 2011 +1000 -+++ b/shell/modules/msg.py Mon Jul 18 12:35:57 2011 +0200 -@@ -27,6 +27,7 @@ class ErrorBuffer(Singleton): - self.msg_list = [] - self.mode = "immediate" - self.lineno = -1 -+ self.written = {} - def buffer(self): - self.mode = "keep" - def release(self): -@@ -65,6 +66,10 @@ class ErrorBuffer(Singleton): - self.writemsg("ERROR: %s" % self.add_lineno(s)) - def warning(self,s): - self.writemsg("WARNING: %s" % self.add_lineno(s)) -+ def one_warning(self,s): -+ if not s in self.written: -+ self.written[s] = 1 -+ self.writemsg("WARNING: %s" % self.add_lineno(s)) - def info(self,s): - self.writemsg("INFO: %s" % self.add_lineno(s)) - def debug(self,s): -@@ -79,12 +84,16 @@ def common_warning(s): - err_buf.warning(s) - def common_warn(s): - err_buf.warning(s) -+def warn_once(s): -+ err_buf.one_warning(s) - def common_info(s): - err_buf.info(s) - def common_debug(s): - err_buf.debug(s) - def no_prog_err(name): - err_buf.error("%s not available, check your installation"%name) -+def no_file_err(name): -+ err_buf.error("%s does not exist"%name) - def missing_prog_warn(name): - err_buf.warning("could not find any %s on the system"%name) - def node_err(msg, node): -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/report.py ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/shell/modules/report.py Mon Jul 18 12:35:57 2011 +0200 -@@ -0,0 +1,887 @@ -+# Copyright (C) 2011 Dejan Muhamedagic -+# -+# This program is free software; you can redistribute it and/or -+# modify it under the terms of the GNU General Public -+# License as published by the Free Software Foundation; either -+# version 2.1 of the License, or (at your option) any later version. -+# -+# This software is distributed in the hope that it will be useful, -+# but WITHOUT ANY WARRANTY; without even the implied warranty of -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -+# General Public License for more details. -+# -+# You should have received a copy of the GNU General Public -+# License along with this library; if not, write to the Free Software -+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+# -+ -+import os -+import sys -+import time -+import datetime -+import copy -+import re -+import glob -+ -+from singletonmixin import Singleton -+from userprefs import Options, UserPrefs -+from cibconfig import CibFactory -+from vars import Vars, getuser -+from term import TerminalController -+from xmlutil import * -+from utils import * -+from msg import * -+from log_patterns import log_patterns, transition_patt -+_NO_PSSH = False -+try: -+ from crm_pssh import next_loglines, next_peinputs -+except: -+ _NO_PSSH = True -+ -+# -+# hb_report interface -+# -+# read hb_report generated report, show interesting stuff, search -+# through logs, get PE input files, get log slices (perhaps even -+# coloured nicely!) -+# -+ -+def mk_re_list(patt_l,repl): -+ 'Build a list of regular expressions, replace "%%" with repl' -+ l = [] -+ for re_l in patt_l: -+ l += [ x.replace("%%",repl) for x in re_l ] -+ if not repl: -+ l = [ x.replace(".*.*",".*") for x in l ] -+ return l -+ -+YEAR = time.strftime("%Y") -+def syslog_ts(s): -+ try: -+ # strptime defaults year to 1900 (sigh) -+ tm = time.strptime(' '.join([YEAR] + s.split()[0:3]),"%Y %b %d %H:%M:%S") -+ return time.mktime(tm) -+ except: -+ common_warn("malformed line: %s" % s) -+ return None -+ -+def log_seek(f, ts, endpos = False): -+ ''' -+ f is an open log. Do binary search for the timestamp. -+ Return the position of the (more or less) first line with a -+ newer time. -+ ''' -+ first = 0 -+ f.seek(0,2) -+ last = f.tell() -+ if not ts: -+ return endpos and last or first -+ badline = 0 -+ maxbadline = 10 -+ common_debug("seek ts %s" % time.ctime(ts)) -+ while first <= last: -+ # we can skip some iterations if it's about few lines -+ if abs(first-last) < 120: -+ break -+ mid = (first+last)/2 -+ f.seek(mid) -+ log_ts = get_timestamp(f) -+ if not log_ts: -+ badline += 1 -+ if badline > maxbadline: -+ common_warn("giving up on log %s" % f.name) -+ return -1 -+ first += 120 # move forward a bit -+ continue -+ if log_ts > ts: -+ last = mid-1 -+ elif log_ts < ts: -+ first = mid+1 -+ else: -+ break -+ common_debug("sought to %s" % time.ctime(log_ts)) -+ return f.tell() -+ -+def get_timestamp(f): -+ ''' -+ Get the whole line from f. The current file position is -+ usually in the middle of the line. -+ Then get timestamp and return it. -+ ''' -+ step = 30 # no line should be longer than 30 -+ cnt = 1 -+ current_pos = f.tell() -+ s = f.readline() -+ if not s: # EOF? -+ f.seek(-step, 1) # backup a bit -+ current_pos = f.tell() -+ s = f.readline() -+ while s and current_pos < f.tell(): -+ if cnt*step >= f.tell(): # at 0? -+ f.seek(0) -+ break -+ f.seek(-cnt*step, 1) -+ s = f.readline() -+ cnt += 1 -+ pos = f.tell() # save the position ... -+ s = f.readline() # get the line -+ f.seek(pos) # ... and move the cursor back there -+ if not s: # definitely EOF (probably cannot happen) -+ return None -+ return syslog_ts(s) -+ -+def is_our_log(s, node_l): -+ try: return s.split()[3] in node_l -+ except: return False -+def log2node(log): -+ return os.path.basename(os.path.dirname(log)) -+def filter(sl, log_l): -+ ''' -+ Filter list of messages to get only those from the given log -+ files list. -+ ''' -+ node_l = [log2node(x) for x in log_l if x] -+ return [x for x in sl if is_our_log(x, node_l)] -+def convert_dt(dt): -+ try: return time.mktime(dt.timetuple()) -+ except: return None -+ -+class LogSyslog(object): -+ ''' -+ Slice log, search log. -+ self.fp is an array of dicts. -+ ''' -+ def __init__(self, central_log, log_l, from_dt, to_dt): -+ self.log_l = log_l -+ self.central_log = central_log -+ self.f = {} -+ self.startpos = {} -+ self.endpos = {} -+ self.cache = {} -+ self.open_logs() -+ self.set_log_timeframe(from_dt, to_dt) -+ def open_log(self, log): -+ try: -+ self.f[log] = open(log) -+ except IOError,msg: -+ common_err("open %s: %s"%(log,msg)) -+ def open_logs(self): -+ if self.central_log: -+ self.open_log(self.central_log) -+ else: -+ for log in self.log_l: -+ self.open_log(log) -+ def set_log_timeframe(self, from_dt, to_dt): -+ ''' -+ Convert datetime to timestamps (i.e. seconds), then -+ find out start/end file positions. Logs need to be -+ already open. -+ ''' -+ self.from_ts = convert_dt(from_dt) -+ self.to_ts = convert_dt(to_dt) -+ bad_logs = [] -+ for log in self.f: -+ f = self.f[log] -+ start = log_seek(f, self.from_ts) -+ end = log_seek(f, self.to_ts, endpos = True) -+ if start == -1 or end == -1: -+ bad_logs.append(log) -+ else: -+ self.startpos[f] = start -+ self.endpos[f] = end -+ for log in bad_logs: -+ del self.f[log] -+ self.log_l.remove(log) -+ def get_match_line(self, f, patt): -+ ''' -+ Get first line from f that matches re_s, but is not -+ behind endpos[f]. -+ ''' -+ while f.tell() < self.endpos[f]: -+ fpos = f.tell() -+ s = f.readline().rstrip() -+ if not patt or patt.search(s): -+ return s,fpos -+ return '',-1 -+ def single_log_list(self, f, patt): -+ l = [] -+ while True: -+ s = self.get_match_line(f, patt)[0] -+ if not s: -+ return l -+ l.append(s) -+ return l -+ def search_logs(self, log_l, re_s = ''): -+ ''' -+ Search logs for re_s and sort by time. -+ ''' -+ patt = None -+ if re_s: -+ patt = re.compile(re_s) -+ # if there's central log, there won't be merge -+ if self.central_log: -+ fl = [ self.f[f] for f in self.f ] -+ else: -+ fl = [ self.f[f] for f in self.f if self.f[f].name in log_l ] -+ for f in fl: -+ f.seek(self.startpos[f]) -+ # get head lines of all nodes -+ top_line = [ self.get_match_line(x, patt)[0] for x in fl ] -+ top_line_ts = [] -+ rm_idx_l = [] -+ # calculate time stamps for head lines -+ for i in range(len(top_line)): -+ if not top_line[i]: -+ rm_idx_l.append(i) -+ else: -+ top_line_ts.append(syslog_ts(top_line[i])) -+ # remove files with no matches found -+ rm_idx_l.reverse() -+ for i in rm_idx_l: -+ del fl[i],top_line[i] -+ common_debug("search <%s> in %s" % (re_s, [ f.name for f in fl ])) -+ if len(fl) == 0: # nothing matched ? -+ return [] -+ if len(fl) == 1: -+ # no need to merge if there's only one log -+ return [top_line[0]] + self.single_log_list(fl[0],patt) -+ # search through multiple logs, merge sorted by time -+ l = [] -+ first = 0 -+ while True: -+ for i in range(len(fl)): -+ try: -+ if i == first: -+ continue -+ if top_line_ts[i] and top_line_ts[i] < top_line_ts[first]: -+ first = i -+ except: pass -+ if not top_line[first]: -+ break -+ l.append(top_line[first]) -+ top_line[first] = self.get_match_line(fl[first], patt)[0] -+ if not top_line[first]: -+ top_line_ts[first] = time.time() -+ else: -+ top_line_ts[first] = syslog_ts(top_line[first]) -+ return l -+ def get_matches(self, re_l, log_l = []): -+ ''' -+ Return a list of log messages which -+ match one of the regexes in re_l. -+ ''' -+ if not log_l: -+ log_l = self.log_l -+ re_s = '|'.join(re_l) -+ return filter(self.search_logs(log_l, re_s), log_l) -+ # caching is not ready! -+ # gets complicated because of different time frames -+ # (TODO) -+ #if not re_s: # just list logs -+ # return filter(self.search_logs(log_l), log_l) -+ #if re_s not in self.cache: # cache regex search -+ # self.cache[re_s] = self.search_logs(log_l, re_s) -+ #return filter(self.cache[re_s], log_l) -+ -+def human_date(dt): -+ 'Some human date representation. Date defaults to now.' -+ if not dt: -+ dt = datetime.datetime.now() -+ # drop microseconds -+ return re.sub("[.].*","","%s %s" % (dt.date(),dt.time())) -+ -+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) -+ if not r: -+ return None -+ if not a or (a[0] <= int(r.group(1)) <= a[1]): -+ return pe_f -+ return None -+ -+def read_log_info(log): -+ 'Read .info and return logfile and next pos' -+ s = file2str("%s.info" % log) -+ try: -+ logf,pos = s.split() -+ return logf, int(pos) -+ except: -+ warn_once("hb_report too old, you need to update cluster-glue") -+ return '',-1 -+ -+def update_loginfo(rptlog, logfile, oldpos, appended_file): -+ 'Update .info with new next pos' -+ newpos = oldpos + os.stat(appended_file).st_size -+ try: -+ f = open("%s.info" % rptlog, "w") -+ f.write("%s %d\n" % (logfile, newpos)) -+ f.close() -+ except IOError, msg: -+ common_err("couldn't the update %s.info: %s" % (rptlog, msg)) -+ -+class Report(Singleton): -+ ''' -+ A hb_report class. -+ ''' -+ live_recent = 6*60*60 # recreate live hb_report once every 6 hours -+ short_live_recent = 60 # update once a minute -+ nodecolors = ( -+ "NORMAL", "GREEN", "CYAN", "MAGENTA", "YELLOW", "WHITE", "BLUE", "RED" -+ ) -+ def __init__(self): -+ self.source = None -+ self.loc = None -+ self.ready = False -+ self.from_dt = None -+ self.to_dt = None -+ self.detail = 0 -+ self.nodecolor = {} -+ self.logobj = None -+ self.desc = None -+ self.log_l = [] -+ self.central_log = None -+ self.cibgrp_d = {} -+ self.cibrsc_l = [] -+ self.cibnode_l = [] -+ self.setnodes = [] -+ self.outdir = os.path.join(vars.report_cache,"psshout") -+ self.errdir = os.path.join(vars.report_cache,"pssherr") -+ self.last_live_update = 0 -+ def error(self, s): -+ common_err("%s: %s" % (self.source, s)) -+ def warn(self, s): -+ common_warn("%s: %s" % (self.source, s)) -+ def rsc_list(self): -+ return self.cibgrp_d.keys() + self.cibrsc_l -+ def node_list(self): -+ 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")] -+ def unpack_report(self, tarball): -+ ''' -+ Unpack hb_report tarball. -+ Don't unpack if the directory already exists! -+ ''' -+ bfname = os.path.basename(tarball) -+ parentdir = os.path.dirname(tarball) -+ common_debug("tarball: %s, in dir: %s" % (bfname,parentdir)) -+ if bfname.endswith(".tar.bz2"): -+ loc = tarball.replace(".tar.bz2","") -+ tar_unpack_option = "j" -+ elif bfname.endswith(".tar.gz"): # hmm, must be ancient -+ loc = tarball.replace(".tar.gz","") -+ tar_unpack_option = "z" -+ else: -+ self.error("this doesn't look like a report tarball") -+ return None -+ if os.path.isdir(loc): -+ return loc -+ cwd = os.getcwd() -+ try: -+ os.chdir(parentdir) -+ except OSError,msg: -+ self.error(msg) -+ return None -+ rc = ext_cmd_nosudo("tar -x%s < %s" % (tar_unpack_option,bfname)) -+ if self.source == "live": -+ os.remove(bfname) -+ os.chdir(cwd) -+ if rc != 0: -+ return None -+ return loc -+ def get_nodes(self): -+ return [ os.path.basename(p) -+ for p in os.listdir(self.loc) -+ if os.path.isdir(os.path.join(self.loc, p)) and -+ os.path.isfile(os.path.join(self.loc, p, "cib.txt")) -+ ] -+ def check_nodes(self): -+ 'Verify if the nodes in cib match the nodes in the report.' -+ nl = self.get_nodes() -+ if not nl: -+ self.error("no nodes in report") -+ return False -+ for n in self.cibnode_l: -+ if not (n in nl): -+ self.warn("node %s not in report" % n) -+ else: -+ nl.remove(n) -+ if nl: -+ self.warn("strange, extra node(s) %s in report" % ','.join(nl)) -+ return True -+ def check_report(self): -+ ''' -+ Check some basic properties of the report. -+ ''' -+ if not self.loc: -+ return False -+ if not os.access(self.desc, os.F_OK): -+ self.error("no description file in the report") -+ return False -+ if not self.check_nodes(): -+ return False -+ return True -+ def _live_loc(self): -+ return os.path.join(vars.report_cache,"live") -+ def is_last_live_recent(self): -+ ''' -+ Look at the last live hb_report. If it's recent enough, -+ return True. Return True also if self.to_dt is not empty -+ (not an open end report). -+ ''' -+ try: -+ last_ts = os.stat(self.desc).st_mtime -+ return (time.time() - last_ts <= self.live_recent) -+ except Exception, msg: -+ self.warn(msg) -+ self.warn("strange, couldn't stat %s" % self.desc) -+ return False -+ def find_node_log(self, node): -+ p = os.path.join(self.loc, node) -+ if is_log(os.path.join(p, "ha-log.txt")): -+ return os.path.join(p, "ha-log.txt") -+ elif is_log(os.path.join(p, "messages")): -+ return os.path.join(p, "messages") -+ else: -+ return None -+ def find_central_log(self): -+ 'Return common log, if found.' -+ central_log = os.path.join(self.loc, "ha-log.txt") -+ if is_log(central_log): -+ logf, pos = read_log_info(central_log) -+ if logf.startswith("synthetic"): -+ # not really central log -+ return -+ common_debug("found central log %s" % logf) -+ self.central_log = central_log -+ def find_logs(self): -+ 'Return a list of logs found (one per node).' -+ l = [] -+ for node in self.get_nodes(): -+ log = self.find_node_log(node) -+ if log: -+ l.append(log) -+ else: -+ self.warn("no log found for node %s" % node) -+ self.log_l = l -+ def append_newlogs(self, a): -+ ''' -+ Append new logs fetched from nodes. -+ ''' -+ if not os.path.isdir(self.outdir): -+ return -+ for node,rptlog,logfile,nextpos in a: -+ fl = glob.glob("%s/*%s*" % (self.outdir,node)) -+ if not fl: -+ continue -+ append_file(rptlog,fl[0]) -+ update_loginfo(rptlog, logfile, nextpos, fl[0]) -+ def unpack_new_peinputs(self, a): -+ ''' -+ Untar PE inputs fetched from nodes. -+ ''' -+ if not os.path.isdir(self.outdir): -+ return -+ for node,pe_l in a: -+ fl = glob.glob("%s/*%s*" % (self.outdir,node)) -+ if not fl: -+ 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): -+ ''' -+ Get a list of pe inputs appearing in logs. -+ ''' -+ if not os.path.isdir(self.outdir): -+ return [] -+ l = [] -+ for node,rptlog,logfile,nextpos in a: -+ node_l = [] -+ fl = glob.glob("%s/*%s*" % (self.outdir,node)) -+ if not fl: -+ continue -+ for s in file2list(fl[0]): -+ r = re.search(transition_patt[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]) -+ return l -+ def update_live(self): -+ ''' -+ Update the existing live report, if it's older than -+ self.short_live_recent: -+ - append newer logs -+ - get new PE inputs -+ ''' -+ if (time.time() - self.last_live_update) <= self.short_live_recent: -+ return True -+ if _NO_PSSH: -+ warn_once("pssh not installed, slow live updates ahead") -+ return False -+ a = [] -+ common_info("fetching new logs, please wait ...") -+ for rptlog in self.log_l: -+ node = log2node(rptlog) -+ logf, pos = read_log_info(rptlog) -+ if logf: -+ a.append([node, rptlog, logf, pos]) -+ if not a: -+ common_info("no elligible logs found :(") -+ return False -+ rmdir_r(self.outdir) -+ rmdir_r(self.errdir) -+ rc1 = next_loglines(a, self.outdir, self.errdir) -+ self.append_newlogs(a) -+ pe_l = self.find_new_peinputs(a) -+ rmdir_r(self.outdir) -+ rmdir_r(self.errdir) -+ rc2 = True -+ if pe_l: -+ rc2 = next_peinputs(pe_l, self.outdir, self.errdir) -+ self.unpack_new_peinputs(pe_l) -+ self.logobj = None -+ rmdir_r(self.outdir) -+ rmdir_r(self.errdir) -+ self.last_live_update = time.time() -+ return (rc1 and rc2) -+ def get_live_report(self): -+ acquire_lock(vars.report_cache) -+ loc = self.new_live_hb_report() -+ release_lock(vars.report_cache) -+ return loc -+ def manage_live_report(self, force = False): -+ ''' -+ Update or create live report. -+ ''' -+ d = self._live_loc() -+ if not d or not os.path.isdir(d): -+ return self.get_live_report() -+ if not self.loc: -+ # the live report is there, but we were just invoked -+ self.loc = d -+ self.report_setup() -+ if not force and self.is_last_live_recent(): -+ acquire_lock(vars.report_cache) -+ rc = self.update_live() -+ release_lock(vars.report_cache) -+ if rc: -+ return self._live_loc() -+ return self.get_live_report() -+ def new_live_hb_report(self): -+ ''' -+ Run hb_report to get logs now. -+ ''' -+ d = self._live_loc() -+ rmdir_r(d) -+ tarball = "%s.tar.bz2" % d -+ to_option = "" -+ if self.to_dt: -+ to_option = "-t '%s'" % human_date(self.to_dt) -+ nodes_option = "" -+ if self.setnodes: -+ nodes_option = "'-n %s'" % ' '.join(self.setnodes) -+ if ext_cmd_nosudo("mkdir -p %s" % os.path.dirname(d)) != 0: -+ return None -+ common_info("retrieving information from cluster nodes, please wait ...") -+ rc = ext_cmd_nosudo("hb_report -f '%s' %s %s %s" % -+ (self.from_dt.ctime(), to_option, nodes_option, d)) -+ if rc != 0: -+ if os.path.isfile(tarball): -+ self.warn("hb_report thinks it failed, proceeding anyway") -+ else: -+ self.error("hb_report failed") -+ return None -+ self.last_live_update = time.time() -+ return self.unpack_report(tarball) -+ def reset_period(self): -+ self.from_dt = None -+ self.to_dt = None -+ def set_source(self,src): -+ 'Set our source.' -+ self.source = src -+ def set_period(self,from_dt,to_dt): -+ ''' -+ Set from/to_dt. -+ ''' -+ common_debug("setting report times: <%s> - <%s>" % (from_dt,to_dt)) -+ if not self.from_dt: -+ self.from_dt = from_dt -+ self.to_dt = to_dt -+ elif self.source != "live": -+ if self.from_dt > from_dt: -+ self.error("from time %s not within report" % from_dt) -+ return False -+ if to_dt and self.to_dt < to_dt: -+ self.error("end time %s not within report" % to_dt) -+ return False -+ self.from_dt = from_dt -+ self.to_dt = to_dt -+ else: -+ need_ref = (self.from_dt > from_dt or \ -+ (to_dt and self.to_dt < to_dt)) -+ self.from_dt = from_dt -+ self.to_dt = to_dt -+ if need_ref: -+ self.refresh_source(force = True) -+ if self.logobj: -+ self.logobj.set_log_timeframe(self.from_dt, self.to_dt) -+ def set_detail(self,detail_lvl): -+ ''' -+ Set the detail level. -+ ''' -+ self.detail = int(detail_lvl) -+ def set_nodes(self,*args): -+ ''' -+ Allow user to set the node list (necessary if the host is -+ not part of the cluster). -+ ''' -+ self.setnodes = args -+ def read_cib(self): -+ ''' -+ Get some information from the report's CIB (node list, -+ resource list, groups). If "live" and not central log, -+ then use cibadmin. -+ ''' -+ nl = self.get_nodes() -+ if not nl: -+ return -+ if self.source == "live" and not self.central_log: -+ doc = cibdump2doc() -+ else: -+ doc = file2doc(os.path.join(self.loc,nl[0],"cib.xml")) -+ if not doc: -+ return # no cib? -+ try: conf = doc.getElementsByTagName("configuration")[0] -+ except: # bad cib? -+ return -+ self.cibrsc_l = [ x.getAttribute("id") -+ for x in conf.getElementsByTagName("primitive") ] -+ self.cibnode_l = [ x.getAttribute("uname") -+ for x in conf.getElementsByTagName("node") ] -+ for grp in conf.getElementsByTagName("group"): -+ self.cibgrp_d[grp.getAttribute("id")] = get_rsc_children_ids(grp) -+ def set_node_colors(self): -+ i = 0 -+ for n in self.cibnode_l: -+ self.nodecolor[n] = self.nodecolors[i] -+ i = (i+1) % len(self.nodecolors) -+ def report_setup(self): -+ if not self.loc: -+ return -+ self.desc = os.path.join(self.loc,"description.txt") -+ self.find_logs() -+ self.find_central_log() -+ self.read_cib() -+ self.set_node_colors() -+ self.logobj = None -+ def prepare_source(self): -+ ''' -+ Unpack a hb_report tarball. -+ For "live", create an ad-hoc hb_report and unpack it -+ somewhere in the cache area. -+ Parse the period. -+ ''' -+ if self.ready and self.source != "live": -+ return True -+ if self.source == "live": -+ self.loc = self.manage_live_report() -+ elif os.path.isfile(self.source): -+ self.loc = self.unpack_report(self.source) -+ elif os.path.isdir(self.source): -+ self.loc = self.source -+ if not self.loc: -+ return False -+ self.report_setup() -+ self.ready = self.check_report() -+ return self.ready -+ def refresh_source(self, force = False): -+ ''' -+ Refresh report from live. -+ ''' -+ if self.source != "live": -+ self.error("refresh not supported") -+ return False -+ self.loc = self.manage_live_report(force) -+ self.report_setup() -+ self.ready = self.check_report() -+ return self.ready -+ def get_patt_l(self,type): -+ ''' -+ get the list of patterns for this type, up to and -+ including current detail level -+ ''' -+ if not type in log_patterns: -+ common_error("%s not featured in log patterns" % type) -+ return None -+ return log_patterns[type][0:self.detail+1] -+ def build_re(self,type,args): -+ ''' -+ Prepare a regex string for the type and args. -+ For instance, "resource" and rsc1, rsc2, ... -+ ''' -+ patt_l = self.get_patt_l(type) -+ if not patt_l: -+ return None -+ if not args: -+ re_l = mk_re_list(patt_l,"") -+ else: -+ re_l = mk_re_list(patt_l,r'(%s)\W' % "|".join(args)) -+ return re_l -+ def disp(self, s): -+ 'color output' -+ a = s.split() -+ try: clr = self.nodecolor[a[3]] -+ except: return s -+ return termctrl.render("${%s}%s${NORMAL}" % (clr,s)) -+ def show_logs(self, log_l = [], re_l = []): -+ ''' -+ Print log lines, either matched by re_l or all. -+ ''' -+ if not log_l: -+ log_l = self.log_l -+ if not self.central_log and not log_l: -+ self.error("no logs found") -+ return -+ if not self.logobj: -+ self.logobj = LogSyslog(self.central_log, log_l, \ -+ self.from_dt, self.to_dt) -+ l = self.logobj.get_matches(re_l, log_l) -+ if not options.batch and sys.stdout.isatty(): -+ page_string('\n'.join([ self.disp(x) for x in l ])) -+ else: # raw output -+ try: # in case user quits the next prog in pipe -+ for s in l: print s -+ except IOError, msg: -+ if not ("Broken pipe" in msg): -+ common_err(msg) -+ def match_args(self, cib_l, args): -+ for a in args: -+ a_clone = re.sub(r':.*', '', a) -+ if not (a in cib_l) and not (a_clone in cib_l): -+ self.warn("%s not found in report, proceeding anyway" % a) -+ def get_desc_line(self,fld): -+ try: -+ f = open(self.desc) -+ except IOError,msg: -+ common_err("open %s: %s"%(self.desc,msg)) -+ return -+ for s in f: -+ if s.startswith("%s: " % fld): -+ f.close() -+ s = s.replace("%s: " % fld,"").rstrip() -+ return s -+ f.close() -+ def info(self): -+ ''' -+ Print information about the source. -+ ''' -+ if not self.prepare_source(): -+ return False -+ print "Source: %s" % self.source -+ if self.source != "live": -+ print "Created:", self.get_desc_line("Date") -+ print "By:", self.get_desc_line("By") -+ print "Period: %s - %s" % \ -+ ((self.from_dt and human_date(self.from_dt) or "start"), -+ (self.to_dt and human_date(self.to_dt) or "end")) -+ print "Nodes:",' '.join(self.cibnode_l) -+ print "Groups:",' '.join(self.cibgrp_d.keys()) -+ print "Resources:",' '.join(self.cibrsc_l) -+ def latest(self): -+ ''' -+ Get the very latest cluster events, basically from the -+ latest transition. -+ Some transitions may be empty though. -+ ''' -+ def events(self): -+ ''' -+ Show all events. -+ ''' -+ if not self.prepare_source(): -+ return False -+ all_re_l = self.build_re("resource",self.cibrsc_l) + \ -+ self.build_re("node",self.cibnode_l) -+ if not all_re_l: -+ self.error("no resources or nodes found") -+ return False -+ self.show_logs(re_l = all_re_l) -+ def resource(self,*args): -+ ''' -+ Show resource relevant logs. -+ ''' -+ if not self.prepare_source(): -+ return False -+ # expand groups (if any) -+ expanded_l = [] -+ for a in args: -+ if a in self.cibgrp_d: -+ expanded_l += self.cibgrp_d[a] -+ else: -+ expanded_l.append(a) -+ self.match_args(self.cibrsc_l,expanded_l) -+ rsc_re_l = self.build_re("resource",expanded_l) -+ if not rsc_re_l: -+ return False -+ self.show_logs(re_l = rsc_re_l) -+ def node(self,*args): -+ ''' -+ Show node relevant logs. -+ ''' -+ if not self.prepare_source(): -+ return False -+ self.match_args(self.cibnode_l,args) -+ node_re_l = self.build_re("node",args) -+ if not node_re_l: -+ return False -+ self.show_logs(re_l = node_re_l) -+ def log(self,*args): -+ ''' -+ Show logs for a node or all nodes. -+ ''' -+ if not self.prepare_source(): -+ return False -+ if not args: -+ self.show_logs() -+ else: -+ l = [] -+ for n in args: -+ if n not in self.cibnode_l: -+ self.warn("%s: no such node" % n) -+ continue -+ l.append(self.find_node_log(n)) -+ 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. -+ ''' -+ if not self.prepare_source(): -+ return [] -+ if not isinstance(a,(tuple,list)) and 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) -+ def dotlist(self, a = []): -+ return self._file_list("dot", a) -+ def find_file(self, f): -+ return file_find_by_name(self.loc, f) -+ -+vars = Vars.getInstance() -+options = Options.getInstance() -+termctrl = TerminalController.getInstance() -+cib_factory = CibFactory.getInstance() -+crm_report = Report.getInstance() -+# vim:ts=4:sw=4:et: -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/ui.py.in ---- a/shell/modules/ui.py.in Fri Jul 15 10:51:00 2011 +1000 -+++ b/shell/modules/ui.py.in Mon Jul 18 12:35:57 2011 +0200 -@@ -27,6 +27,7 @@ from vars import Vars - from levels import Levels - from cibconfig import mkset_obj, CibFactory - from cibstatus import CibStatus -+from report import Report - from template import LoadTemplate - from cliformat import nvpairs2list - from ra import * -@@ -1390,6 +1391,7 @@ cluster. - self.cmd_table["cib"] = CibShadow - self.cmd_table["cibstatus"] = StatusMgmt - self.cmd_table["template"] = Template -+ self.cmd_table["history"] = History - self.cmd_table["_test"] = (self.check_structure,(0,0),1,0) - self.cmd_table["_regtest"] = (self.regression_testing,(1,1),1,0) - self.cmd_table["_objects"] = (self.showobjects,(0,0),1,0) -@@ -1661,6 +1663,227 @@ cluster. - self.commit("commit") - cib_factory.reset() - -+class History(UserInterface): -+ ''' -+ The history class -+ ''' -+ lvl_name = "history" -+ desc_short = "CRM cluster history" -+ desc_long = """ -+The history level. -+ -+Examine Pacemaker's history: node and resource events, logs. -+""" -+ def __init__(self): -+ UserInterface.__init__(self) -+ self.cmd_table["source"] = (self.source,(1,1),1,0) -+ self.cmd_table["limit"] = (self.limit,(1,2),1,0) -+ self.cmd_table["refresh"] = (self.refresh,(0,1),1,0) -+ self.cmd_table["detail"] = (self.detail,(1,1),1,0) -+ self.cmd_table["setnodes"] = (self.setnodes,(1,),1,0) -+ self.cmd_table["info"] = (self.info,(0,0),1,0) -+ self.cmd_table["latest"] = (self.latest,(0,0),1,0) -+ 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,(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") -+ def parse_time(self, t): -+ ''' -+ Try to make sense of the user provided time spec. -+ Use dateutil if available, otherwise strptime. -+ Return the datetime value. -+ ''' -+ try: -+ import dateutil.parser -+ dt = dateutil.parser.parse(t) -+ except ValueError,msg: -+ common_err("%s: %s" % (t,msg)) -+ return None -+ except ImportError,msg: -+ import datetime -+ try: -+ tm = time.strptime(t) -+ dt = datetime.datetime(*tm[0:7]) -+ except ValueError,msg: -+ common_err("no dateutil, please provide times as printed by date(1)") -+ return None -+ return dt -+ def _set_period(self,from_time,to_time = ''): -+ ''' -+ parse time specs and set period -+ ''' -+ from_dt = self.parse_time(from_time) -+ if not from_dt: -+ return False -+ to_dt = None -+ if to_time: -+ to_dt = self.parse_time(to_time) -+ if not to_dt: -+ return False -+ if to_dt and to_dt <= from_dt: -+ common_err("%s - %s: bad period" % from_time, to_time) -+ return False -+ common_debug("setting period to <%s>:<%s>" % (from_dt,to_dt)) -+ return crm_report.set_period(from_dt,to_dt) -+ def _check_source(self,src): -+ 'a (very) quick source check' -+ if src == "live" or os.path.isfile(src) or os.path.isdir(src): -+ return True -+ else: -+ common_error("source %s doesn't exist" % src) -+ return False -+ def _set_source(self,src,live_from_time = None): -+ ''' -+ Have the last history source survive the History -+ and Report instances -+ ''' -+ common_debug("setting source to %s" % src) -+ if not self._check_source(src): -+ return False -+ crm_report.set_source(src) -+ options.history = src -+ options.report_to_time = '' -+ rc = True -+ if src == "live": -+ options.report_from_time = time.ctime(live_from_time and \ -+ live_from_time or (time.time() - 60*60)) -+ rc = self._set_period(\ -+ options.report_from_time, options.report_to_time) -+ else: -+ options.report_from_time = '' -+ return rc -+ def source(self,cmd,src = None): -+ "usage: source {||live}" -+ if src != options.history: -+ return self._set_source(src) -+ def limit(self,cmd,from_time,to_time = ''): -+ "usage: limit []" -+ if options.report_from_time != from_time or \ -+ options.report_to_time != to_time: -+ if not self._set_period(from_time, to_time): -+ return False -+ options.report_from_time = from_time -+ options.report_to_time = to_time -+ def refresh(self, cmd, 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": -+ syntax_err((cmd,force), context = 'refresh') -+ return False -+ force = True -+ return crm_report.refresh_source(force) -+ def detail(self,cmd,detail_lvl): -+ "usage: detail " -+ detail_num = convert2ints(detail_lvl) -+ if not (isinstance(detail_num,int) and int(detail_num) >= 0): -+ bad_usage(cmd,detail_lvl) -+ return False -+ return crm_report.set_detail(detail_lvl) -+ def setnodes(self,cmd,*args): -+ "usage: setnodes [ ...]" -+ if options.history != "live": -+ common_info("setting nodes not necessary for existing reports, proceeding anyway") -+ return crm_report.set_nodes(*args) -+ def info(self,cmd): -+ "usage: info" -+ return crm_report.info() -+ def latest(self,cmd): -+ "usage: latest" -+ try: -+ prev_level = levels.previous().myname() -+ except: -+ prev_level = '' -+ if prev_level != "cibconfig": -+ common_err("%s is available only when invoked from configure" % cmd) -+ return False -+ ts = cib_factory.last_commit_at() -+ if not ts: -+ common_err("no last commit time found") -+ return False -+ if not wait4dc("transition", not options.batch): -+ return False -+ self._set_source("live", ts) -+ crm_report.refresh_source() -+ return crm_report.events() -+ def resource(self,cmd,*args): -+ "usage: resource [ ...]" -+ return crm_report.resource(*args) -+ def node(self,cmd,*args): -+ "usage: node [ ...]" -+ return crm_report.node(*args) -+ def log(self,cmd,*args): -+ "usage: log [ ...]" -+ return crm_report.log(*args) -+ def ptest(self, nograph, scores, utilization, actions, verbosity): -+ 'Send a decompressed self.pe_file to ptest' -+ try: -+ f = open(self.pe_file) -+ except IOError,msg: -+ common_err("open: %s"%msg) -+ return False -+ 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 [{|} ...] -+ peinputs get [{|} ...] -+ peinputs show [] [nograph] [v...] [scores] [actions] [utilization] -+ peinputs showdot []""" -+ if subcmd in ("get","list"): -+ if args: -+ l = [] -+ for s in args: -+ a = convert2ints(s.split(':')) -+ if 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) -+ else: -+ print '\n'.join(l) -+ elif subcmd in ("show","showdot"): -+ 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": -+ if not user_prefs.dotty: -+ common_err("install graphviz to draw transition graphs") -+ return False -+ l = crm_report.dotlist(n) -+ else: -+ l = crm_report.pelist(n) -+ if len(l) < abs(idx): -+ common_err("pe input or dot file not found") -+ return False -+ if subcmd == "show": -+ self.pe_file = l[idx] -+ return ptestlike(self.ptest,'vv',"%s %s" % \ -+ (cmd, subcmd), *args[startarg:]) -+ else: -+ show_dot_graph(l[idx]) -+ else: -+ bad_usage(cmd,' '.join(subcmd,args)) -+ return False -+ - class TopLevel(UserInterface): - ''' - The top level. -@@ -1681,6 +1904,7 @@ class TopLevel(UserInterface): - self.cmd_table['configure'] = CibConfig - self.cmd_table['node'] = NodeMgmt - self.cmd_table['options'] = CliOptions -+ self.cmd_table['history'] = History - self.cmd_table['status'] = (self.status,(0,5),0,0) - self.cmd_table['ra'] = RA - setup_aliases(self) -@@ -1726,5 +1950,5 @@ vars = Vars.getInstance() - levels = Levels.getInstance(TopLevel) - cib_status = CibStatus.getInstance() - cib_factory = CibFactory.getInstance() -- -+crm_report = Report.getInstance() - # vim:ts=4:sw=4:et: -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/userprefs.py ---- a/shell/modules/userprefs.py Fri Jul 15 10:51:00 2011 +1000 -+++ b/shell/modules/userprefs.py Mon Jul 18 12:35:57 2011 +0200 -@@ -18,6 +18,7 @@ - from os import getenv - import subprocess - import sys -+import datetime, time - - from singletonmixin import Singleton - from term import TerminalController -@@ -26,6 +27,10 @@ class Options(Singleton): - interactive = False - batch = False - regression_tests = False -+ history = "live" -+ # now minus one hour -+ report_from_time = time.ctime(time.time()-60*60) -+ report_to_time = "" - - options = Options.getInstance() - termctrl = TerminalController.getInstance() -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/utils.py ---- a/shell/modules/utils.py Fri Jul 15 10:51:00 2011 +1000 -+++ b/shell/modules/utils.py Mon Jul 18 12:35:57 2011 +0200 -@@ -22,6 +22,7 @@ import subprocess - import re - import glob - import time -+import shutil - - from userprefs import Options, UserPrefs - from vars import Vars -@@ -177,6 +178,29 @@ def str2file(s,fname): - f.write(s) - f.close() - return True -+def file2str(fname, noerr = True): -+ ''' -+ Read a one line file into a string, strip whitespace around. -+ ''' -+ try: f = open(fname,"r") -+ except IOError, msg: -+ if not noerr: -+ common_err(msg) -+ return None -+ s = f.readline() -+ f.close() -+ return s.strip() -+def file2list(fname): -+ ''' -+ Read a file into a list (newlines dropped). -+ ''' -+ try: f = open(fname,"r") -+ except IOError, msg: -+ common_err(msg) -+ return None -+ l = ''.join(f).split('\n') -+ f.close() -+ return l - - def is_filename_sane(name): - if re.search("['`/#*?$\[\]]",name): -@@ -203,9 +227,70 @@ def ext_cmd(cmd): - print ".EXT", cmd - return subprocess.call(add_sudo(cmd), shell=True) - --def get_stdout(cmd, stderr_on = True): -+def rmdir_r(d): -+ if d and os.path.isdir(d): -+ shutil.rmtree(d) -+ -+_LOCKDIR = ".lockdir" -+_PIDF = "pid" -+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, err: -+ if err.errno == os.errno.ESRCH: -+ common_info("history: removing stale lock") -+ rmdir_r(os.path.join(dir,_LOCKDIR)) -+ else: -+ common_err("%s: %s" % (_LOCKDIR,err.message)) -+def acquire_lock(dir): -+ check_locker(dir) -+ while True: -+ try: -+ os.mkdir(os.path.join(dir,_LOCKDIR)) -+ str2file("%d" % os.getpid(),os.path.join(dir,_LOCKDIR,_PIDF)) -+ return True -+ except OSError, e: -+ if e.errno != os.errno.EEXIST: -+ common_err("%s" % e.message) -+ return False -+ time.sleep(0.1) -+ continue -+ else: -+ return False -+def release_lock(dir): -+ rmdir_r(os.path.join(dir,_LOCKDIR)) -+ -+#def ext_cmd_nosudo(cmd): -+# if options.regression_tests: -+# print ".EXT", cmd -+# return subprocess.call(cmd, shell=True) -+ -+def ext_cmd_nosudo(cmd): -+ if options.regression_tests: -+ print ".EXT", cmd -+ proc = subprocess.Popen(cmd, shell = True, -+ stdout = subprocess.PIPE, -+ stderr = subprocess.PIPE) -+ (outp,err_outp) = proc.communicate() -+ proc.wait() -+ rc = proc.returncode -+ if rc != 0: -+ print outp -+ print err_outp -+ return rc -+ -+def get_stdout(cmd, input_s = None, stderr_on = True): - ''' -- Run a cmd, return stdin output. -+ Run a cmd, return stdout output. -+ Optional input string "input_s". - stderr_on controls whether to show output which comes on stderr. - ''' - if stderr_on: -@@ -213,8 +298,9 @@ def get_stdout(cmd, stderr_on = True): - else: - stderr = subprocess.PIPE - proc = subprocess.Popen(cmd, shell = True, \ -+ stdin = subprocess.PIPE, \ - stdout = subprocess.PIPE, stderr = stderr) -- outp = proc.communicate()[0] -+ outp = proc.communicate(input_s)[0] - proc.wait() - outp = outp.strip() - return outp -@@ -223,9 +309,27 @@ def stdout2list(cmd, stderr_on = True): - Run a cmd, fetch output, return it as a list of lines. - stderr_on controls whether to show output which comes on stderr. - ''' -- s = get_stdout(add_sudo(cmd), stderr_on) -+ s = get_stdout(add_sudo(cmd), stderr_on = stderr_on) - return s.split('\n') - -+def append_file(dest,src): -+ 'Append src to dest' -+ try: -+ dest_f = open(dest,"a") -+ except IOError,msg: -+ common_err("open %s: %s" % (dest, msg)) -+ return False -+ try: -+ f = open(src) -+ except IOError,msg: -+ common_err("open %s: %s" % (src, msg)) -+ dest_f.close() -+ return False -+ dest_f.write(''.join(f)) -+ f.close() -+ dest_f.close() -+ return True -+ - def wait4dc(what = "", show_progress = True): - ''' - Wait for the DC to get into the S_IDLE state. This should be -@@ -283,6 +387,38 @@ def wait4dc(what = "", show_progress = T - if cnt % 5 == 0: - sys.stderr.write(".") - -+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 = "ptest -X" -+ 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 user_prefs.dotty and not nograph: -+ fd,dotfile = mkstemp() -+ ptest = "%s -D %s" % (ptest,dotfile) -+ else: -+ dotfile = None -+ # ptest prints to stderr -+ if actions: -+ ptest = "%s 2>&1 | %s" % (ptest, actions_filter) -+ print get_stdout(ptest, input_s = graph_s) -+ #page_string(get_stdout(ptest, input_s = graph_s)) -+ if dotfile: -+ show_dot_graph(dotfile) -+ vars.tmpfiles.append(dotfile) -+ else: -+ if not nograph: -+ common_info("install graphviz to see a transition graph") -+ return True -+ - def is_id_valid(id): - """ - Verify that the id follows the definition: -@@ -299,6 +435,57 @@ def check_filename(fname): - fname_re = "^[^/]+$" - return re.match(fname_re,id) - -+def check_range(a): -+ """ -+ Verify that the integer range in list a is valid. -+ """ -+ if len(a) != 2: -+ return False -+ if not isinstance(a[0],int) or not isinstance(a[1],int): -+ return False -+ return (int(a[0]) < int(a[1])) -+ -+def sort_by_mtime(l): -+ 'Sort a (small) list of files by time mod.' -+ l2 = [(os.stat(x).st_mtime, x) for x in l] -+ l2.sort() -+ return [x[1] for x in l2] -+def dirwalk(dir): -+ "walk a directory tree, using a generator" -+ # http://code.activestate.com/recipes/105873/ -+ for f in os.listdir(dir): -+ fullpath = os.path.join(dir,f) -+ if os.path.isdir(fullpath) and not os.path.islink(fullpath): -+ for x in dirwalk(fullpath): # recurse into subdir -+ yield x -+ else: -+ yield fullpath -+def file_find_by_name(dir, fname): -+ 'Find a file within a tree matching fname.' -+ if not dir: -+ common_err("cannot dirwalk nothing!") -+ return None -+ if not fname: -+ common_err("file to find not provided") -+ return None -+ for f in dirwalk(dir): -+ if os.path.basename(f) == fname: -+ return f -+ return None -+ -+def convert2ints(l): -+ """ -+ Convert a list of strings (or a string) to a list of ints. -+ All strings must be ints, otherwise conversion fails and None -+ is returned! -+ """ -+ try: -+ if isinstance(l,(tuple,list)): -+ return [int(x) for x in l] -+ else: # it's a string then -+ return int(l) -+ except: return None -+ - def is_process(s): - proc = subprocess.Popen("ps -e -o pid,command | grep -qs '%s'" % s, \ - shell=True, stdout=subprocess.PIPE) -diff -r 9609937061d7 -r b694b75d2e33 shell/modules/vars.py.in ---- a/shell/modules/vars.py.in Fri Jul 15 10:51:00 2011 +1000 -+++ b/shell/modules/vars.py.in Mon Jul 18 12:35:57 2011 +0200 -@@ -186,6 +186,7 @@ class Vars(Singleton): - hist_file = os.path.join(homedir,".crm_history") - rc_file = os.path.join(homedir,".crm.rc") - tmpl_conf_dir = os.path.join(homedir,".crmconf") -+ report_cache = os.path.join("@CRM_CACHE_DIR@","history") - tmpl_dir = "@datadir@/@PACKAGE@/templates" - pe_dir = "@PE_STATE_DIR@" - crm_conf_dir = "@CRM_CONFIG_DIR@" - diff --git a/crm_history_10_d21f988a419c.patch b/crm_history_10_d21f988a419c.patch deleted file mode 100644 index 1920025..0000000 --- a/crm_history_10_d21f988a419c.patch +++ /dev/null @@ -1,38 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1314279513 -7200 -# Node ID d21f988a419c0c7fa349c4e26f6b500944d91370 -# Parent 709ef91cfada2822aca53dcef085ddb6952393c5 -Low: Shell: look for log segments with more care and don't throw exception on seek (bnc#713939) - -diff --git a/shell/modules/report.py b/shell/modules/report.py ---- a/shell/modules/report.py -+++ b/shell/modules/report.py -@@ -72,8 +72,15 @@ def seek_to_edge(f, ts, to_end): - Linear search, but should be short. - ''' - if not to_end: -+ beg = 0 - while ts == get_timestamp(f): -- f.seek(-1000, 1) # go back 10 or so lines -+ if f.tell() < 1000: -+ f.seek(0) # otherwise, the seek below throws an exception -+ if beg > 0: # avoid infinite loop -+ return # goes all the way to the top -+ beg += 1 -+ else: -+ f.seek(-1000, 1) # go back 10 or so lines - while True: - pos = f.tell() - s = f.readline() -@@ -86,8 +93,8 @@ def seek_to_edge(f, ts, to_end): - def log_seek(f, ts, to_end = False): - ''' - f is an open log. Do binary search for the timestamp. -- Return the position of the (more or less) first line with a -- newer time. -+ Return the position of the (more or less) first line with an -+ earlier (or later) time. - ''' - first = 0 - f.seek(0,2) diff --git a/crm_history_11_ccd0c1e1edf9.patch b/crm_history_11_ccd0c1e1edf9.patch deleted file mode 100644 index 6a5ef7b..0000000 --- a/crm_history_11_ccd0c1e1edf9.patch +++ /dev/null @@ -1,298 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1314632951 -7200 -# Node ID ccd0c1e1edf9f23cafb4363014acba755f1b4e25 -# Parent d21f988a419c0c7fa349c4e26f6b500944d91370 -Medium: Shell: several history improvements - -- add more patterns for fencing -- handle better PE files number reaching limit - -diff --git a/doc/crm.8.txt b/doc/crm.8.txt ---- a/doc/crm.8.txt -+++ b/doc/crm.8.txt -@@ -2426,7 +2426,8 @@ Example: - - The `latest` command shows a bit of recent history, more - precisely whatever happened since the last cluster change (the --latest transition). -+latest transition). If the transition is running, the shell will -+first wait until it finishes. - - Usage: - ............... -@@ -2540,10 +2541,13 @@ Example: - setnodes node_a node_b - ............... - --[[cmdhelp_history_resource,resource failed actions]] -+[[cmdhelp_history_resource,resource events]] - ==== `resource` - --Show status changes and any failures that happened on a resource. -+Show actions and any failures that happened on all specified -+resources on all nodes. Normally, one gives resource names as -+arguments, but it is also possible to use extended regular -+expressions. - - Usage: - ............... -@@ -2551,14 +2555,17 @@ Usage: - ............... - Example: - ............... -- resource mydb -+ resource bigdb public_ip -+ resource bigdb:0 -+ resource bigdb:. - ............... - - [[cmdhelp_history_node,node events]] - ==== `node` - - Show important events that happened on a node. Important events --are node lost and join, standby and online, and fence. -+are node lost and join, standby and online, and fence. Use either -+node names or extended regular expressions. - - Usage: - ............... -@@ -2572,7 +2579,17 @@ Example: - [[cmdhelp_history_log,log content]] - ==== `log` - --Show logs for a node or combined logs of all nodes. -+Show messages logged on one or more nodes. Leaving out a node -+name produces combined logs of all nodes. Messages are sorted by -+time and, if the terminal emulations supports it, displayed in -+different colours depending on the node to allow for easier -+reading. -+ -+The sorting key is the timestamp as written by syslog which -+normally has the maximum resolution of one second. Obviously, -+messages generated by events which share the same timestamp may -+not be sorted in the same way as they happened. Such close events -+may actually happen fairly often. - - Usage: - ............... -@@ -2634,8 +2651,8 @@ the transition are printed. - - Usage: - ............... -- transition [|] [nograph] [v...] [scores] [actions] [utilization] -- transition showdot [|] -+ transition [||] [nograph] [v...] [scores] [actions] [utilization] -+ transition showdot [||] - ............... - Examples: - ............... -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 -@@ -12,34 +12,41 @@ - # detail level 0 is the lowest, i.e. should match the least - # number of relevant messages - --# NB: If you modify this file, you must follow python syntax! -+# NB: -+# %% stands for whatever user input we get, for instance a -+# resource name or node name or just some regular expression -+# in optimal case, it should be surrounded by literals -+# -+# [Note that resources may contain clone numbers!] - - log_patterns = { - "resource": ( - ( # detail 0 -- "lrmd:.*rsc:%%.*(start|stop|promote|demote|migrate)", -- "lrmd:.*RA output:.*%%.*stderr", -- "lrmd:.*WARN:.*Managed.*%%.*exited", -+ "lrmd:.*rsc:%% (start|stop|promote|demote|migrate)", -+ "lrmd:.*RA output: .%%:.*:stderr", -+ "lrmd:.*WARN: Managed %%:.*exited", - ), - ( # detail 1 -- "lrmd:.*rsc:%%.*(probe|notify)", -- "lrmd:.*info:.*Managed.*%%.*exited", -+ "lrmd:.*rsc:%%:.*(probe|notify)", -+ "lrmd:.*info: Managed %%:.*exited", - ), - ), - "node": ( - ( # detail 0 -- "%%.*Corosync.Cluster.Engine", -- "%%.*Executive.Service.RELEASE", -- "%%.*crm_shutdown:.Requesting.shutdown", -- "%%.*pcmk_shutdown:.Shutdown.complete", -- "%%.*Configuration.validated..Starting.heartbeat", -- "pengine.*Scheduling Node %%", -- "te_fence_node.*Exec.*%%", -- "stonith-ng.*log_oper.*reboot.*%%", -- "stonithd.*to STONITH.*%%", -- "stonithd.*fenced node %%", -- "pcmk_peer_update.*(lost|memb): %%", -- "crmd.*ccm_event.*(NEW|LOST) %%", -+ " %% .*Corosync.Cluster.Engine", -+ " %% .*Executive.Service.RELEASE", -+ " %% .*crm_shutdown:.Requesting.shutdown", -+ " %% .*pcmk_shutdown:.Shutdown.complete", -+ " %% .*Configuration.validated..Starting.heartbeat", -+ "pengine.*Scheduling Node %% for STONITH", -+ "crmd.* tengine_stonith_callback: .* of %% failed", -+ "stonith-ng.*log_operation:.*host '%%'", -+ "te_fence_node: Exec.*on %% ", -+ "pe_fence_node: Node %% will be fenced", -+ "stonith-ng.*remote_op_timeout:.*for %% timed", -+ "stonithd.*Succeeded.*node %%:", -+ "pcmk_peer_update.*(lost|memb): %% ", -+ "crmd.*ccm_event.*(NEW|LOST):.* %% ", - ), - ( # detail 1 - ), -diff --git a/shell/modules/report.py b/shell/modules/report.py ---- a/shell/modules/report.py -+++ b/shell/modules/report.py -@@ -589,7 +589,7 @@ class Report(Singleton): - 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) -+ pe_l = self.list_transitions([x for x in f], future_pe = True) - if pe_l: - l.append([node,pe_l]) - return l -@@ -752,12 +752,13 @@ 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): -+ def list_transitions(self, msg_l = None, future_pe = False): - ''' -- Get a list of transitions. -+ List transitions by reading logs. - Empty transitions are skipped. -- Some callers need original PE file path (keep_pe_path), -- otherwise we produce the path within the report. -+ Some callers need original PE file path (future_pe), -+ otherwise we produce the path within the report and check -+ if the transition files exist. - 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. -@@ -786,11 +787,18 @@ class Report(Singleton): - continue - elif num_actions == -1: # couldn't find messages - common_warn("could not find number of actions for transition (%s)" % pe_base) -- common_debug("found PE input at %s: %s" % (node, pe_file)) -- if keep_pe_path: -- pe_l.append(pe_file) -+ if not future_pe: -+ pe_l_file = os.path.join(self.loc, node, "pengine", pe_base) -+ if not os.path.isfile(pe_l_file): -+ warn_once("%s in the logs, but not in the report" % pe_l_file) -+ continue - else: -- pe_l.append(os.path.join(self.loc, node, "pengine", pe_base)) -+ pe_l_file = "%s:%s" % (node, pe_file) -+ if pe_l_file in pe_l: -+ common_warn("duplicate %s, replacing older PE file" % pe_l_file) -+ pe_l.remove(pe_l_file) -+ common_debug("found PE input: %s" % pe_l_file) -+ pe_l.append(pe_l_file) - return pe_l - def report_setup(self): - if not self.loc: -@@ -802,11 +810,7 @@ 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) -+ self.peinputs_l = self.list_transitions() - def prepare_source(self): - ''' - Unpack a hb_report tarball. -@@ -859,7 +863,7 @@ class Report(Singleton): - if not args: - re_l = mk_re_list(patt_l,"") - else: -- re_l = mk_re_list(patt_l,r'(%s)\W' % "|".join(args)) -+ re_l = mk_re_list(patt_l,r'(%s)' % "|".join(args)) - return re_l - def disp(self, s): - 'color output' -@@ -886,11 +890,6 @@ class Report(Singleton): - self.error("no logs found") - return - self.display_logs(self.logobj.get_matches(re_l, log_l)) -- def match_args(self, cib_l, args): -- for a in args: -- a_clone = re.sub(r':.*', '', a) -- if not (a in cib_l) and not (a_clone in cib_l): -- self.warn("%s not found in report, proceeding anyway" % a) - def get_desc_line(self,fld): - try: - f = open(self.desc) -@@ -923,8 +922,9 @@ class Report(Singleton): - ''' - Show all events. - ''' -- all_re_l = self.build_re("resource",self.cibrsc_l) + \ -- self.build_re("node",self.cibnode_l) -+ all_re_l = self.build_re("resource", self.cibrsc_l) + \ -+ self.build_re("node", self.cibnode_l) + \ -+ self.build_re("events", []) - if not all_re_l: - self.error("no resources or nodes found") - return False -@@ -940,6 +940,7 @@ class Report(Singleton): - te_invoke_patt = transition_patt[0].replace("%%", pe_num) - run_patt = transition_patt[1].replace("%%", pe_num) - r = None -+ msg_l.reverse() - for msg in msg_l: - r = re.search(te_invoke_patt, msg) - if r: -@@ -1009,7 +1010,6 @@ class Report(Singleton): - expanded_l += self.cibgrp_d[a] - else: - expanded_l.append(a) -- self.match_args(self.cibrsc_l,expanded_l) - rsc_re_l = self.build_re("resource",expanded_l) - if not rsc_re_l: - return False -@@ -1020,7 +1020,6 @@ class Report(Singleton): - ''' - if not self.prepare_source(): - return False -- self.match_args(self.cibnode_l,args) - node_re_l = self.build_re("node",args) - if not node_re_l: - return False -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 -@@ -1877,16 +1877,16 @@ Examine Pacemaker's history: node and re - def _get_pe_byidx(self, idx): - l = crm_report.pelist() - if len(l) < abs(idx): -- common_err("pe input file for index %d not found" % (idx+1)) -+ common_err("PE input file for index %d not found" % (idx+1)) - return None - return l[idx] - def _get_pe_bynum(self, n): - l = crm_report.pelist([n]) - if len(l) == 0: -- common_err("%s: PE file %d not found" % n) -+ common_err("PE file %d not found" % n) - return None - elif len(l) > 1: -- common_err("%s: PE file %d ambiguous" % n) -+ common_err("PE file %d ambiguous" % n) - return None - return l[0] - def transition(self,cmd,*args): diff --git a/crm_history_1_d0359dca5dba.patch b/crm_history_1_d0359dca5dba.patch deleted file mode 100644 index c90072a..0000000 --- a/crm_history_1_d0359dca5dba.patch +++ /dev/null @@ -1,20 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1312579593 -7200 -# Node ID d0359dca5dba3fd6fee856d51cca5ee7ac752ee6 -# Parent a7acb683b3568ca81d90472f770b0270270d5dfd -Low: Shell: relax host key checking in pssh - -diff -r a7acb683b356 -r d0359dca5dba shell/modules/crm_pssh.py ---- a/shell/modules/crm_pssh.py Fri Aug 05 23:13:37 2011 +0200 -+++ b/shell/modules/crm_pssh.py Fri Aug 05 23:26:33 2011 +0200 -@@ -84,7 +84,8 @@ def do_pssh(l, opts): - hosts = [] - for host, cmdline in l: - cmd = ['ssh', host, '-o', 'PasswordAuthentication=no', -- '-o', 'SendEnv=PSSH_NODENUM'] -+ '-o', 'SendEnv=PSSH_NODENUM', -+ '-o', 'StrictHostKeyChecking=no'] - if opts.options: - for opt in opts.options: - cmd += ['-o', opt] diff --git a/crm_history_2_29fd4f04c01f.patch b/crm_history_2_29fd4f04c01f.patch deleted file mode 100644 index 3438020..0000000 --- a/crm_history_2_29fd4f04c01f.patch +++ /dev/null @@ -1,19 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1312580731 -7200 -# Node ID 29fd4f04c01f92e54026d9d6bb54d617d8b1fdcd -# Parent d0359dca5dba3fd6fee856d51cca5ee7ac752ee6 -Low: Shell: enforce remote report directory removal for history - -diff -r d0359dca5dba -r 29fd4f04c01f shell/modules/report.py ---- a/shell/modules/report.py Fri Aug 05 23:26:33 2011 +0200 -+++ b/shell/modules/report.py Fri Aug 05 23:45:31 2011 +0200 -@@ -595,7 +595,7 @@ class Report(Singleton): - if ext_cmd_nosudo("mkdir -p %s" % os.path.dirname(d)) != 0: - return None - common_info("retrieving information from cluster nodes, please wait ...") -- rc = ext_cmd_nosudo("hb_report -f '%s' %s %s %s" % -+ rc = ext_cmd_nosudo("hb_report -Z -f '%s' %s %s %s" % - (self.from_dt.ctime(), to_option, nodes_option, d)) - if rc != 0: - if os.path.isfile(tarball): diff --git a/crm_history_3_b3a014c0f85b.patch b/crm_history_3_b3a014c0f85b.patch deleted file mode 100644 index afcc4f3..0000000 --- a/crm_history_3_b3a014c0f85b.patch +++ /dev/null @@ -1,558 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# 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 [{|} ...] -- peinputs get [{|} ...] -+ peinputs [{|} ...] [v] - - range :: : - ............... - 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 [] [nograph] [v...] [scores] [actions] [utilization] -- transition showdot [] -+ transition [|] [nograph] [v...] [scores] [actions] [utilization] -+ transition showdot [|] - ............... - 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 [{|} ...] -- peinputs get [{|} ...]""" -- if subcmd not in ("get","list"): -- bad_usage(cmd,subcmd) -- return False -- if args: -+ def peinputs(self,cmd,*args): -+ """usage: peinputs [{|} ...] [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 [] [nograph] [v...] [scores] [actions] [utilization] -- transition showdot []""" -- 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 [|] [nograph] [v...] [scores] [actions] [utilization] -+ transition showdot [|]""" -+ 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.' diff --git a/crm_history_4_a09974a06cdf.patch b/crm_history_4_a09974a06cdf.patch deleted file mode 100644 index 679cad5..0000000 --- a/crm_history_4_a09974a06cdf.patch +++ /dev/null @@ -1,23 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1312838871 -7200 -# Node ID a09974a06cdf6a3d73c3cdfa6e4d89d41e2ca9f0 -# Parent 29fd4f04c01f92e54026d9d6bb54d617d8b1fdcd -Low: Shell: avoid DeprecationWarning for BaseException.message - -diff --git a/shell/modules/utils.py b/shell/modules/utils.py ---- a/shell/modules/utils.py -+++ b/shell/modules/utils.py -@@ -257,9 +257,9 @@ def acquire_lock(dir): - os.mkdir(os.path.join(dir,_LOCKDIR)) - str2file("%d" % os.getpid(),os.path.join(dir,_LOCKDIR,_PIDF)) - return True -- except OSError, e: -- if e.errno != os.errno.EEXIST: -- common_err("%s" % e.message) -+ except OSError as (errno, strerror): -+ if errno != os.errno.EEXIST: -+ common_err(strerror) - return False - time.sleep(0.1) - continue diff --git a/crm_history_5_c3068d22de72.patch b/crm_history_5_c3068d22de72.patch deleted file mode 100644 index 2d42591..0000000 --- a/crm_history_5_c3068d22de72.patch +++ /dev/null @@ -1,90 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1313019300 -7200 -# Node ID c3068d22de72d1ba616d43c808091bef830eb9f6 -# Parent b3a014c0f85b2bbe1e6a2360c44fbbfc7ac27b73 -Medium: Shell: improve capture log slices for transitions (bnc#710907) - -diff --git a/shell/modules/report.py b/shell/modules/report.py ---- a/shell/modules/report.py -+++ b/shell/modules/report.py -@@ -65,7 +65,25 @@ def syslog_ts(s): - common_warn("malformed line: %s" % s) - return None - --def log_seek(f, ts, endpos = False): -+def seek_to_edge(f, ts, to_end): -+ ''' -+ f contains lines with exactly the timestamp ts. -+ Read forward (or backward) till we find the edge. -+ Linear search, but should be short. -+ ''' -+ if not to_end: -+ while ts == get_timestamp(f): -+ f.seek(-1000, 1) # go back 10 or so lines -+ while True: -+ pos = f.tell() -+ s = f.readline() -+ curr_ts = syslog_ts(s) -+ if (to_end and curr_ts > ts) or \ -+ (not to_end and curr_ts >= ts): -+ break -+ f.seek(pos) -+ -+def log_seek(f, ts, to_end = False): - ''' - f is an open log. Do binary search for the timestamp. - Return the position of the (more or less) first line with a -@@ -75,10 +93,11 @@ def log_seek(f, ts, endpos = False): - f.seek(0,2) - last = f.tell() - if not ts: -- return endpos and last or first -+ return to_end and last or first - badline = 0 - maxbadline = 10 -- common_debug("seek ts %s" % time.ctime(ts)) -+ common_debug("seek %s:%s in %s" % -+ (time.ctime(ts), to_end and "end" or "start", f.name)) - while first <= last: - # we can skip some iterations if it's about few lines - if abs(first-last) < 120: -@@ -98,9 +117,12 @@ def log_seek(f, ts, endpos = False): - elif log_ts < ts: - first = mid+1 - else: -+ seek_to_edge(f, log_ts, to_end) - break -- common_debug("sought to %s" % time.ctime(log_ts)) -- return f.tell() -+ fpos = f.tell() -+ common_debug("sought to %s (%d)" % (f.readline(), fpos)) -+ f.seek(fpos) -+ return fpos - - def get_timestamp(f): - ''' -@@ -187,7 +209,7 @@ class LogSyslog(object): - for log in self.f: - f = self.f[log] - start = log_seek(f, self.from_ts) -- end = log_seek(f, self.to_ts, endpos = True) -+ end = log_seek(f, self.to_ts, to_end = True) - if start == -1 or end == -1: - bad_logs.append(log) - else: -diff --git a/shell/modules/utils.py b/shell/modules/utils.py ---- a/shell/modules/utils.py -+++ b/shell/modules/utils.py -@@ -413,7 +413,10 @@ def run_ptest(graph_s, nograph, scores, - print get_stdout(ptest, input_s = graph_s) - #page_string(get_stdout(ptest, input_s = graph_s)) - if dotfile: -- show_dot_graph(dotfile) -+ if os.path.getsize(dotfile) > 0: -+ show_dot_graph(dotfile) -+ else: -+ common_warn("ptest produced empty dot file") - vars.tmpfiles.append(dotfile) - else: - if not nograph: diff --git a/crm_history_6_441f4448eba6.patch b/crm_history_6_441f4448eba6.patch deleted file mode 100644 index 64ca5c8..0000000 --- a/crm_history_6_441f4448eba6.patch +++ /dev/null @@ -1,245 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1313081065 -7200 -# Node ID 441f4448eba6eda1a2cf44d3d63a0db9f8d56a20 -# Parent c3068d22de72d1ba616d43c808091bef830eb9f6 -Medium: Shell: reimplement the history latest command (bnc#710958) - -This command is going to show logs for the latest transition. -Basically, it's the same as "history transition", but it will -wait for the current (if any) transition to finish. - -(Also, the horrible transition command arg parsing has been -improved.) - -diff --git a/shell/modules/report.py b/shell/modules/report.py ---- a/shell/modules/report.py -+++ b/shell/modules/report.py -@@ -467,8 +467,7 @@ class Report(Singleton): - def is_last_live_recent(self): - ''' - Look at the last live hb_report. If it's recent enough, -- return True. Return True also if self.to_dt is not empty -- (not an open end report). -+ return True. - ''' - try: - last_ts = os.stat(self.desc).st_mtime -@@ -800,6 +799,7 @@ class Report(Singleton): - if self.source != "live": - self.error("refresh not supported") - return False -+ self.last_live_update = 0 - self.loc = self.manage_live_report(force) - self.report_setup() - self.ready = self.check_report() -@@ -884,18 +884,10 @@ class Report(Singleton): - print "Nodes:",' '.join(self.cibnode_l) - print "Groups:",' '.join(self.cibgrp_d.keys()) - print "Resources:",' '.join(self.cibrsc_l) -- def latest(self): -- ''' -- Get the very latest cluster events, basically from the -- latest transition. -- Some transitions may be empty though. -- ''' - def events(self): - ''' - Show all events. - ''' -- if not self.prepare_source(): -- return False - all_re_l = self.build_re("resource",self.cibrsc_l) + \ - self.build_re("node",self.cibnode_l) - if not all_re_l: -@@ -906,6 +898,8 @@ class Report(Singleton): - ''' - Search for events within the given transition. - ''' -+ if not self.prepare_source(): -+ return False - pe_base = os.path.basename(pe_file) - r = re.search("pe-[^-]+-([0-9]+)[.]", pe_base) - pe_num = r.group(1) -@@ -926,6 +920,9 @@ class Report(Singleton): - self.warn("strange, no timestamps found") - return False - # limit the log scope temporarily -+ common_info("logs for transition %s (%s-%s)" % -+ (pe_file.replace(self.loc+"/",""), \ -+ shorttime(start_ts), shorttime(end_ts))) - self.logobj.set_log_timeframe(start_ts, end_ts) - self.events() - self.logobj.set_log_timeframe(self.from_dt, self.to_dt) -@@ -994,6 +991,11 @@ class Report(Singleton): - '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 pe2dot(self, f): -+ f = f.replace("bz2","dot") -+ if os.path.isfile(f): -+ return f -+ return None - 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 -@@ -1796,22 +1796,15 @@ Examine Pacemaker's history: node and re - return crm_report.info() - def latest(self,cmd): - "usage: latest" -- try: -- prev_level = levels.previous().myname() -- except: -- prev_level = '' -- if prev_level != "cibconfig": -- common_err("%s is available only when invoked from configure" % cmd) -- return False -- ts = cib_factory.last_commit_at() -- if not ts: -- common_err("no last commit time found") -- return False - if not wait4dc("transition", not options.batch): - return False -- self._set_source("live", ts) -+ self._set_source("live") - crm_report.refresh_source() -- return crm_report.events() -+ f = self._get_pe_byidx(-1) -+ if not f: -+ common_err("no transitions found") -+ return False -+ crm_report.show_transition_log(f) - def resource(self,cmd,*args): - "usage: resource [ ...]" - return crm_report.resource(*args) -@@ -1853,6 +1846,30 @@ Examine Pacemaker's history: node and re - else: - s = '\n'.join(l) - page_string(s) -+ def _get_pe_byname(self, s): -+ l = crm_report.find_pe_files(s) -+ if len(l) == 0: -+ common_err("%s: path not found" % s) -+ return None -+ elif len(l) > 1: -+ common_err("%s: path ambiguous" % s) -+ return None -+ return l[0] -+ def _get_pe_byidx(self, idx): -+ l = crm_report.pelist() -+ if len(l) < abs(idx): -+ common_err("pe input file not found") -+ return None -+ return l[idx] -+ def _get_pe_bynum(self, n): -+ l = crm_report.pelist([n]) -+ if len(l) == 0: -+ common_err("%s: PE file %d not found" % n) -+ return None -+ elif len(l) > 1: -+ common_err("%s: PE file %d ambiguous" % n) -+ return None -+ return l[0] - def transition(self,cmd,*args): - """usage: transition [|] [nograph] [v...] [scores] [actions] [utilization] - transition showdot [|]""" -@@ -1864,48 +1881,35 @@ Examine Pacemaker's history: node and re - return False - 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] -+ if argl: -+ if re.search('pe-', argl[0]): -+ f = self._get_pe_byname(argl[0]) -+ argl.pop(0) -+ elif is_int(argl[0]): -+ n = int(argl[0]) -+ if n <= 0: -+ f = self._get_pe_byidx(n-1) -+ else: -+ f = self._get_pe_bynum(n) -+ argl.pop(0) -+ else: -+ f = self._get_pe_byidx(-1) - else: -- 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] -+ f = self._get_pe_byidx(-1) - if not f: - return False - rc = True - if subcmd == "show": - self.pe_file = f # self.pe_file needed by self.ptest -+ common_info("running ptest with %s" % f) - rc = ptestlike(self.ptest,'vv',"%s %s" % \ -- (cmd, subcmd), *argl[startarg:]) -+ (cmd, subcmd), *argl) - else: -- show_dot_graph(f.replace("bz2","dot")) -+ f = crm_report.pe2dot(f) -+ if not f: -+ common_err("dot file not found in the report") -+ return False -+ show_dot_graph(f) - if rc: - crm_report.show_transition_log(f) - return rc -diff --git a/shell/modules/utils.py b/shell/modules/utils.py ---- a/shell/modules/utils.py -+++ b/shell/modules/utils.py -@@ -449,6 +449,9 @@ def check_range(a): - return False - return (int(a[0]) <= int(a[1])) - -+def shorttime(ts): -+ return time.strftime("%X",time.localtime(ts)) -+ - def sort_by_mtime(l): - 'Sort a (small) list of files by time mod.' - l2 = [(os.stat(x).st_mtime, x) for x in l] -@@ -489,6 +492,13 @@ def convert2ints(l): - else: # it's a string then - return int(l) - except: return None -+def is_int(s): -+ 'Check if the string can be converted to an integer.' -+ try: -+ i = int(s) -+ return True -+ except: -+ return False - - def is_process(s): - proc = subprocess.Popen("ps -e -o pid,command | grep -qs '%s'" % s, \ diff --git a/crm_history_7_3f3c348aaaed.patch b/crm_history_7_3f3c348aaaed.patch deleted file mode 100644 index 5c5aca2..0000000 --- a/crm_history_7_3f3c348aaaed.patch +++ /dev/null @@ -1,207 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1313413824 -7200 -# Node ID 3f3c348aaaed52383f6646b08899943aec8911f4 -# Parent 441f4448eba6eda1a2cf44d3d63a0db9f8d56a20 -Medium: Shell: relax transition acceptance - -Sometimes logs are missing one or another transition related -message. Try to be more forgiving then. -Also, print information about number of actions which were -completed, skipped, etc. - -diff --git a/shell/modules/report.py b/shell/modules/report.py ---- a/shell/modules/report.py -+++ b/shell/modules/report.py -@@ -320,10 +320,8 @@ def is_log(p): - return os.path.isfile(p) and os.path.getsize(p) > 0 - - 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]): -+ pe_num = get_pe_num(pe_f) -+ if not a or (a[0] <= int(pe_num) <= a[1]): - return pe_f - return None - -@@ -347,6 +345,12 @@ def update_loginfo(rptlog, logfile, oldp - except IOError, msg: - common_err("couldn't the update %s.info: %s" % (rptlog, msg)) - -+def get_pe_num(pe_file): -+ try: -+ return re.search("pe-[^-]+-([0-9]+)[.]", pe_file).group(1) -+ except: -+ return "-1" -+ - # r.group(1) transition number (a different thing from file number) - # r.group(2) contains full path - # r.group(3) file number -@@ -358,6 +362,40 @@ transition_patt = ( - "crmd: .* unpack_graph: Unpacked transition (%%): ([0-9]+) actions", # number of actions - ) - -+def run_graph_msg_actions(msg): -+ ''' -+ crmd: [13667]: info: run_graph: Transition 399 (Complete=5, -+ Pending=1, Fired=1, Skipped=0, Incomplete=3, -+ Source=... -+ ''' -+ d = {} -+ s = msg -+ while True: -+ r = re.search("([A-Z][a-z]+)=([0-9]+)", s) -+ if not r: -+ return d -+ d[r.group(1)] = int(r.group(2)) -+ s = s[r.end():] -+def transition_actions(msg_l, te_invoke_msg, pe_file): -+ ''' -+ Get the number of actions for the transition. -+ ''' -+ # check if there were any actions in this transition -+ pe_num = get_pe_num(pe_file) -+ te_invoke_patt = transition_patt[0].replace("%%", pe_num) -+ run_patt = transition_patt[1].replace("%%", pe_num) -+ r = re.search(te_invoke_patt, te_invoke_msg) -+ trans_num = r.group(1) -+ unpack_patt = transition_patt[2].replace("%%", trans_num) -+ for msg in msg_l: -+ try: -+ return int(re.search(unpack_patt, msg).group(2)) -+ except: -+ if re.search(run_patt, msg): -+ act_d = run_graph_msg_actions(msg) -+ return sum(act_d.values()) -+ return -1 -+ - class Report(Singleton): - ''' - A hb_report class. -@@ -396,8 +434,7 @@ class Report(Singleton): - def node_list(self): - return self.cibnode_l - def peinputs_list(self): -- return [re.search("pe-[^-]+-([0-9]+)[.]bz2$", x).group(1) -- for x in self.peinputs_l] -+ return [get_pe_num(x) for x in self.peinputs_l] - def unpack_report(self, tarball): - ''' - Unpack hb_report tarball. -@@ -712,8 +749,6 @@ class Report(Singleton): - ''' - 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 -@@ -738,19 +773,12 @@ class Report(Singleton): - 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 -+ num_actions = transition_actions(msg_l, msg, pe_file) - if num_actions == 0: # empty transition -- common_debug("skipping empty transition %s (%s)" % (trans_num, pe_base)) -+ common_debug("skipping empty transition (%s)" % pe_base) - continue -+ elif num_actions == -1: # couldn't find messages -+ common_warn("could not find number of actions for transition (%s)" % pe_base) - common_debug("found PE input at %s: %s" % (node, pe_file)) - if keep_pe_path: - pe_l.append(pe_file) -@@ -894,6 +922,34 @@ class Report(Singleton): - self.error("no resources or nodes found") - return False - self.show_logs(re_l = all_re_l) -+ def get_transition_msgs(self, pe_file, msg_l = []): -+ if not msg_l: -+ trans_re_l = [x.replace("%%", "[0-9]+") for x in transition_patt] -+ msg_l = self.logobj.get_matches(trans_re_l) -+ te_invoke_msg = "" -+ run_msg = "" -+ unpack_msg = "" -+ pe_num = get_pe_num(pe_file) -+ te_invoke_patt = transition_patt[0].replace("%%", pe_num) -+ run_patt = transition_patt[1].replace("%%", pe_num) -+ r = None -+ for msg in msg_l: -+ r = re.search(te_invoke_patt, msg) -+ if r: -+ te_invoke_msg = msg -+ break -+ if not r: -+ return ["", "", ""] -+ trans_num = r.group(1) -+ unpack_patt = transition_patt[2].replace("%%", trans_num) -+ for msg in msg_l: -+ if re.search(run_patt, msg): -+ run_msg = msg -+ elif re.search(unpack_patt, msg): -+ unpack_msg = msg -+ if run_msg and unpack_msg: -+ break -+ return [unpack_msg, te_invoke_msg, run_msg] - def show_transition_log(self, pe_file): - ''' - Search for events within the given transition. -@@ -901,28 +957,34 @@ class Report(Singleton): - if not self.prepare_source(): - return False - pe_base = os.path.basename(pe_file) -- 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: -+ pe_num = get_pe_num(pe_base) -+ unpack_msg, te_invoke_msg, run_msg = self.get_transition_msgs(pe_file) -+ if not te_invoke_msg: - common_warn("start of transition %s not found in logs" % pe_base) - return False -- if not trans_end: -+ if not run_msg: - 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]) -- start_ts = syslog_ts(trans_start[0]) -- end_ts = syslog_ts(trans_end[0]) -+ common_debug("transition start: %s" % te_invoke_msg) -+ common_debug("transition end: %s" % run_msg) -+ start_ts = syslog_ts(te_invoke_msg) -+ end_ts = syslog_ts(run_msg) - if not start_ts or not end_ts: - self.warn("strange, no timestamps found") - return False -- # limit the log scope temporarily -+ act_d = run_graph_msg_actions(run_msg) -+ total = sum(act_d.values()) -+ s = "" -+ for a in act_d: -+ if not act_d[a]: -+ continue -+ s = "%s %s=%d" % (s, a, act_d[a]) -+ common_info("transition %s %d actions: %s" % -+ (pe_file.replace(self.loc+"/",""), total, s)) - common_info("logs for transition %s (%s-%s)" % - (pe_file.replace(self.loc+"/",""), \ - shorttime(start_ts), shorttime(end_ts))) -+ # limit the log scope temporarily - self.logobj.set_log_timeframe(start_ts, end_ts) - self.events() - self.logobj.set_log_timeframe(self.from_dt, self.to_dt) diff --git a/crm_history_8_3681d3471fde.patch b/crm_history_8_3681d3471fde.patch deleted file mode 100644 index 997d6f6..0000000 --- a/crm_history_8_3681d3471fde.patch +++ /dev/null @@ -1,34 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1313416746 -7200 -# Node ID 3681d3471fdecde109ea7c25ab2ceb31e1e8646f -# Parent 3f3c348aaaed52383f6646b08899943aec8911f4 -Low: Shell: update log patterns for history - -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 -@@ -3,7 +3,7 @@ - # log pattern specification - # - # patterns are grouped one of several classes: --# - resources: pertaining to a resource -+# - resource: pertaining to a resource - # - node: pertaining to a node - # - quorum: quorum changes - # - events: other interesting events (core dumps, etc) -@@ -17,12 +17,12 @@ - log_patterns = { - "resource": ( - ( # detail 0 -- "lrmd:.*rsc:%%.*(start|stop)", -+ "lrmd:.*rsc:%%.*(start|stop|promote|demote|migrate)", - "lrmd:.*RA output:.*%%.*stderr", - "lrmd:.*WARN:.*Managed.*%%.*exited", - ), - ( # detail 1 -- "lrmd:.*rsc:%%.*probe", -+ "lrmd:.*rsc:%%.*(probe|notify)", - "lrmd:.*info:.*Managed.*%%.*exited", - ), - ), diff --git a/crm_history_9_709ef91cfada.patch b/crm_history_9_709ef91cfada.patch deleted file mode 100644 index 5c44438..0000000 --- a/crm_history_9_709ef91cfada.patch +++ /dev/null @@ -1,47 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1314196090 -7200 -# Node ID 709ef91cfada2822aca53dcef085ddb6952393c5 -# Parent 3a81b7eae66672dd9873fe6b53ee3c0da6fc87d7 -Low: Shell: update pe not found message - -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 -@@ -1822,7 +1822,6 @@ Examine Pacemaker's history: node and re - crm_report.refresh_source() - f = self._get_pe_byidx(-1) - if not f: -- common_err("no transitions found") - return False - crm_report.show_transition_log(f) - def resource(self,cmd,*args): -@@ -1878,7 +1877,7 @@ Examine Pacemaker's history: node and re - def _get_pe_byidx(self, idx): - l = crm_report.pelist() - if len(l) < abs(idx): -- common_err("pe input file not found") -+ common_err("pe input file for index %d not found" % (idx+1)) - return None - return l[idx] - def _get_pe_bynum(self, n): -@@ -1913,7 +1912,8 @@ Examine Pacemaker's history: node and re - f = self._get_pe_bynum(n) - argl.pop(0) - else: -- f = self._get_pe_byidx(-1) -+ common_err("<%s> doesn't sound like a PE input" % argl[0]) -+ return False - else: - f = self._get_pe_byidx(-1) - if not f: -@@ -1922,8 +1922,7 @@ Examine Pacemaker's history: node and re - if subcmd == "show": - self.pe_file = f # self.pe_file needed by self.ptest - common_info("running ptest with %s" % f) -- rc = ptestlike(self.ptest,'vv',"%s %s" % \ -- (cmd, subcmd), *argl) -+ rc = ptestlike(self.ptest,'vv', cmd, *argl) - else: - f = crm_report.pe2dot(f) - if not f: diff --git a/crm_history_peinputs.patch b/crm_history_peinputs.patch deleted file mode 100644 index 91034f0..0000000 --- a/crm_history_peinputs.patch +++ /dev/null @@ -1,315 +0,0 @@ -changeset: 10788:6f9cc20dba0d -user: Dejan Muhamedagic -date: Mon Jul 18 12:35:57 2011 +0200 -summary: Dev: Shell: spawn transition command from peinputs - -diff -r b694b75d2e33 -r 6f9cc20dba0d doc/crm.8.txt ---- a/doc/crm.8.txt Mon Jul 18 12:35:57 2011 +0200 -+++ b/doc/crm.8.txt Mon Jul 18 12:35:57 2011 +0200 -@@ -2565,6 +2565,21 @@ were created (the DC at the time). The ` - all PE input files to the current working directory (and use ssh - if necessary). - -+Usage: -+............... -+ peinputs list [{|} ...] -+ peinputs get [{|} ...] -+ -+ range :: : -+............... -+Example: -+............... -+ peinputs get 440:444 446 -+............... -+ -+[[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 -@@ -2581,22 +2596,20 @@ last one, i.e. the last transition. If t - then the corresponding transition relative to the last one is - chosen. - -+After the `ptest` output, logs about events that happened during -+the transition are printed. -+ - Usage: - ............... -- peinputs list [{|} ...] -- peinputs get [{|} ...] -- peinputs show [] [nograph] [v...] [scores] [actions] [utilization] -- peinputs showdot [] -- -- range :: : -+ transition show [] [nograph] [v...] [scores] [actions] [utilization] -+ transition showdot [] - ............... --Example: -+Examples: - ............... -- peinputs get 440:444 446 -- peinputs show -- peinputs show 444 -- peinputs show -1 -- peinputs showdot 444 -+ transition show -+ transition show 444 -+ transition show -1 -+ transition showdot 444 - ............... - - === `end` (`cd`, `up`) -diff -r b694b75d2e33 -r 6f9cc20dba0d shell/modules/log_patterns.py ---- a/shell/modules/log_patterns.py Mon Jul 18 12:35:57 2011 +0200 -+++ b/shell/modules/log_patterns.py Mon Jul 18 12:35:57 2011 +0200 -@@ -64,6 +64,6 @@ log_patterns = { - } - - transition_patt = ( -- "crmd: .* Processing graph.*derived from (.*bz2)", # transition start -- "crmd: .* Transition.*Source=(.*bz2): (Stopped|Complete|Terminated)", # and stop -+ "crmd: .* Processing graph.*derived from .*/pe-[^-]+-(%%)[.]bz2", # transition start -+ "crmd: .* Transition.*Source=.*/pe-[^-]+-(%%)[.]bz2.: (Stopped|Complete|Terminated)", # and stop - ) -diff -r b694b75d2e33 -r 6f9cc20dba0d shell/modules/report.py ---- a/shell/modules/report.py Mon Jul 18 12:35:57 2011 +0200 -+++ b/shell/modules/report.py Mon Jul 18 12:35:57 2011 +0200 -@@ -177,8 +177,12 @@ class LogSyslog(object): - find out start/end file positions. Logs need to be - already open. - ''' -- self.from_ts = convert_dt(from_dt) -- self.to_ts = convert_dt(to_dt) -+ if isinstance(from_dt, datetime.datetime): -+ self.from_ts = convert_dt(from_dt) -+ self.to_ts = convert_dt(to_dt) -+ else: -+ self.from_ts = from_dt -+ self.to_ts = to_dt - bad_logs = [] - for log in self.f: - f = self.f[log] -@@ -498,13 +502,14 @@ class Report(Singleton): - 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 = [] - fl = glob.glob("%s/*%s*" % (self.outdir,node)) - if not fl: - continue - for s in file2list(fl[0]): -- r = re.search(transition_patt[0], s) -+ r = re.search(trans_re_l[0], s) - if not r: - continue - node_l.append(r.group(1)) -@@ -680,7 +685,8 @@ class Report(Singleton): - self.find_central_log() - self.read_cib() - self.set_node_colors() -- self.logobj = None -+ self.logobj = LogSyslog(self.central_log, self.log_l, \ -+ self.from_dt, self.to_dt) - def prepare_source(self): - ''' - Unpack a hb_report tarball. -@@ -740,6 +746,15 @@ class Report(Singleton): - try: clr = self.nodecolor[a[3]] - except: return s - return termctrl.render("${%s}%s${NORMAL}" % (clr,s)) -+ def display_logs(self, l): -+ if not options.batch and sys.stdout.isatty(): -+ page_string('\n'.join([ self.disp(x) for x in l ])) -+ else: # raw output -+ try: # in case user quits the next prog in pipe -+ for s in l: print s -+ except IOError, msg: -+ if not ("Broken pipe" in msg): -+ common_err(msg) - def show_logs(self, log_l = [], re_l = []): - ''' - Print log lines, either matched by re_l or all. -@@ -749,18 +764,7 @@ class Report(Singleton): - if not self.central_log and not log_l: - self.error("no logs found") - return -- if not self.logobj: -- self.logobj = LogSyslog(self.central_log, log_l, \ -- self.from_dt, self.to_dt) -- l = self.logobj.get_matches(re_l, log_l) -- if not options.batch and sys.stdout.isatty(): -- page_string('\n'.join([ self.disp(x) for x in l ])) -- else: # raw output -- try: # in case user quits the next prog in pipe -- for s in l: print s -- except IOError, msg: -- if not ("Broken pipe" in msg): -- common_err(msg) -+ self.display_logs(self.logobj.get_matches(re_l, log_l)) - def match_args(self, cib_l, args): - for a in args: - a_clone = re.sub(r':.*', '', a) -@@ -812,6 +816,34 @@ class Report(Singleton): - self.error("no resources or nodes found") - return False - self.show_logs(re_l = all_re_l) -+ def show_transition_log(self, pe_file): -+ ''' -+ Search for events within the given transition. -+ ''' -+ pe_base = os.path.basename(pe_file) -+ r = re.search("pe-[^-]+-([0-9]+)[.]bz2", 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) -+ return False -+ if not trans_end: -+ common_warn("transition %s end not found in logs" % pe_base) -+ return False -+ common_debug("transition start: %s" % trans_start[0]) -+ common_debug("transition end: %s" % trans_end[0]) -+ start_ts = syslog_ts(trans_start[0]) -+ end_ts = syslog_ts(trans_end[0]) -+ if not start_ts or not end_ts: -+ self.warn("strange, no timestamps found") -+ return False -+ # limit the log scope temporarily -+ self.logobj.set_log_timeframe(start_ts, end_ts) -+ self.events() -+ self.logobj.set_log_timeframe(self.from_dt, self.to_dt) -+ return True - def resource(self,*args): - ''' - Show resource relevant logs. -diff -r b694b75d2e33 -r 6f9cc20dba0d shell/modules/ui.py.in ---- a/shell/modules/ui.py.in Mon Jul 18 12:35:57 2011 +0200 -+++ b/shell/modules/ui.py.in Mon Jul 18 12:35:57 2011 +0200 -@@ -1686,7 +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,(0,),1,0) -+ self.cmd_table["peinputs"] = (self.peinputs,(1,),1,0) -+ self.cmd_table["transition"] = (self.transition,(1,),1,0) - self._set_source(options.history) - def _no_source(self): - common_error("we have no source set yet! please use the source command") -@@ -1832,57 +1833,63 @@ Examine Pacemaker's history: node and re - return run_ptest(s, nograph, scores, utilization, actions, verbosity) - def peinputs(self,cmd,subcmd,*args): - """usage: peinputs list [{|} ...] -- peinputs get [{|} ...] -- peinputs show [] [nograph] [v...] [scores] [actions] [utilization] -- peinputs showdot []""" -- if subcmd in ("get","list"): -- if args: -- l = [] -- for s in args: -- a = convert2ints(s.split(':')) -- if 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) -- else: -- print '\n'.join(l) -- elif subcmd in ("show","showdot"): -- 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": -- if not user_prefs.dotty: -- common_err("install graphviz to draw transition graphs") -+ peinputs get [{|} ...]""" -+ if subcmd not in ("get","list"): -+ bad_usage(cmd,subcmd) -+ return False -+ if args: -+ l = [] -+ for s in args: -+ a = convert2ints(s.split(':')) -+ if len(a) == 2 and not check_range(a): -+ common_err("%s: invalid peinputs range" % a) - return False -- l = crm_report.dotlist(n) -- else: -- l = crm_report.pelist(n) -- if len(l) < abs(idx): -- common_err("pe input or dot file not found") -+ 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) -+ else: -+ print '\n'.join(l) -+ def transition(self,cmd,subcmd,*args): -+ """usage: transition show [] [nograph] [v...] [scores] [actions] [utilization] -+ transition showdot []""" -+ 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": -+ if not user_prefs.dotty: -+ common_err("install graphviz to draw transition graphs") - return False -- if subcmd == "show": -- self.pe_file = l[idx] -- return ptestlike(self.ptest,'vv',"%s %s" % \ -- (cmd, subcmd), *args[startarg:]) -- else: -- show_dot_graph(l[idx]) -+ l = crm_report.dotlist(n) - else: -- bad_usage(cmd,' '.join(subcmd,args)) -+ l = crm_report.pelist(n) -+ if len(l) < abs(idx): -+ common_err("pe input or dot file not found") - return False -+ rc = True -+ if subcmd == "show": -+ self.pe_file = l[idx] -+ rc = ptestlike(self.ptest,'vv',"%s %s" % \ -+ (cmd, subcmd), *args[startarg:]) -+ if rc: -+ crm_report.show_transition_log(self.pe_file) -+ else: -+ show_dot_graph(l[idx]) -+ return rc - - class TopLevel(UserInterface): - ''' - diff --git a/crm_history_pssh.patch b/crm_history_pssh.patch deleted file mode 100644 index f98f681..0000000 --- a/crm_history_pssh.patch +++ /dev/null @@ -1,12 +0,0 @@ -Index: pacemaker/shell/modules/Makefile.am -=================================================================== ---- pacemaker.orig/shell/modules/Makefile.am -+++ pacemaker/shell/modules/Makefile.am -@@ -35,6 +35,7 @@ modules = __init__.py \ - ra.py \ - report.py \ - log_patterns.py \ -+ crm_pssh.py \ - singletonmixin.py \ - template.py \ - term.py \ diff --git a/crm_lrmsecrets_3a81b7eae666.patch b/crm_lrmsecrets_3a81b7eae666.patch deleted file mode 100644 index 84ffafd..0000000 --- a/crm_lrmsecrets_3a81b7eae666.patch +++ /dev/null @@ -1,98 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1313760016 -7200 -# Node ID 3a81b7eae66672dd9873fe6b53ee3c0da6fc87d7 -# Parent e8ea8fb95f310997995576ee831693b0d3b2736a -Medium: Shell: support for LRM secrets in resource level - -diff --git a/doc/crm.8.txt b/doc/crm.8.txt ---- a/doc/crm.8.txt -+++ b/doc/crm.8.txt -@@ -869,6 +869,34 @@ Example: - param ip_0 show ip - ............... - -+[[cmdhelp_resource_secret,manage sensitive parameters]] -+==== `secret` -+ -+Sensitive parameters can be kept in local files rather than CIB -+in order to prevent accidental data exposure. Use the `secret` -+command to manage such parameters. `stash` and `unstash` move the -+value from the CIB and back to the CIB respectively. The `set` -+subcommand sets the parameter to the provided value. `delete` -+removes the parameter completely. `show` displays the value of -+the parameter from the local file. Use `check` to verify if the -+local file content is valid. -+ -+Usage: -+............... -+ secret set -+ secret stash -+ secret unstash -+ secret delete -+ secret show -+ secret check -+............... -+Example: -+............... -+ secret fence_1 show password -+ secret fence_1 stash password -+ secret fence_1 set password secret_value -+............... -+ - [[cmdhelp_resource_meta,manage a meta attribute]] - ==== `meta` - -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 -@@ -661,7 +661,8 @@ def manage_attr(cmd,attr_ext_commands,*a - else: - bad_usage(cmd,' '.join(args)) - return False -- elif args[1] in ('delete','show'): -+ elif args[1] in ('delete','show') or \ -+ (cmd == "secret" and args[1] in ('stash','unstash','check')): - if len(args) == 3: - if not is_name_sane(args[0]) \ - or not is_name_sane(args[2]): -@@ -770,6 +771,14 @@ program. - 'delete': "crm_resource -z -r '%s' -d '%s'", - 'show': "crm_resource -z -r '%s' -g '%s'", - } -+ rsc_secret = { -+ 'set': "cibsecret set '%s' '%s' '%s'", -+ 'stash': "cibsecret stash '%s' '%s'", -+ 'unstash': "cibsecret unstash '%s' '%s'", -+ 'delete': "cibsecret delete '%s' '%s'", -+ 'show': "cibsecret get '%s' '%s'", -+ 'check': "cibsecret check '%s' '%s'", -+ } - rsc_refresh = "crm_resource -R" - rsc_refresh_node = "crm_resource -R -H '%s'" - rsc_reprobe = "crm_resource -P" -@@ -787,6 +796,7 @@ program. - self.cmd_table["migrate"] = (self.migrate,(1,4),0,1) - self.cmd_table["unmigrate"] = (self.unmigrate,(1,1),0,1) - self.cmd_table["param"] = (self.param,(3,4),1,1) -+ self.cmd_table["secret"] = (self.secret,(3,4),1,1) - self.cmd_table["meta"] = (self.meta,(3,4),1,1) - self.cmd_table["utilization"] = (self.utilization,(3,4),1,1) - self.cmd_table["failcount"] = (self.failcount,(3,4),0,0) -@@ -924,6 +934,16 @@ program. - param show """ - d = lambda: manage_attr(cmd,self.rsc_param,*args) - return d() -+ def secret(self,cmd,*args): -+ """usage: -+ secret set -+ secret stash -+ secret unstash -+ secret delete -+ secret show -+ secret check """ -+ d = lambda: manage_attr(cmd,self.rsc_secret,*args) -+ return d() - def meta(self,cmd,*args): - """usage: - meta set diff --git a/crm_pager_f77e52725f2d.patch b/crm_pager_f77e52725f2d.patch deleted file mode 100644 index be59314..0000000 --- a/crm_pager_f77e52725f2d.patch +++ /dev/null @@ -1,28 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1314633641 -7200 -# Node ID f77e52725f2d98c219d8b22208da0b89b3d42112 -# Parent ccd0c1e1edf9f23cafb4363014acba755f1b4e25 -Low: Shell: let the pager decide how to handle output smaller than terminal - -Instead of trying to calculate the size of the output, which may -not be trivial, better let the pager deal with it. For instance, -less(1) can be configured to exit immediately on a -less-than-screenful of input (-F). IIRC, more(1) does that -automatically. - -diff --git a/shell/modules/utils.py b/shell/modules/utils.py ---- a/shell/modules/utils.py -+++ b/shell/modules/utils.py -@@ -524,10 +524,7 @@ def page_string(s): - 'Write string through a pager.' - if not s: - return -- w,h = get_winsize() -- if s.count('\n') < h: -- print s -- elif not user_prefs.pager or not sys.stdout.isatty() or options.batch: -+ if not user_prefs.pager or not sys.stdout.isatty() or options.batch: - print s - else: - opts = "" diff --git a/crm_path_bnc712605.patch b/crm_path_bnc712605.patch deleted file mode 100644 index 8e614a2..0000000 --- a/crm_path_bnc712605.patch +++ /dev/null @@ -1,34 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1313589488 -7200 -# Node ID 0abb257259ed722abaa32a237c3c284c08ec0737 -# Parent 3681d3471fdecde109ea7c25ab2ceb31e1e8646f -Low: Shell: add crm execute directory to the PATH if not already present (bnc#712605) - -Important if crm is run as non-root user. We use sys.argv[0], -but perhaps it'd be better to use autoconf @sbindir@ (or however -it's named) and set it in vars.sbindir. - -diff --git a/shell/modules/main.py b/shell/modules/main.py ---- a/shell/modules/main.py -+++ b/shell/modules/main.py -@@ -16,6 +16,7 @@ - # - - import sys -+import os - import shlex - import getopt - -@@ -205,7 +206,10 @@ vars = Vars.getInstance() - levels = Levels.getInstance() - - # prefer the user set PATH --os.putenv("PATH", "%s:%s" % (os.getenv("PATH"),vars.crm_daemon_dir)) -+mybinpath = os.path.dirname(sys.argv[0]) -+for p in mybinpath, vars.crm_daemon_dir: -+ if p not in os.environ["PATH"].split(':'): -+ os.environ['PATH'] = "%s:%s" % (os.environ['PATH'], p) - - def set_interactive(): - '''Set the interactive option only if we're on a tty.''' diff --git a/crm_site_9b07d41c73b4.patch b/crm_site_9b07d41c73b4.patch deleted file mode 100644 index c41d332..0000000 --- a/crm_site_9b07d41c73b4.patch +++ /dev/null @@ -1,148 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1314872213 -7200 -# Node ID 9b07d41c73b456e8189fea757a5c3d9e5b32512d -# Parent 825cb3e79d7bc1c4ac30468f8c028c9129d00541 -High: Shell: geo-cluster support commands - -diff --git a/doc/crm.8.txt b/doc/crm.8.txt ---- a/doc/crm.8.txt -+++ b/doc/crm.8.txt -@@ -1133,6 +1133,31 @@ Example: - status-attr node_1 show pingd - ............... - -+[[cmdhelp_site,site support]] -+=== `site` -+ -+A cluster may consist of two or more subclusters in different and -+distant locations. This set of commands supports such setups. -+ -+[[cmdhelp_site_ticket,manage site tickets]] -+==== `ticket` -+ -+Tickets are cluster-wide attributes. They can be managed at the -+site where this command is executed. -+ -+It is then possible to constrain resources depending on the -+ticket availability (see the <> command -+for more details). -+ -+Usage: -+............... -+ ticket {grant|revoke|show|time|delete} -+............... -+Example: -+............... -+ ticket grant ticket1 -+............... -+ - [[cmdhelp_options,user preferences]] - === `options` - -@@ -1652,6 +1677,8 @@ resource (or resources) if the ticket is - either `stop` or `demote` depending on whether a resource is - multi-state. - -+See also the <> set of commands. -+ - Usage: - ............... - rsc_ticket : [:] [[:] ...] -diff --git a/shell/modules/completion.py b/shell/modules/completion.py ---- a/shell/modules/completion.py -+++ b/shell/modules/completion.py -@@ -173,6 +173,10 @@ def report_pe_list_peinputs(idx,delimite - if delimiter: - return ' ' - return crm_report.peinputs_list() + ["v"] -+def ticket_cmd_list(idx,delimiter = False): -+ if delimiter: -+ return ' ' -+ return ["grant","revoke","show","time","delete"] - - # - # completion for primitives including help for parameters -@@ -488,6 +492,9 @@ completer_lists = { - "peinputs" : (report_pe_list_peinputs,loop), - "transition" : (report_pe_list_transition,), - }, -+ "site" : { -+ "ticket" : (ticket_cmd_list,), -+ }, - } - def get_completer_list(level,cmd): - 'Return a list of completer functions.' -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 -@@ -1938,6 +1938,61 @@ Examine Pacemaker's history: node and re - crm_report.show_transition_log(f) - return rc - -+class Site(UserInterface): -+ ''' -+ The site class -+ ''' -+ lvl_name = "site" -+ desc_short = "Geo-cluster support" -+ desc_long = """ -+The site level. -+ -+Geo-cluster related management. -+""" -+ crm_ticket = { -+ 'grant': "crm_ticket -t '%s' -v true", -+ 'revoke': "crm_ticket -t '%s' -v false", -+ 'delete': "crm_ticket -t '%s' -D", -+ 'show': "crm_ticket -t '%s' -G", -+ 'time': "crm_ticket -t '%s' -T", -+ } -+ def __init__(self): -+ UserInterface.__init__(self) -+ self.cmd_table["ticket"] = (self.ticket,(2,2),1,0) -+ def ticket(self, cmd, subcmd, ticket): -+ "usage: ticket {grant|revoke|show|time|delete} " -+ try: -+ attr_cmd = self.crm_ticket[subcmd] -+ except: -+ bad_usage(cmd,'%s %s' % (subcmd, ticket)) -+ return False -+ if not is_name_sane(ticket): -+ return False -+ if subcmd not in ("show", "time"): -+ return ext_cmd(attr_cmd % ticket) == 0 -+ l = stdout2list(attr_cmd % ticket) -+ try: -+ val = l[0].split('=')[3] -+ except: -+ common_warn("apparently nothing to show for ticket %s" % ticket) -+ return False -+ if subcmd == "show": -+ if val == "false": -+ print "ticket %s is revoked" % ticket -+ elif val == "true": -+ print "ticket %s is granted" % ticket -+ else: -+ common_warn("unexpected value for ticket %s: %s" % (ticket, val)) -+ return False -+ else: # time -+ if not is_int(val): -+ common_warn("unexpected value for ticket %s: %s" % (ticket, val)) -+ return False -+ if val == "-1": -+ print "%s: no such ticket" % ticket -+ return False -+ print "ticket %s last time granted on %s" % (ticket, time.ctime(int(val))) -+ - class TopLevel(UserInterface): - ''' - The top level. -@@ -1959,6 +2014,7 @@ class TopLevel(UserInterface): - self.cmd_table['node'] = NodeMgmt - self.cmd_table['options'] = CliOptions - self.cmd_table['history'] = History -+ self.cmd_table['site'] = Site - self.cmd_table['status'] = (self.status,(0,5),0,0) - self.cmd_table['ra'] = RA - setup_aliases(self) diff --git a/crm_tickets_825cb3e79d7b.patch b/crm_tickets_825cb3e79d7b.patch deleted file mode 100644 index fe01811..0000000 --- a/crm_tickets_825cb3e79d7b.patch +++ /dev/null @@ -1,326 +0,0 @@ -# HG changeset patch -# User Dejan Muhamedagic -# Date 1314783705 -7200 -# Node ID 825cb3e79d7bc1c4ac30468f8c028c9129d00541 -# Parent f77e52725f2d98c219d8b22208da0b89b3d42112 -High: Shell: support for rsc_ticket - -diff --git a/doc/crm.8.txt b/doc/crm.8.txt ---- a/doc/crm.8.txt -+++ b/doc/crm.8.txt -@@ -1639,6 +1639,34 @@ Example: - order o1 inf: A ( B C ) - ............... - -+[[cmdhelp_configure_rsc_ticket,resources ticket dependency]] -+==== `rsc_ticket` -+ -+This constraint expresses dependency of resources on cluster-wide -+attributes, also known as tickets. Tickets are mainly used in -+geo-clusters, which consist of multiple sites. A ticket may be -+granted to a site, thus allowing resources to run there. -+ -+The `loss-policy` attribute specifies what happens to the -+resource (or resources) if the ticket is revoked. The default is -+either `stop` or `demote` depending on whether a resource is -+multi-state. -+ -+Usage: -+............... -+ rsc_ticket : [:] [[:] ...] -+ [loss-policy=] -+ -+ loss_policy_action :: stop | demote | fence | freeze -+............... -+Example: -+............... -+ rsc_ticket ticket-A_public-ip ticket-A: public-ip -+ rsc_ticket ticket-A_bigdb ticket-A: bigdb loss-policy=fence -+ rsc_ticket ticket-B_storage ticket-B: drbd-a:Master drbd-b:Master -+............... -+ -+ - [[cmdhelp_configure_property,set a cluster property]] - ==== `property` - -diff --git a/shell/modules/cibconfig.py b/shell/modules/cibconfig.py ---- a/shell/modules/cibconfig.py -+++ b/shell/modules/cibconfig.py -@@ -1243,7 +1243,7 @@ class CibSimpleConstraint(CibObject): - if node.getElementsByTagName("resource_set"): - col = rsc_set_constraint(node,obj_type) - else: -- col = two_rsc_constraint(node,obj_type) -+ col = simple_rsc_constraint(node,obj_type) - if not col: - return None - symm = node.getAttribute("symmetrical") -@@ -1264,6 +1264,27 @@ class CibSimpleConstraint(CibObject): - remove_id_used_attributes(oldnode) - return headnode - -+class CibRscTicket(CibSimpleConstraint): -+ ''' -+ rsc_ticket constraint. -+ ''' -+ def repr_cli_head(self,node): -+ obj_type = vars.cib_cli_map[node.tagName] -+ node_id = node.getAttribute("id") -+ s = cli_display.keyword(obj_type) -+ id = cli_display.id(node_id) -+ ticket = cli_display.ticket(node.getAttribute("ticket")) -+ if node.getElementsByTagName("resource_set"): -+ col = rsc_set_constraint(node,obj_type) -+ else: -+ col = simple_rsc_constraint(node,obj_type) -+ if not col: -+ return None -+ a = node.getAttribute("loss-policy") -+ if a: -+ col.append("loss-policy=%s" % a) -+ return "%s %s %s: %s" % (s,id,ticket,' '.join(col)) -+ - class CibProperty(CibObject): - ''' - Cluster properties. -@@ -1371,6 +1392,7 @@ cib_object_map = { - "rsc_location": ( "location", CibLocation, "constraints" ), - "rsc_colocation": ( "colocation", CibSimpleConstraint, "constraints" ), - "rsc_order": ( "order", CibSimpleConstraint, "constraints" ), -+ "rsc_ticket": ( "rsc_ticket", CibRscTicket, "constraints" ), - "cluster_property_set": ( "property", CibProperty, "crm_config", "cib-bootstrap-options" ), - "rsc_defaults": ( "rsc_defaults", CibProperty, "rsc_defaults", "rsc-options" ), - "op_defaults": ( "op_defaults", CibProperty, "op_defaults", "op-options" ), -diff --git a/shell/modules/clidisplay.py b/shell/modules/clidisplay.py ---- a/shell/modules/clidisplay.py -+++ b/shell/modules/clidisplay.py -@@ -62,6 +62,8 @@ class CliDisplay(Singleton): - return self.otherword(4, s) - def score(self, s): - return self.otherword(5, s) -+ def ticket(self, s): -+ return self.otherword(5, s) - - user_prefs = UserPrefs.getInstance() - vars = Vars.getInstance() -diff --git a/shell/modules/cliformat.py b/shell/modules/cliformat.py ---- a/shell/modules/cliformat.py -+++ b/shell/modules/cliformat.py -@@ -226,22 +226,25 @@ def rsc_set_constraint(node,obj_type): - action = n.getAttribute("action") - for r in n.getElementsByTagName("resource_ref"): - rsc = cli_display.rscref(r.getAttribute("id")) -- q = (obj_type == "colocation") and role or action -+ q = (obj_type == "order") and action or role - col.append(q and "%s:%s"%(rsc,q) or rsc) - cnt += 1 - if not sequential: - col.append(")") -- if cnt <= 2: # a degenerate thingie -+ if (obj_type != "rsc_ticket" and cnt <= 2) or \ -+ (obj_type == "rsc_ticket" and cnt <= 1): # a degenerate thingie - col.insert(0,"_rsc_set_") - return col --def two_rsc_constraint(node,obj_type): -+def simple_rsc_constraint(node,obj_type): - col = [] - if obj_type == "colocation": - col.append(mkrscrole(node,"rsc")) - col.append(mkrscrole(node,"with-rsc")) -- else: -+ elif obj_type == "order": - col.append(mkrscaction(node,"first")) - col.append(mkrscaction(node,"then")) -+ else: # rsc_ticket -+ col.append(mkrscrole(node,"rsc")) - return col - - # this pre (or post)-processing is oversimplified -diff --git a/shell/modules/completion.py b/shell/modules/completion.py ---- a/shell/modules/completion.py -+++ b/shell/modules/completion.py -@@ -467,6 +467,7 @@ completer_lists = { - "location" : (null_list,rsc_id_list), - "colocation" : (null_list,null_list,rsc_id_list,loop), - "order" : (null_list,null_list,rsc_id_list,loop), -+ "rsc_ticket" : (null_list,null_list,rsc_id_list,loop), - "property" : (property_complete,loop), - "rsc_defaults" : (prim_complete_meta,loop), - "op_defaults" : (op_attr_list,loop), -diff --git a/shell/modules/parse.py b/shell/modules/parse.py ---- a/shell/modules/parse.py -+++ b/shell/modules/parse.py -@@ -178,6 +178,15 @@ def parse_op(s): - head_pl.append(["name",s[0]]) - return cli_list - -+def cli_parse_ticket(ticket,pl): -+ if ticket.endswith(':'): -+ ticket = ticket.rstrip(':') -+ else: -+ syntax_err(ticket, context = 'rsc_ticket') -+ return False -+ pl.append(["ticket",ticket]) -+ return True -+ - def cli_parse_score(score,pl,noattr = False): - if score.endswith(':'): - score = score.rstrip(':') -@@ -197,6 +206,7 @@ def cli_parse_score(score,pl,noattr = Fa - else: - pl.append(["score-attribute",score]) - return True -+ - def is_binary_op(s): - l = s.split(':') - if len(l) == 2: -@@ -302,13 +312,13 @@ def parse_location(s): - return False - return cli_list - --def cli_opt_symmetrical(p,pl): -+def cli_opt_attribute(type, p, pl, attr): - if not p: - return True - pl1 = [] - cli_parse_attr([p],pl1) -- if len(pl1) != 1 or not find_value(pl1,"symmetrical"): -- syntax_err(p,context = "order") -+ if len(pl1) != 1 or not find_value(pl1, attr): -+ syntax_err(p,context = type) - return False - pl += pl1 - return True -@@ -490,7 +500,33 @@ def parse_order(s): - resource_set_obj = ResourceSet(type,s[3:],cli_list) - if not resource_set_obj.parse(): - return False -- if not cli_opt_symmetrical(symm,head_pl): -+ if not cli_opt_attribute(type, symm, head_pl, "symmetrical"): -+ return False -+ return cli_list -+ -+def parse_rsc_ticket(s): -+ cli_list = [] -+ head_pl = [] -+ type = "rsc_ticket" -+ cli_list.append([s[0],head_pl]) -+ if len(s) < 4: -+ syntax_err(s,context = "rsc_ticket") -+ return False -+ head_pl.append(["id",s[1]]) -+ if not cli_parse_ticket(s[2],head_pl): -+ return False -+ # save loss-policy for later (if it exists) -+ loss_policy = "" -+ if is_attribute(s[len(s)-1],"loss-policy"): -+ loss_policy = s.pop() -+ if len(s) == 4: -+ if not cli_parse_rsc_role(s[3], head_pl): -+ return False -+ else: -+ resource_set_obj = ResourceSet(type, s[3:], cli_list) -+ if not resource_set_obj.parse(): -+ return False -+ if not cli_opt_attribute(type, loss_policy, head_pl, attr = "loss-policy"): - return False - return cli_list - -@@ -501,6 +537,8 @@ def parse_constraint(s): - return parse_colocation(s) - elif keyword_cmp(s[0], "order"): - return parse_order(s) -+ elif keyword_cmp(s[0], "rsc_ticket"): -+ return parse_rsc_ticket(s) - def parse_property(s): - cli_list = [] - head_pl = [] -@@ -708,6 +746,7 @@ class CliParser(object): - "colocation": (3,parse_constraint), - "collocation": (3,parse_constraint), - "order": (3,parse_constraint), -+ "rsc_ticket": (3,parse_constraint), - "monitor": (3,parse_op), - "node": (2,parse_node), - "property": (2,parse_property), -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 -@@ -1400,6 +1400,7 @@ cluster. - self.cmd_table["location"] = (self.conf_location,(2,),1,0) - self.cmd_table["colocation"] = (self.conf_colocation,(2,),1,0) - self.cmd_table["order"] = (self.conf_order,(2,),1,0) -+ self.cmd_table["rsc_ticket"] = (self.conf_rsc_ticket,(2,),1,0) - self.cmd_table["property"] = (self.conf_property,(1,),1,0) - self.cmd_table["rsc_defaults"] = (self.conf_rsc_defaults,(1,),1,0) - self.cmd_table["op_defaults"] = (self.conf_op_defaults,(1,),1,0) -@@ -1632,6 +1633,10 @@ cluster. - """usage: order score-type: [:] [:] - [symmetrical=]""" - return self.__conf_object(cmd,*args) -+ def conf_rsc_ticket(self,cmd,*args): -+ """usage: rsc_ticket : [:] [[:] ...] -+ [loss-policy=]""" -+ return self.__conf_object(cmd,*args) - def conf_property(self,cmd,*args): - "usage: property [$id=]