Hello community,

here is the log from the commit of package crmsh for openSUSE:Factory checked 
in at 2015-09-13 09:45:11
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/crmsh (Old)
 and      /work/SRC/openSUSE:Factory/.crmsh.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "crmsh"

Changes:
--------
--- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes      2015-09-08 
17:44:35.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.crmsh.new/crmsh.changes 2015-09-13 
09:45:18.000000000 +0200
@@ -1,0 +2,14 @@
+Fri Sep 11 10:21:55 UTC 2015 - kgronl...@suse.com
+
+- Update to version 2.2.0~rc3+git.1441965248.a9a616d:
+  + low: scripts: Fix typo in email type verifier
+  + low: scripts: [MailTo] install mailx package
+  + high: scripts: Add enum type to script values
+  + medium: parse: Add support for node attribute as fencing topology target
+  + doc: Improve documentation for the history level
+  + low: ui_history: Swap from and to times if to < from
+  + low: ui_history: Better error handling and documentation for the detail 
command
+  + medium: report: Add transition tags command (bsc#943470)
+  + medium: report: Mark transitions with errors with a star in info output 
(bsc#943470)
+
+-------------------------------------------------------------------

Old:
----
  crmsh-2.2.0~rc3+git.1441319359.d823416.tar.bz2

New:
----
  crmsh-2.2.0~rc3+git.1441965248.a9a616d.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ crmsh.spec ++++++
--- /var/tmp/diff_new_pack.cU0IDa/_old  2015-09-13 09:45:19.000000000 +0200
+++ /var/tmp/diff_new_pack.cU0IDa/_new  2015-09-13 09:45:19.000000000 +0200
@@ -36,7 +36,7 @@
 Summary:        High Availability cluster command-line interface
 License:        GPL-2.0+
 Group:          %{pkg_group}
-Version:        2.2.0~rc3+git.1441319359.d823416
+Version:        2.2.0~rc3+git.1441965248.a9a616d
 Release:        0
 Url:            http://crmsh.github.io
 Source0:        %{name}-%{version}.tar.bz2

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.cU0IDa/_old  2015-09-13 09:45:19.000000000 +0200
+++ /var/tmp/diff_new_pack.cU0IDa/_new  2015-09-13 09:45:19.000000000 +0200
@@ -1,4 +1,4 @@
 <servicedata>
 <service name="tar_scm">
             <param name="url">git://github.com/ClusterLabs/crmsh.git</param>
-          <param 
name="changesrevision">d823416786d3e5c66d27d6de2fe43228df22fdac</param></service></servicedata>
\ No newline at end of file
+          <param 
name="changesrevision">a9a616da75689af2b80f39d530e99e8aa855da64</param></service></servicedata>
\ No newline at end of file

++++++ crmsh-2.2.0~rc3+git.1441319359.d823416.tar.bz2 -> 
crmsh-2.2.0~rc3+git.1441965248.a9a616d.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-2.2.0~rc3+git.1441319359.d823416/data-manifest 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/data-manifest
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/data-manifest    2015-09-04 
10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/data-manifest    2015-09-11 
12:21:55.000000000 +0200
@@ -30,6 +30,7 @@
 scripts/init/verify.py
 scripts/libvirt/main.yml
 scripts/lvm/main.yml
+scripts/mailto/main.yml
 scripts/nfsserver/main.yml
 scripts/ocfs2/main.yml
 scripts/oracle/main.yml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/doc/crm.8.adoc 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/doc/crm.8.adoc
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/doc/crm.8.adoc   2015-09-04 
10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/doc/crm.8.adoc   2015-09-11 
12:21:55.000000000 +0200
@@ -2755,14 +2755,19 @@
 If the node is left out, the order is used for all nodes.
 That should reduce the configuration size in some stonith setups.
 
+From Pacemaker version 1.1.14, it is possible to use a node attribute
+as the +target+ in a fencing topology. The syntax for this usage is
+described below.
+
 Usage:
 ...............
 fencing_topology stonith_resources [stonith_resources ...]
 fencing_topology fencing_order [fencing_order ...]
 
-fencing_order :: <node>: stonith_resources [stonith_resources ...]
+fencing_order :: target stonith_resources [stonith_resources ...]
 
 stonith_resources :: <rsc>[,<rsc>...]
+target :: <node>: | attr:<node-attribute>=<value>
 ...............
 Example:
 ...............
@@ -2770,6 +2775,7 @@
 fencing_topology \
     node-a: poison-pill power
     node-b: ipmi serial
+fencing_topology attr:rack=1 apc01,apc02
 ...............
 
 [[cmdhelp_configure_filter,filter CIB objects]]
@@ -4220,34 +4226,42 @@
 [[cmdhelp_history,Cluster history]]
 === `history` - Cluster 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 the python parallax 
module. If
-+python-parallax+ 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.
-
-If you think you may have found a bug or just need clarification
-from developers or your support, the `session pack` command can
-help create a report.
+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 the 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 `crm report`. Since this process takes some time and
+we always need fresh logs, information is refreshed in a much faster
+way using the python parallax module. If +python-parallax+ is not
+found on the system, examining a live cluster is still possible --
+though not as comfortable.
+
+Apart from examining a live cluster, events may be retrieved from a
+report generated by `crm 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.
+
+If you have discovered an issue that you want to show someone else,
+you can use the `session pack` command to save the current session as
+a tarball, similar to those generated by `crm report`.
+
+In order to minimize the size of the tarball, and to make it easier
+for others to find the interesting events, it is recommended to limit
+the time frame which the saved session covers. This can be done using
+the `timeframe` command (example below).
+
+It is also possible to name the saved session using the `session save`
+command.
 
 Example:
 ...............
@@ -4258,14 +4272,12 @@
 crm(live)history#
 ...............
 
-In order to reduce report size and allow developers to
-concentrate on the issue, you should beforehand limit the time
-frame. Giving a meaningful session name helps too.
-
 [[cmdhelp_history_detail,set the level of detail shown]]
 ==== `detail`
 
-How much detail to show from the logs.
+How much detail to show from the logs. Valid detail levels are either
+`0` or `1`, where `1` is the highest detail level. The default detail
+level is `0`.
 
 Usage:
 ...............
@@ -4383,30 +4395,33 @@
 [[cmdhelp_history_limit,limit timeframe to be examined]]
 ==== `limit` (`timeframe`)
 
-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.
+This command can be used to modify the time span to examine. All
+history commands look at events within a certain time span.
+
+For the `live` source, the default time span is the _last hour_.
 
-The time period is parsed by the dateutil python module. It
-covers wide range of date formats. For instance:
+There is no time span limit for the `hb_report` source.
 
-- 3:00      (today at 3am)
-- 15:00     (today at 3pm)
+The time period is parsed by the `dateutil` python module. It
+covers a 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.
+For more examples of valid time/date statements, please refer to the
+`python-dateutil` documentation:
+
+- https://dateutil.readthedocs.org/[dateutil.readthedocs.org]
 
-If dateutil is not available, then the time is parsed using
+If the dateutil module 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 [<from_time> [<to_time>]]
+limit [<from_time>] [<to_time>]
 ...............
 Examples:
 ...............
@@ -4634,12 +4649,18 @@
 After the `ptest` output, logs about events that happened during
 the transition are printed.
 
+The `tags` subcommand scans the logs for the transition and return a
+list of key events during that transition. For example, the tag
++error+ will be returned if there are any errors logged during the
+transition.
+
 Usage:
 ...............
 transition [<number>|<index>|<file>] [nograph] [v...] [scores] [actions] 
[utilization]
 transition showdot [<number>|<index>|<file>]
 transition log [<number>|<index>|<file>]
 transition save [<number>|<index>|<file> [name]]
+transition tags [<number>|<index>|<file>]
 ...............
 Examples:
 ...............
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/cibconfig.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/cibconfig.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/cibconfig.py     
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/cibconfig.py     
2015-09-11 12:21:55.000000000 +0200
@@ -1928,7 +1928,10 @@
         s = clidisplay.keyword(self.obj_type)
         d = odict()
         for c in self.node.iterchildren("fencing-level"):
-            target = c.get("target")
+            if "target-attribute" in c.attrib:
+                target = (c.get("target-attribute"), c.get("target-value"))
+            else:
+                target = c.get("target")
             if target not in d:
                 d[target] = {}
             d[target][c.get("index")] = c.get("devices")
@@ -1942,7 +1945,13 @@
             d2[devs_s] = 1
         if len(d2) == 1 and len(d) == len(cib_factory.node_id_list()):
             return "%s %s" % (s, devs_s)
-        return cli_format([s] + ["%s: %s" % (x, ' '.join(dd[x]))
+
+        def fmt_target(tgt):
+            if isinstance(tgt, tuple):
+                return "attr:%s=%s" % tgt
+            else:
+                return tgt + ":"
+        return cli_format([s] + ["%s %s" % (fmt_target(x), ' '.join(dd[x]))
                                  for x in dd.keys()],
                           break_lines=(format > 0))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/parse.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/parse.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/parse.py 2015-09-04 
10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/parse.py 2015-09-11 
12:21:55.000000000 +0200
@@ -961,9 +961,22 @@
     <fencing-topology>
     <fencing-level id=<id> target=<text> index=<+int> devices="\w,\w..."/>
     </fencing-topology>
+
+    new:
+
+    from 1.1.14 on, target can be a node attribute value mapping:
+
+    attr:<name>=<value> maps to XML:
+
+    <fencing-topology>
+    <fencing-level id=<id> target-attribute=<text> target-value=<text>
+                   index=<+int> devices="\w,\w..."/>
+    </fencing-topology>
+
     """
 
-    _TARGET_RE = re.compile(r'([^:]+):$')
+    _TARGET_RE = re.compile(r'([\w=-]+):$')
+    _TARGET_ATTR_RE = re.compile(r'attr:([\w-]+)=([\w-]+)$')
 
     def can_parse(self):
         return ('fencing-topology', 'fencing_topology')
@@ -976,7 +989,9 @@
         # (target, devices)
         raw_levels = []
         while self.has_tokens():
-            if self.try_match(self._TARGET_RE):
+            if self.try_match(self._TARGET_ATTR_RE):
+                target = (self.matched(1), self.matched(2))
+            elif self.try_match(self._TARGET_RE):
                 target = self.matched(1)
             else:
                 raw_levels.append((target, self.match_any()))
@@ -995,16 +1010,26 @@
                         yield node, devices
             lvl_generator = node_levels
         else:
-            lvl_generator = lambda: raw_levels
+            def wrap_levels():
+                return raw_levels
+            lvl_generator = wrap_levels
 
         out = xmlbuilder.new('fencing-topology')
         targets = defaultdict(repeat(1).next)
         for target, devices in lvl_generator():
-            xmlbuilder.child(out, 'fencing-level',
-                             target=target,
-                             index=str(targets[target]),
-                             devices=devices)
-            targets[target] += 1
+            if isinstance(target, tuple):
+                c = xmlbuilder.child(out, 'fencing-level',
+                                     index=str(targets[target[0]]),
+                                     devices=devices)
+                c.set('target-attribute', target[0])
+                c.set('target-value', target[1])
+                targets[target[0]] += 1
+            else:
+                xmlbuilder.child(out, 'fencing-level',
+                                 target=target,
+                                 index=str(targets[target]),
+                                 devices=devices)
+                targets[target] += 1
 
         return out
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/report.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/report.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/report.py        
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/report.py        
2015-09-11 12:21:55.000000000 +0200
@@ -17,12 +17,12 @@
 from .utils import page_string, release_lock, rmdir_r, parse_time, 
get_cib_attributes
 from .utils import is_pcmk_118, pipe_cmd_nosudo, file_find_by_name
 
-_NO_PARALLAX = False
-
+_HAS_PARALLAX = False
 try:
     from .crm_pssh import next_loglines, next_peinputs
+    _HAS_PARALLAX = True
 except:
-    _NO_PARALLAX = True
+    pass
 
 
 YEAR = None
@@ -497,13 +497,17 @@
         s = s[r.end():]
 
 
-def extract_pe_file(msg):
+def get_pe_file_num_from_msg(msg):
+    """
+    Get PE file name and number from log message
+    Returns: (file, num)
+    """
     msg_a = msg.split()
     if len(msg_a) < 5:
         # this looks too short
         common_warn("log message <%s> unexpected format, please report a bug" 
% msg)
-        return ""
-    return msg_a[-1]
+        return ("", "-1")
+    return (msg_a[-1], get_pe_num(msg_a[-1]))
 
 
 def transition_start_re(number_re):
@@ -552,13 +556,12 @@
     return None
 
 
-def get_matching_run_msg(te_invoke_msg, trans_msg_l):
+def find_transition_end_msg(transition_start_msg, trans_msg_l):
     """
     Given the start of a transition log message, find
     and return the end of the transition log messages.
     """
-    pe_file = extract_pe_file(te_invoke_msg)
-    pe_num = get_pe_num(pe_file)
+    pe_file, pe_num = get_pe_file_num_from_msg(transition_start_msg)
     if pe_num == "-1":
         common_warn("%s: strange, transition number not found" % pe_file)
         return ""
@@ -566,7 +569,7 @@
 
 
 def trans_str(node, pe_file):
-    '''Convert node,pe_file to transition sting.'''
+    '''Convert node,pe_file to transition string.'''
     return "%s:%s" % (node, os.path.basename(pe_file).replace(".bz2", ""))
 
 
@@ -581,29 +584,28 @@
     Capture transition related information.
     '''
 
-    def __init__(self, te_invoke_msg, run_msg):
-        self.te_invoke_msg = te_invoke_msg
-        self.run_msg = run_msg
-        self.parse_msgs()
+    def __init__(self, start_msg, end_msg):
+        self.start_msg = start_msg
+        self.end_msg = end_msg
+        self.tags = set()
+        self.pe_file, self.pe_num = get_pe_file_num_from_msg(start_msg)
+        self.dc = syslog2node(start_msg)
+        self.start_ts = syslog_ts(start_msg)
+        if end_msg:
+            self.end_ts = syslog_ts(end_msg)
+        else:
+            common_warn("end of transition %s not found in logs (transition 
not complete yet?)" % self)
+            self.end_ts = self.start_ts
 
     def __str__(self):
-        return trans_str(self.dc, self.pe_file)
+        return self.get_node_file()
 
-    def parse_msgs(self):
-        self.pe_file = extract_pe_file(self.te_invoke_msg)
-        self.pe_num = get_pe_num(self.pe_file)
-        self.dc = syslog2node(self.te_invoke_msg)
-        self.start_ts = syslog_ts(self.te_invoke_msg)
-        if self.run_msg:
-            self.end_ts = syslog_ts(self.run_msg)
-        else:
-            common_warn("end of transition %s not found in logs (transition 
not complete yet?)" %
-                        self)
-            self.end_ts = self.start_ts
+    def get_node_file(self):
+        return trans_str(self.dc, self.pe_file)
 
     def actions_count(self):
-        if self.run_msg:
-            act_d = run_graph_msg_actions(self.run_msg)
+        if self.end_msg:
+            act_d = run_graph_msg_actions(self.end_msg)
             return sum(act_d.values())
         else:
             return -1
@@ -613,9 +615,9 @@
 
     def transition_info(self):
         print "Transition %s (%s -" % (self, shorttime(self.start_ts)),
-        if self.run_msg:
+        if self.end_msg:
             print "%s):" % shorttime(self.end_ts)
-            act_d = run_graph_msg_actions(self.run_msg)
+            act_d = run_graph_msg_actions(self.end_msg)
             total = sum(act_d.values())
             s = ", ".join(["%d %s" % (act_d[x], x) for x in act_d if act_d[x]])
             print "\ttotal %d actions: %s" % (total, s)
@@ -677,7 +679,7 @@
         self.nodecolor = {}
         self.logobj = None
         self.desc = None
-        self.peinputs_l = []
+        self._transitions = []
         self.cibgrp_d = {}
         self.cibcln_d = {}
         self.cibrsc_l = []
@@ -688,7 +690,7 @@
         self.detail = 0
         self.log_filter_out = []
         self.log_filter_out_re = []
-        # change_origin may be CH_SRC, CH_TIME, CH_UPD
+        # change_origin may be 0, CH_SRC, CH_TIME, CH_UPD
         # depending on the change_origin, we update our attributes
         self.change_origin = CH_SRC
         set_year()
@@ -706,7 +708,7 @@
         return self.node_l
 
     def peinputs_list(self):
-        return [x.pe_num for x in self.peinputs_l]
+        return [x.pe_num for x in self._transitions]
 
     def session_subcmd_list(self):
         return ["save", "load", "pack", "delete", "list", "update"]
@@ -934,7 +936,7 @@
                 continue
             pe_l = []
             for new_t_obj in self.list_transitions(log_l, future_pe=True):
-                self.new_peinput(new_t_obj)
+                self._new_transition(new_t_obj)
                 pe_l.append(new_t_obj.pe_file)
             if pe_l:
                 node_pe_l.append([node, pe_l])
@@ -973,7 +975,7 @@
             # try just to refresh the live report
             if self.to_dt or self.is_live_very_recent() or no_live_update:
                 return self._live_loc()
-            if not _NO_PARALLAX:
+            if _HAS_PARALLAX:
                 if not acquire_lock(self.report_cache_dir):
                     return None
                 rc = self.update_live_report()
@@ -989,6 +991,13 @@
         '''
         Run hb_report to get logs now.
         '''
+        from . import ui_report
+
+        extcmd = ui_report.report_tool()
+        if extcmd is None:
+            self.error("No reporting tool found")
+            return None
+
         d = self._live_loc()
         rmdir_r(d)
         tarball = "%s.tar.bz2" % d
@@ -1000,12 +1009,7 @@
             nodes_option = "'-n %s'" % ' '.join(self.setnodes)
         if pipe_cmd_nosudo("mkdir -p %s" % os.path.dirname(d)) != 0:
             return None
-        common_info("retrieving information from cluster nodes, please wait 
...")
-        from . import ui_report
-        extcmd = ui_report.report_tool()
-        if extcmd is None:
-            self.error("No reporting tool found")
-            return None
+        common_info("Retrieving information from cluster nodes, please 
wait...")
         rc = pipe_cmd_nosudo("%s -Z -Q -f '%s' %s %s %s %s" %
                              (extcmd,
                               self.from_dt.ctime(),
@@ -1046,7 +1050,7 @@
             refresh = from_dt and top_dt > from_dt
         if refresh:
             self.set_change_origin(CH_UPD)
-            self.refresh_source(force=True)
+            return self.refresh_source(force=True)
         else:
             self.set_change_origin(CH_TIME)
             self.report_setup()
@@ -1102,13 +1106,13 @@
                 pass
         self.cibnotcloned_l = [x for x in self.cibrsc_l if x not in 
self.cibcloned_l]
 
-    def new_peinput(self, new_pe):
-        t_obj = self.find_peinput(str(new_pe))
+    def _new_transition(self, transition):
+        t_obj = self.find_transition(transition.get_node_file())
         if t_obj:
-            common_debug("duplicate %s, replacing older PE file" % t_obj)
-            self.peinputs_l.remove(t_obj)
-        common_debug("appending new PE %s" % new_pe)
-        self.peinputs_l.append(new_pe)
+            common_debug("duplicate %s, replacing older PE file" % transition)
+            self._transitions.remove(t_obj)
+        common_debug("appending new PE %s" % transition)
+        self._transitions.append(transition)
 
     def set_node_colors(self):
         i = 0
@@ -1164,11 +1168,12 @@
         trans_start_msg_l = self.get_invoke_trans_msgs(trans_msg_l)
         prev_transition = None
         for msg in trans_start_msg_l:
-            run_msg = get_matching_run_msg(msg, trans_msg_l)
-            t_obj = Transition(msg, run_msg)
+            transition_end_msg = find_transition_end_msg(msg, trans_msg_l)
+            t_obj = Transition(msg, transition_end_msg)
             if self.is_empty_transition(prev_transition, t_obj):
                 common_debug("skipping empty transition (%s)" % t_obj)
                 continue
+            self._set_transition_tags(t_obj)
             if not future_pe:
                 pe_l_file = self.pe_report_path(t_obj)
                 if not os.path.isfile(pe_l_file):
@@ -1178,52 +1183,62 @@
             prev_transition = t_obj
             yield t_obj
 
-    def report_setup(self):
-        if not self.change_origin:
-            return
-        if not self.loc:
+    def _report_setup_source(self):
+        constants.pcmk_version = None
+        # is this an hb_report or a crm_report?
+        for descname in ("description.txt", "report.summary"):
+            self.desc = os.path.join(self.loc, descname)
+            if os.path.isfile(self.desc):
+                yr = os.stat(self.desc).st_mtime
+                common_debug("Found %s, created %s" % (descname, yr))
+                self._creation_time = time.strftime("%a %d %b %H:%M:%S %Z %Y",
+                                                    time.localtime(yr))
+                if descname == 'report.summary':
+                    self._creator = "crm_report"
+                else:
+                    self._creator = 'unknown'
+                set_year(yr)
+                break
+        else:
+            self.error("Invalid report: No description found")
             return
-        if self.change_origin == CH_SRC:
-            constants.pcmk_version = None
-            # is this an hb_report or a crm_report?
-            for descname in ("description.txt", "report.summary"):
-                self.desc = os.path.join(self.loc, descname)
-                if os.path.isfile(self.desc):
-                    yr = os.stat(self.desc).st_mtime
-                    common_debug("Found %s, created %s" % (descname, yr))
-                    self._creation_time = time.strftime("%a %d %b %H:%M:%S %Z 
%Y",
-                                                        time.localtime(yr))
-                    if descname == 'report.summary':
-                        self._creator = "crm_report"
-                    else:
-                        self._creator = 'unknown'
-                    set_year(yr)
-                    break
-            else:
-                self.error("Invalid report: No description found")
-                return
 
-            self.node_l = self.get_nodes()
+        self.node_l = self.get_nodes()
+        self.set_node_colors()
+        self.log_l = self.find_logs()
+        self.find_central_log()
+        self.read_cib()
+
+    def _report_setup_update(self):
+        l = self.get_nodes()
+        if self.node_l != l:
+            self.node_l = l
             self.set_node_colors()
             self.log_l = self.find_logs()
-            self.find_central_log()
             self.read_cib()
+
+    def report_setup(self):
+        if self.change_origin == 0:
+            return False
+        if not self.loc:
+            return False
+
+        if self.change_origin == CH_SRC:
+            self._report_setup_source()
         elif self.change_origin == CH_UPD:
-            l = self.get_nodes()
-            if self.node_l != l:
-                self.node_l = l
-                self.set_node_colors()
-                self.log_l = self.find_logs()
-                self.read_cib()
+            self._report_setup_update()
+
         self.logobj = LogSyslog(self.central_log,
                                 self.log_l,
                                 self.from_dt,
                                 self.to_dt)
+
         if self.change_origin != CH_UPD:
             common_debug("getting transitions from logs")
-            self.peinputs_l = []
+            self._transitions = []
             for new_t_obj in self.list_transitions():
-                self.new_peinput(new_t_obj)
+                self._new_transition(new_t_obj)
+
         self.ready = self.check_report()
         self.set_change_origin(0)
 
@@ -1356,11 +1371,15 @@
         output'''
         max_output = 20
         s = ""
-        if len(self.peinputs_l) > max_output:
+        if len(self._transitions) > max_output:
             s = "... "
-        return "%s%s" % (s,
-                         ' '.join([self._str_nodecolor(x.dc, x.pe_num)
-                                   for x in self.peinputs_l[-max_output:]]))
+
+        def fmt(t):
+            if 'error' in t.tags:
+                return self._str_nodecolor(t.dc, t.pe_num) + "*"
+            return self._str_nodecolor(t.dc, t.pe_num)
+
+        return "%s%s" % (s, ' '.join([fmt(x) for x in 
self._transitions[-max_output:]]))
 
     def get_rpt_dt(self, dt, whence):
         '''
@@ -1422,9 +1441,9 @@
             return False
         self.show_logs(re_l=all_re_l)
 
-    def find_peinput(self, t_str):
-        for t_obj in self.peinputs_l:
-            if str(t_obj) == t_str:
+    def find_transition(self, t_str):
+        for t_obj in self._transitions:
+            if t_obj.get_node_file() == t_str:
                 return t_obj
         return None
 
@@ -1434,7 +1453,7 @@
         '''
         if not self.prepare_source(no_live_update=self.prevent_live_update()):
             return False
-        t_obj = self.find_peinput(rpt_pe2t_str(rpt_pe_file))
+        t_obj = self.find_transition(rpt_pe2t_str(rpt_pe_file))
         if not t_obj:
             common_err("%s: transition not found" % rpt_pe_file)
             return False
@@ -1448,6 +1467,36 @@
         self.logobj.set_log_timeframe(self.from_dt, self.to_dt)
         return True
 
+    def show_transition_tags(self, rpt_pe_file):
+        '''
+        prints the tags for the transition
+        '''
+        t_obj = self.find_transition(rpt_pe2t_str(rpt_pe_file))
+        if not t_obj:
+            common_err("%s: transition not found" % rpt_pe_file)
+            return False
+        for tag in t_obj.tags:
+            print tag
+        return True
+
+    def _set_transition_tags(self, transition):
+        # limit the log scope temporarily
+        self.logobj.set_log_timeframe(transition.start_ts, transition.end_ts)
+
+        # search log, match regexes to tags
+        regexes = [
+            re.compile(r"(error|unclean)", re.I),
+            re.compile(r"crmd.*notice:\s+Operation\s+([^:]+):\s+(?!ok)"),
+        ]
+
+        for l in self.logobj.get_matches(regexes):
+            for rx in regexes:
+                m = rx.search(l)
+                if m:
+                    transition.tags.add(m.group(1).lower())
+
+        self.logobj.set_log_timeframe(self.from_dt, self.to_dt)
+
     def resource(self, *args):
         '''
         Show resource relevant logs.
@@ -1532,8 +1581,8 @@
                 a.append(a[0])
         elif a is not None:
             a = [a, a]
-        l = [long and self.pe_detail_format(x) or self.pe_report_path(x)
-             for x in self.peinputs_l if pe_file_in_range(x.pe_file, a)]
+        l = [long and self.pe_detail_format(t_obj) or 
self.pe_report_path(t_obj)
+             for t_obj in self._transitions if pe_file_in_range(t_obj.pe_file, 
a)]
         if long:
             l = [self.pe_details_header, self.pe_details_separator] + l
         return l
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/scripts.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/scripts.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/scripts.py       
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/scripts.py       
2015-09-11 12:21:55.000000000 +0200
@@ -1278,12 +1278,6 @@
         ok, _ = _valid_integer(value)
         if not ok:
             errors.append("%s=%s is not an integer" % (param.get('name'), 
value))
-    elif type == 'port':
-        ok, ival = _valid_integer(value)
-        if not ok:
-            errors.append("%s=%s is not a valid port" % (param.get('name'), 
value))
-        if ival < 0 or ival > 65535:
-            errors.append("%s=%s is out of port range" % (param.get('name'), 
value))
     elif type == 'string':
         return value
     elif type == 'boolean':
@@ -1294,12 +1288,21 @@
                 errors.append("%s=%s invalid resource identifier" % 
(param.get('name'), value))
         except TypeError as e:
             errors.append("%s=%s %s" % (param.get('name'), value, str(e)))
+    elif type == 'enum':
+        if 'values' not in param:
+            errors.append("%s=%s enum without list of values" % 
(param.get('name'), value))
+        else:
+            opts = param['values']
+            if isinstance(opts, basestring):
+                opts = opts.replace(',', ' ').split(' ')
+            for v in opts:
+                if value.lower() == v.lower():
+                    return v
+            else:
+                errors.append("%s=%s does not match '%s'" % 
(param.get('name'), value, "|".join(opts)))
     elif type == 'ip_address':
         if not _valid_ip(value):
             errors.append("%s=%s is not an IP address" % (param.get('name'), 
value))
-    elif type == 'email':
-        if not re.match(r'[^@]+@[^@]+', value):
-            errors.append("%s=%s is not a valid email address" % 
(param.get('value'), value))
     elif type == 'ip_network':
         sp = value.rsplit('/', 1)
         if len(sp) == 1 and not (is_valid_ipv4_address(value) or 
is_valid_ipv6_address(value)):
@@ -1308,6 +1311,15 @@
             errors.append("%s=%s is not a valid IP network" % 
(param.get('name'), value))
         else:
             errors.append("%s=%s is not a valid IP network" % 
(param.get('name'), value))
+    elif type == 'port':
+        ok, ival = _valid_integer(value)
+        if not ok:
+            errors.append("%s=%s is not a valid port" % (param.get('name'), 
value))
+        if ival < 0 or ival > 65535:
+            errors.append("%s=%s is out of port range" % (param.get('name'), 
value))
+    elif type == 'email':
+        if not re.match(r'[^@]+@[^@]+', value):
+            errors.append("%s=%s is not a valid email address" % 
(param.get('name'), value))
     else:
         errors.append("%s=%s is unknown type %s" % (param.get('name'), value, 
type))
     return value
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/ui_history.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/ui_history.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/ui_history.py    
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/ui_history.py    
2015-09-11 12:21:55.000000000 +0200
@@ -61,9 +61,12 @@
             to_dt = utils.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
+        if to_dt and from_dt:
+            if to_dt < from_dt:
+                from_dt, to_dt = to_dt, from_dt
+            elif to_dt == from_dt:
+                common_err("%s - %s: To and from dates cannot be the same" % 
(from_time, to_time))
+                return False
         return crm_report().set_period(from_dt, to_dt)
 
     def _check_source(self, src):
@@ -138,9 +141,8 @@
         "usage: detail <detail_level>"
         self._init_source()
         detail_num = utils.convert2ints(detail_lvl)
-        if not (isinstance(detail_num, int) and int(detail_num) >= 0):
-            bad_usage(context.get_command_name(), detail_lvl)
-            return False
+        if detail_num is None or detail_num not in (0, 1):
+            context.fatal_error("Expected '0' or '1' (was '%s')" % 
(detail_lvl))
         return crm_report().set_detail(detail_lvl)
 
     @command.skill_level('administrator')
@@ -305,7 +307,7 @@
         self._init_source()
         argl = list(args)
         subcmd = "show"
-        if argl and argl[0] in ("showdot", "log", "save"):
+        if argl and argl[0] in ("showdot", "log", "save", "tags"):
             subcmd = argl[0]
             del argl[0]
         if subcmd == "show":
@@ -328,6 +330,8 @@
             rc = self._display_dot(f)
         elif subcmd == "save":
             rc = self._pe2shadow(f, argl)
+        elif subcmd == "tags":
+            rc = crm_report().show_transition_tags(f)
         else:
             rc = crm_report().show_transition_log(f, True)
         return rc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/utils.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/utils.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/utils.py 2015-09-04 
10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/utils.py 2015-09-11 
12:21:55.000000000 +0200
@@ -406,6 +406,7 @@
 
 
 def rmdir_r(d):
+    # TODO: Make sure we're not deleting something we shouldn't!
     if d and os.path.isdir(d):
         shutil.rmtree(d)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/xmlbuilder.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/xmlbuilder.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/modules/xmlbuilder.py    
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/modules/xmlbuilder.py    
2015-09-11 12:21:55.000000000 +0200
@@ -51,7 +51,6 @@
     """
     <nvpair id-ref=<idref> [name=<name>]/>
     """
-    print "nvpair_ref:", repr(idref), repr(name)
     nvp = new("nvpair")
     nvp.set('id-ref', idref)
     if name is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/scripts/mailto/main.yml 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/scripts/mailto/main.yml
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/scripts/mailto/main.yml  
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/scripts/mailto/main.yml  
2015-09-11 12:21:55.000000000 +0200
@@ -20,6 +20,9 @@
         op stop timeout="10"
         op monitor interval="10" timeout="10"
   actions:
+    - install:
+        - mailx
+      shortdesc: Ensure mail package is installed
     - include: mailto
     - cib: |
         clone c-{{id}} {{id}}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/testcases/newfeatures.exp 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/testcases/newfeatures.exp
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/testcases/newfeatures.exp   
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/testcases/newfeatures.exp   
2015-09-11 12:21:55.000000000 +0200
@@ -9,8 +9,6 @@
 .INP: primitive p0 Dummy params $p0-state:state=1
 .INP: primitive p1 Dummy params     rule role=Started date in start=2009-05-26 
end=2010-05-26 or date gt 2014-01-01     state=2
 .INP: primitive p2 Dummy params @p0-state
-nvpair_ref: 'p0-state' None
-nvpair_ref: 'p0-state' None
 .INP: property rule #uname eq node1 stonith-enabled=no
 .INP: tag tag1: p0 p1 p2
 .INP: tag tag2 p0 p1 p2
@@ -43,4 +41,3 @@
 .EXT pengine metadata
 .EXT cib metadata
 .INP: commit
-nvpair_ref: 'p0-state' None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/testcases/scripts.exp 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/testcases/scripts.exp
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/testcases/scripts.exp       
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/testcases/scripts.exp       
2015-09-11 12:21:55.000000000 +0200
@@ -10,9 +10,11 @@
 .EXT crm_resource --show-metadata systemd:haproxy
 ERROR: 2: Error when loading script haproxy: No meta-data for agent: 
systemd:haproxy
 .EXT crm_resource --show-metadata ocf:heartbeat:LVM
+.EXT crm_resource --show-metadata ocf:heartbeat:MailTo
 .EXT crm_resource --show-metadata ocf:heartbeat:Raid1
 Basic:
 
+mailto           MailTo
 virtual-ip       Virtual IP
 
 Database:
@@ -57,6 +59,7 @@
 ERROR: 3: Error when loading script haproxy: No meta-data for agent: 
systemd:haproxy
 Basic:
 
+mailto           MailTo
 virtual-ip       Virtual IP
 
 Database:
@@ -128,6 +131,7 @@
 init
 libvirt
 lvm
+mailto
 nfsserver
 ocfs2
 oracle
@@ -162,6 +166,7 @@
 init
 libvirt
 lvm
+mailto
 nfsserver
 ocfs2
 oracle
@@ -196,6 +201,7 @@
 init
 libvirt
 lvm
+mailto
 nfsserver
 ocfs2
 oracle
@@ -237,8 +243,6 @@
       CIDR netmask
   broadcast
       Broadcast address
-  lvs_support
-      Enable support for LVS DR
 
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/unittests/test_cliformat.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/unittests/test_cliformat.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/unittests/test_cliformat.py 
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/unittests/test_cliformat.py 
2015-09-11 12:21:55.000000000 +0200
@@ -244,3 +244,7 @@
     roundtrip('role silly-role-2 read xpath:"//nodes//attributes" ' +
               'deny type:nvpair deny ref:d0 deny type:nvpair')
 
+
+@with_setup(setup_func, teardown_func)
+def test_topology_1114():
+    roundtrip('fencing_topology attr:rack=1 node1,node2')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/unittests/test_parse.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/unittests/test_parse.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/unittests/test_parse.py     
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/unittests/test_parse.py     
2015-09-11 12:21:55.000000000 +0200
@@ -434,7 +434,7 @@
         # num test nodes are 3
 
         out = self.parser.parse('fencing_topology poison-pill power')
-        self.assertEqual(6, len(out))
+        self.assertEqual("""<fencing-topology><fencing-level 
devices="poison-pill" index="1" target="ha-one"/><fencing-level devices="power" 
index="2" target="ha-one"/><fencing-level devices="poison-pill" index="1" 
target="ha-two"/><fencing-level devices="power" index="2" 
target="ha-two"/><fencing-level devices="poison-pill" index="1" 
target="ha-three"/><fencing-level devices="power" index="2" 
target="ha-three"/></fencing-topology>""", etree.tostring(out))
 
         out = self.parser.parse('fencing_topology node-a: poison-pill power 
node-b: ipmi serial')
         self.assertEqual(4, len(out))
@@ -442,8 +442,21 @@
         devs = ['stonith-vbox3-1-off', 'stonith-vbox3-2-off',
                 'stonith-vbox3-1-on', 'stonith-vbox3-2-on']
         out = self.parser.parse('fencing_topology vbox4: %s' % ','.join(devs))
+        print etree.tostring(out)
         self.assertEqual(1, len(out))
 
+    def test_fencing_1114(self):
+        """
+        Test node attribute fence target assignment
+        """
+        out = self.parser.parse('fencing_topology attr:rack=1 poison-pill 
power')
+        expect = """<fencing-topology><fencing-level devices="poison-pill" 
index="1" target-attribute="rack" target-value="1"/><fencing-level 
devices="power" index="2" target-attribute="rack" 
target-value="1"/></fencing-topology>"""
+        self.assertEqual(expect, etree.tostring(out))
+
+        out = self.parser.parse('fencing_topology attr:rack=1 
poison-pill,power')
+        expect = '<fencing-topology><fencing-level devices="poison-pill,power" 
index="1" target-attribute="rack" target-value="1"/></fencing-topology>'
+        self.assertEqual(expect, etree.tostring(out))
+
     def test_tag(self):
         out = self.parser.parse('tag tag1: one two three')
         self.assertEqual(out.get('id'), 'tag1')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/unittests/test_scripts.py 
new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/unittests/test_scripts.py
--- old/crmsh-2.2.0~rc3+git.1441319359.d823416/test/unittests/test_scripts.py   
2015-09-04 10:08:47.000000000 +0200
+++ new/crmsh-2.2.0~rc3+git.1441965248.a9a616d/test/unittests/test_scripts.py   
2015-09-11 12:21:55.000000000 +0200
@@ -4,7 +4,7 @@
 
 from os import path
 from pprint import pprint
-from nose.tools import eq_, with_setup
+from nose.tools import eq_, with_setup, assert_raises
 from lxml import etree
 from crmsh import scripts
 from crmsh import ra
@@ -644,3 +644,80 @@
     eq_(len(actions), 1)
     pprint(actions)
     assert actions[0]['text'] == "primitive SARUMAN apacho"
+
+
+@with_setup(setup_func, teardown_func)
+def test_enums_basic():
+    a = '''---
+- version: 2.2
+  category: Script
+  parameters:
+    - name: foo
+      required: true
+      type: enum
+      values:
+        - one
+        - two
+        - three
+  actions:
+    - cib: "{{foo}}"
+'''
+
+    script_a = scripts.load_script_string('test-a', a)
+    assert script_a is not None
+
+    actions = scripts.verify(script_a,
+                             {"foo": "one"})
+    eq_(len(actions), 1)
+    pprint(actions)
+    assert actions[0]['text'] == "one"
+
+    actions = scripts.verify(script_a,
+                             {"foo": "three"})
+    eq_(len(actions), 1)
+    pprint(actions)
+    assert actions[0]['text'] == "three"
+
+
+@with_setup(setup_func, teardown_func)
+def test_enums_fail():
+    a = '''---
+- version: 2.2
+  category: Script
+  parameters:
+    - name: foo
+      required: true
+      type: enum
+      values:
+        - one
+        - two
+        - three
+  actions:
+    - cib: "{{foo}}"
+'''
+    script_a = scripts.load_script_string('test-a', a)
+    assert script_a is not None
+
+    def ver():
+        return scripts.verify(script_a, {"foo": "wrong"})
+    assert_raises(ValueError, ver)
+
+
+@with_setup(setup_func, teardown_func)
+def test_enums_fail2():
+    a = '''---
+- version: 2.2
+  category: Script
+  parameters:
+    - name: foo
+      required: true
+      type: enum
+  actions:
+    - cib: "{{foo}}"
+'''
+    script_a = scripts.load_script_string('test-a', a)
+    assert script_a is not None
+
+    def ver():
+        return scripts.verify(script_a, {"foo": "one"})
+    assert_raises(ValueError, ver)


Reply via email to