3604df
From 62ad86bf93d94212eddbb50b010f5bd2908e9600 Mon Sep 17 00:00:00 2001
3604df
From: Milind Changire <mchangir@redhat.com>
3604df
Date: Wed, 26 Oct 2016 15:28:14 +0530
3604df
Subject: [PATCH 145/157] tools/glusterfind: kill remote processes and separate run-time directories
3604df
3604df
Problem #1:
3604df
Hitting CTRL+C leaves stale processes on remote nodes if glusterfind pre
3604df
has been initiated.
3604df
3604df
Solution #1:
3604df
Adding "-t -t" to ssh command-line forces pseudo-terminal to be assigned
3604df
to remote process. When local process receives Keyboard Interrupt,
3604df
SIGHUP is immediately conveyed to the remote terminal causing remote
3604df
changelog.py process to terminate immediately.
3604df
3604df
Problem #2:
3604df
Concurrent glusterfind pre runs are not possible on the same glusterfind
3604df
session in case of a runaway process.
3604df
3604df
Solution #2:
3604df
glusterfind pre runs now add random directory name to the working
3604df
directory to store and manage temporary database and changelog
3604df
processing.
3604df
If KeyboardInterrupt is received, the function call
3604df
run_cmd_nodes("cleanup", args, tmpfilename=gtmpfilename)
3604df
cleans up the remote run specific directory.
3604df
3604df
Patch:
3604df
7571380 cli/xml: Fix wrong XML format in volume get command
3604df
broke "gluster volume get <vol> changelog.rollover-time --xml"
3604df
Now fixed function utils.py::get_changelog_rollover_time()
3604df
3604df
Fixed spurious trailing space getting written if second path is empty in
3604df
main.py::write_output()
3604df
Fixed repetitive changelog processing in changelog.py::get_changes()
3604df
3604df
mainline:
3604df
> > Reviewed-on: http://review.gluster.org/15609
3604df
> > Smoke: Gluster Build System <jenkins@build.gluster.org>
3604df
> > CentOS-regression: Gluster Build System <jenkins@build.gluster.org>
3604df
> > NetBSD-regression: NetBSD Build System <jenkins@build.gluster.org>
3604df
> > Reviewed-by: Aravinda VK <avishwan@redhat.com>
3604df
(cherry picked from commit feea851fad4f89b48bfe89fe3b75250cc7bd6501)
3604df
3604df
release-3.9:
3604df
> Reviewed-on: http://review.gluster.org/15729
3604df
> Smoke: Gluster Build System <jenkins@build.gluster.org>
3604df
> NetBSD-regression: NetBSD Build System <jenkins@build.gluster.org>
3604df
> CentOS-regression: Gluster Build System <jenkins@build.gluster.org>
3604df
> Reviewed-by: Aravinda VK <avishwan@redhat.com>
3604df
(cherry picked from commit 915ae56a65d5a96bfddf977193dca60535ac7c11)
3604df
3604df
Change-Id: Ia8d96e2cd47bf2a64416bece312e67631a1dbf29
3604df
BUG: 1379790
3604df
Signed-off-by: Milind Changire <mchangir@redhat.com>
3604df
Reviewed-on: https://code.engineering.redhat.com/gerrit/88272
3604df
Reviewed-by: Atin Mukherjee <amukherj@redhat.com>
3604df
---
3604df
 tools/glusterfind/src/changelog.py |    2 +-
3604df
 tools/glusterfind/src/main.py      |   68 +++++++++++++++++++++++++++++-------
3604df
 tools/glusterfind/src/nodeagent.py |    4 ++-
3604df
 tools/glusterfind/src/utils.py     |    2 +-
3604df
 4 files changed, 60 insertions(+), 16 deletions(-)
3604df
3604df
diff --git a/tools/glusterfind/src/changelog.py b/tools/glusterfind/src/changelog.py
3604df
index 283a035..721b8d0 100644
3604df
--- a/tools/glusterfind/src/changelog.py
3604df
+++ b/tools/glusterfind/src/changelog.py
3604df
@@ -284,7 +284,7 @@ def get_changes(brick, hash_dir, log_file, start, end, args):
3604df
         # history_getchanges()
3604df
         changes = []
3604df
         while libgfchangelog.cl_history_scan() > 0:
3604df
-            changes += libgfchangelog.cl_history_getchanges()
3604df
+            changes = libgfchangelog.cl_history_getchanges()
3604df
 
3604df
             for change in changes:
3604df
                 # Ignore if last processed changelog comes
3604df
diff --git a/tools/glusterfind/src/main.py b/tools/glusterfind/src/main.py
3604df
index 37d6c38..0c993f5 100644
3604df
--- a/tools/glusterfind/src/main.py
3604df
+++ b/tools/glusterfind/src/main.py
3604df
@@ -18,6 +18,9 @@ import xml.etree.cElementTree as etree
3604df
 from argparse import ArgumentParser, RawDescriptionHelpFormatter, Action
3604df
 import logging
3604df
 import shutil
3604df
+import tempfile
3604df
+import signal
3604df
+from datetime import datetime
3604df
 
3604df
 from utils import execute, is_host_local, mkdirp, fail
3604df
 from utils import setup_logger, human_time, handle_rm_error
3604df
@@ -34,6 +37,7 @@ ParseError = etree.ParseError if hasattr(etree, 'ParseError') else SyntaxError
3604df
 logger = logging.getLogger()
3604df
 node_outfiles = []
3604df
 vol_statusStr = ""
3604df
+gtmpfilename = None
3604df
 
3604df
 
3604df
 class StoreAbsPath(Action):
3604df
@@ -71,6 +75,8 @@ def node_cmd(host, host_uuid, task, cmd, args, opts):
3604df
             cmd = ["ssh",
3604df
                    "-oNumberOfPasswordPrompts=0",
3604df
                    "-oStrictHostKeyChecking=no",
3604df
+                   "-t",
3604df
+                   "-t",
3604df
                    "-i", pem_key_path,
3604df
                    "root@%s" % host] + cmd
3604df
 
3604df
@@ -98,8 +104,13 @@ def run_cmd_nodes(task, args, **kwargs):
3604df
         host_uuid = node[0]
3604df
         cmd = []
3604df
         opts = {}
3604df
+
3604df
+        # tmpfilename is valid only for tasks: pre, query and cleanup
3604df
+        tmpfilename = kwargs.get("tmpfilename", "BADNAME")
3604df
+
3604df
         node_outfile = os.path.join(conf.get_opt("working_dir"),
3604df
                                     args.session, args.volume,
3604df
+                                    tmpfilename,
3604df
                                     "tmp_output_%s" % num)
3604df
 
3604df
         if task == "pre":
3604df
@@ -117,6 +128,9 @@ def run_cmd_nodes(task, args, **kwargs):
3604df
                     tag = '""' if not is_host_local(host_uuid) else ""
3604df
 
3604df
             node_outfiles.append(node_outfile)
3604df
+            # remote file will be copied into this directory
3604df
+            mkdirp(os.path.dirname(node_outfile),
3604df
+                   exit_on_err=True, logger=logger)
3604df
 
3604df
             cmd = [change_detector,
3604df
                    args.session,
3604df
@@ -144,6 +158,9 @@ def run_cmd_nodes(task, args, **kwargs):
3604df
                     tag = '""' if not is_host_local(host_uuid) else ""
3604df
 
3604df
             node_outfiles.append(node_outfile)
3604df
+            # remote file will be copied into this directory
3604df
+            mkdirp(os.path.dirname(node_outfile),
3604df
+                   exit_on_err=True, logger=logger)
3604df
 
3604df
             cmd = [change_detector,
3604df
                    args.session,
3604df
@@ -162,8 +179,9 @@ def run_cmd_nodes(task, args, **kwargs):
3604df
             opts["node_outfile"] = node_outfile
3604df
             opts["copy_outfile"] = True
3604df
         elif task == "cleanup":
3604df
-            # After pre run, cleanup the working directory and other temp files
3604df
-            # Remove the copied node_outfile in main node
3604df
+            # After pre/query run, cleanup the working directory and other
3604df
+            # temp files. Remove the directory to which node_outfile has
3604df
+            # been copied in main node
3604df
             try:
3604df
                 os.remove(node_outfile)
3604df
             except (OSError, IOError):
3604df
@@ -174,7 +192,9 @@ def run_cmd_nodes(task, args, **kwargs):
3604df
             cmd = [conf.get_opt("nodeagent"),
3604df
                    "cleanup",
3604df
                    args.session,
3604df
-                   args.volume] + (["--debug"] if args.debug else [])
3604df
+                   args.volume,
3604df
+                   os.path.dirname(node_outfile)] + \
3604df
+                (["--debug"] if args.debug else [])
3604df
         elif task == "create":
3604df
             if vol_statusStr != "Started":
3604df
                 fail("Volume %s is not online" % args.volume,
3604df
@@ -422,8 +442,8 @@ def enable_volume_options(args):
3604df
                 % args.volume)
3604df
 
3604df
 
3604df
-def write_output(args, outfilemerger):
3604df
-    with codecs.open(args.outfile, "a", encoding="utf-8") as f:
3604df
+def write_output(outfile, outfilemerger):
3604df
+    with codecs.open(outfile, "a", encoding="utf-8") as f:
3604df
         for row in outfilemerger.get():
3604df
             # Multiple paths in case of Hardlinks
3604df
             paths = row[1].split(",")
3604df
@@ -438,9 +458,10 @@ def write_output(args, outfilemerger):
3604df
                 if p_rep == row_2_rep:
3604df
                     continue
3604df
 
3604df
-                f.write(u"{0} {1} {2}\n".format(row[0],
3604df
-                                                p_rep,
3604df
-                                                row_2_rep))
3604df
+                if row_2_rep and row_2_rep != "":
3604df
+                    f.write(u"{0} {1} {2}\n".format(row[0], p_rep, row_2_rep))
3604df
+                else:
3604df
+                    f.write(u"{0} {1}\n".format(row[0], p_rep))
3604df
 
3604df
 
3604df
 def mode_create(session_dir, args):
3604df
@@ -490,6 +511,8 @@ def mode_create(session_dir, args):
3604df
 
3604df
 
3604df
 def mode_query(session_dir, args):
3604df
+    global gtmpfilename
3604df
+
3604df
     # Verify volume status
3604df
     cmd = ["gluster", 'volume', 'info', args.volume, "--xml"]
3604df
     _, data, _ = execute(cmd,
3604df
@@ -533,7 +556,10 @@ def mode_query(session_dir, args):
3604df
                  "Start time: %s"
3604df
                  % ("default", args.volume, start))
3604df
 
3604df
-    run_cmd_nodes("query", args, start=start)
3604df
+    prefix = datetime.now().strftime("%Y%m%d-%H%M%S-%f-")
3604df
+    gtmpfilename = prefix + next(tempfile._get_candidate_names())
3604df
+
3604df
+    run_cmd_nodes("query", args, start=start, tmpfilename=gtmpfilename)
3604df
 
3604df
     # Merger
3604df
     if args.full:
3604df
@@ -545,7 +571,7 @@ def mode_query(session_dir, args):
3604df
         # Read each Changelogs db and generate finaldb
3604df
         create_file(args.outfile, exit_on_err=True, logger=logger)
3604df
         outfilemerger = OutputMerger(args.outfile + ".db", node_outfiles)
3604df
-        write_output(args, outfilemerger)
3604df
+        write_output(args.outfile, outfilemerger)
3604df
 
3604df
     try:
3604df
         os.remove(args.outfile + ".db")
3604df
@@ -558,6 +584,8 @@ def mode_query(session_dir, args):
3604df
 
3604df
 
3604df
 def mode_pre(session_dir, args):
3604df
+    global gtmpfilename
3604df
+
3604df
     """
3604df
     Read from Session file and write to session.pre file
3604df
     """
3604df
@@ -587,7 +615,10 @@ def mode_pre(session_dir, args):
3604df
                  "Start time: %s, End time: %s"
3604df
                  % (args.session, args.volume, start, endtime_to_update))
3604df
 
3604df
-    run_cmd_nodes("pre", args, start=start)
3604df
+    prefix = datetime.now().strftime("%Y%m%d-%H%M%S-%f-")
3604df
+    gtmpfilename = prefix + next(tempfile._get_candidate_names())
3604df
+
3604df
+    run_cmd_nodes("pre", args, start=start, tmpfilename=gtmpfilename)
3604df
 
3604df
     # Merger
3604df
     if args.full:
3604df
@@ -599,8 +630,7 @@ def mode_pre(session_dir, args):
3604df
         # Read each Changelogs db and generate finaldb
3604df
         create_file(args.outfile, exit_on_err=True, logger=logger)
3604df
         outfilemerger = OutputMerger(args.outfile + ".db", node_outfiles)
3604df
-
3604df
-        write_output(args, outfilemerger)
3604df
+        write_output(args.outfile, outfilemerger)
3604df
 
3604df
     try:
3604df
         os.remove(args.outfile + ".db")
3604df
@@ -713,6 +743,10 @@ def mode_list(session_dir, args):
3604df
 
3604df
 
3604df
 def main():
3604df
+    global gtmpfilename
3604df
+
3604df
+    args = None
3604df
+
3604df
     try:
3604df
         args = _get_args()
3604df
         mkdirp(conf.get_opt("session_dir"), exit_on_err=True)
3604df
@@ -756,5 +790,13 @@ def main():
3604df
         # mode_<args.mode> will be the function name to be called
3604df
         globals()["mode_" + args.mode](session_dir, args)
3604df
     except KeyboardInterrupt:
3604df
+        if args is not None:
3604df
+            if args.mode == "pre" or args.mode == "query":
3604df
+                # cleanup session
3604df
+                if gtmpfilename is not None:
3604df
+                    # no more interrupts until we clean up
3604df
+                    signal.signal(signal.SIGINT, signal.SIG_IGN)
3604df
+                    run_cmd_nodes("cleanup", args, tmpfilename=gtmpfilename)
3604df
+
3604df
         # Interrupted, exit with non zero error code
3604df
         sys.exit(2)
3604df
diff --git a/tools/glusterfind/src/nodeagent.py b/tools/glusterfind/src/nodeagent.py
3604df
index f707449..07d8282 100644
3604df
--- a/tools/glusterfind/src/nodeagent.py
3604df
+++ b/tools/glusterfind/src/nodeagent.py
3604df
@@ -26,7 +26,8 @@ logger = logging.getLogger()
3604df
 def mode_cleanup(args):
3604df
     working_dir = os.path.join(conf.get_opt("working_dir"),
3604df
                                args.session,
3604df
-                               args.volume)
3604df
+                               args.volume,
3604df
+                               args.tmpfilename)
3604df
 
3604df
     mkdirp(os.path.join(conf.get_opt("log_dir"), args.session, args.volume),
3604df
            exit_on_err=True)
3604df
@@ -98,6 +99,7 @@ def _get_args():
3604df
     parser_cleanup = subparsers.add_parser('cleanup')
3604df
     parser_cleanup.add_argument("session", help="Session Name")
3604df
     parser_cleanup.add_argument("volume", help="Volume Name")
3604df
+    parser_cleanup.add_argument("tmpfilename", help="Temporary File Name")
3604df
     parser_cleanup.add_argument("--debug", help="Debug", action="store_true")
3604df
 
3604df
     parser_session_create = subparsers.add_parser('create')
3604df
diff --git a/tools/glusterfind/src/utils.py b/tools/glusterfind/src/utils.py
3604df
index 598cc9e..70737be 100644
3604df
--- a/tools/glusterfind/src/utils.py
3604df
+++ b/tools/glusterfind/src/utils.py
3604df
@@ -227,7 +227,7 @@ def get_changelog_rollover_time(volumename):
3604df
 
3604df
     try:
3604df
         tree = etree.fromstring(out)
3604df
-        return int(tree.find('volGetopts/Value').text)
3604df
+        return int(tree.find('volGetopts/Opt/Value').text)
3604df
     except ParseError:
3604df
         return DEFAULT_CHANGELOG_INTERVAL
3604df
 
3604df
-- 
3604df
1.7.1
3604df