CORD-1025 run ansible_runner in a subprocess

Change-Id: I39b8c9e4b1941c73f5b7c7f8fdf7aff4553c8bb4
diff --git a/xos/synchronizers/base/ansible_helper.py b/xos/synchronizers/base/ansible_helper.py
index 3040a26..8b36de5 100644
--- a/xos/synchronizers/base/ansible_helper.py
+++ b/xos/synchronizers/base/ansible_helper.py
@@ -4,6 +4,7 @@
 import tempfile
 import os
 import json
+import pickle
 import pdb
 import string
 import random
@@ -49,41 +50,49 @@
 
     return (opts, os.path.join(pathed_sys_dir,objname))
 
-def run_playbook(ansible_hosts, ansible_config, fqp, opts):#, q):
+def run_playbook(ansible_hosts, ansible_config, fqp, opts):
+    args = {"ansible_hosts": ansible_hosts,
+            "ansible_config": ansible_config,
+            "fqp": fqp,
+            "opts": opts}
+
+    keep_temp_files = getattr(Config(), "observer_keep_temp_files", False)
+
+    dir = tempfile.mkdtemp()
+    args_fn = None
+    result_fn = None
     try:
-        if ansible_config:
-           os.environ["ANSIBLE_CONFIG"] = ansible_config
-        else:
-           try:
-               del os.environ["ANSIBLE_CONFIG"]
-           except KeyError:
-               pass
+        logger.info("creating args file in %s" % dir)
 
-        if ansible_hosts:
-           os.environ["ANSIBLE_HOSTS"] = ansible_hosts
-        else:
-           try:
-               del os.environ["ANSIBLE_HOSTS"]
-           except KeyError:
-               pass
+        args_fn = os.path.join(dir, "args")
+        result_fn = os.path.join(dir, "result")
 
-        import ansible_runner
-        reload(ansible_runner)
+        open(args_fn, "w").write(pickle.dumps(args))
 
-        # Dropped support for observer_pretend - to be redone
-        runner = ansible_runner.Runner(
-            playbook=fqp,
-            run_data=opts,
-            host_file=ansible_hosts)
+        ansible_main_fn = os.path.join(os.path.dirname(__file__), "ansible_main.py")
 
-        stats,aresults = runner.run()
-    except Exception, e:
-        logger.log_exc("Exception executing playbook",extra={'exception':str(e)})
+        os.system("python %s %s %s" % (ansible_main_fn, args_fn, result_fn))
+
+        result = pickle.loads(open(result_fn).read())
+
+        if hasattr(result, "exception"):
+            logger.log_error("Exception in playbook: %s" % result["exception"])
+
+        stats = result.get("stats", None)
+        aresults = result.get("aresults", None)
+    except:
+        logger.log_exc("Exception running ansible_main")
         stats = None
         aresults = None
+    finally:
+        if not keep_temp_files:
+            if args_fn and os.path.exists(args_fn):
+                os.remove(args_fn)
+            if result_fn and os.path.exists(result_fn):
+                os.remove(result_fn)
+            os.rmdir(dir)
 
-    #q.put([stats,aresults])
-    return (stats,aresults)
+    return (stats, aresults)
 
 def run_template(name, opts, path='', expected_num=None, ansible_config=None, ansible_hosts=None, run_ansible_script=None, object=None):
     global uglylock
@@ -114,7 +123,9 @@
     stats,aresults = run_playbook(ansible_hosts,ansible_config,fqp,opts)
 
     uglylock.release()
-    
+
+    error_msg = []
+
     output_file = fqp + '.out'
     try:
         if (aresults is None):
@@ -124,8 +135,6 @@
         total_unreachable = 0
         failed = 0
 
-        error_msg = []
-
         ofile = open(output_file, 'w')
 
         for x in aresults:
@@ -174,11 +183,14 @@
 		raise ValueError('Ansible playbook failed.')
 
     except ValueError,e:
-        try:
-            error = ' // '.join(error_msg)
-        except:
-            pass
-        raise Exception(error)
+        if error_msg:
+            try:
+                error = ' // '.join(error_msg)
+            except:
+                error = "failed to join error_msg"
+            raise Exception(error)
+        else:
+            raise
 
     
             
diff --git a/xos/synchronizers/base/ansible_main.py b/xos/synchronizers/base/ansible_main.py
new file mode 100644
index 0000000..2df23b9
--- /dev/null
+++ b/xos/synchronizers/base/ansible_main.py
@@ -0,0 +1,57 @@
+import os
+import pickle
+import sys
+#import json
+import traceback
+
+sys.path.append("/opt/xos")
+
+def run_playbook(ansible_hosts, ansible_config, fqp, opts):
+    try:
+        if ansible_config:
+           os.environ["ANSIBLE_CONFIG"] = ansible_config
+        else:
+           try:
+               del os.environ["ANSIBLE_CONFIG"]
+           except KeyError:
+               pass
+
+        if ansible_hosts:
+           os.environ["ANSIBLE_HOSTS"] = ansible_hosts
+        else:
+           try:
+               del os.environ["ANSIBLE_HOSTS"]
+           except KeyError:
+               pass
+
+        import ansible_runner
+        reload(ansible_runner)
+
+        # Dropped support for observer_pretend - to be redone
+        runner = ansible_runner.Runner(
+            playbook=fqp,
+            run_data=opts,
+            host_file=ansible_hosts)
+
+        stats,aresults = runner.run()
+    except Exception, e:
+        return {"stats": None, "aresults": None, "exception": traceback.format_exc()}
+
+    return {"stats": stats, "aresults": aresults}
+
+def main():
+    input_fn = sys.argv[1]
+    result_fn = sys.argv[2]
+
+    args = pickle.loads(open(input_fn).read())
+    ansible_hosts = args["ansible_hosts"]
+    ansible_config = args["ansible_config"]
+    fqp = args["fqp"]
+    opts = args["opts"]
+
+    result = run_playbook(ansible_hosts, ansible_config, fqp, opts)
+
+    open(result_fn, "w").write(pickle.dumps(result))
+
+if __name__ == "__main__":
+    main()
diff --git a/xos/synchronizers/new_base/ansible_helper.py b/xos/synchronizers/new_base/ansible_helper.py
index 7c914d8..8b36de5 100644
--- a/xos/synchronizers/new_base/ansible_helper.py
+++ b/xos/synchronizers/new_base/ansible_helper.py
@@ -1,14 +1,17 @@
 #!/usr/bin/env python
+
 import jinja2
 import tempfile
 import os
 import json
+import pickle
 import pdb
 import string
 import random
 import re
 import traceback
 import subprocess
+import threading
 from xos.config import Config, XOS_DIR
 from xos.logger import observer_logger as logger
 from multiprocessing import Process, Queue
@@ -47,42 +50,60 @@
 
     return (opts, os.path.join(pathed_sys_dir,objname))
 
-def run_playbook(ansible_hosts, ansible_config, fqp, opts, q):
+def run_playbook(ansible_hosts, ansible_config, fqp, opts):
+    args = {"ansible_hosts": ansible_hosts,
+            "ansible_config": ansible_config,
+            "fqp": fqp,
+            "opts": opts}
+
+    keep_temp_files = getattr(Config(), "observer_keep_temp_files", False)
+
+    dir = tempfile.mkdtemp()
+    args_fn = None
+    result_fn = None
     try:
-        if ansible_config:
-           os.environ["ANSIBLE_CONFIG"] = ansible_config
-        else:
-           try:
-               del os.environ["ANSIBLE_CONFIG"]
-           except KeyError:
-               pass
+        logger.info("creating args file in %s" % dir)
 
-        if ansible_hosts:
-           os.environ["ANSIBLE_HOSTS"] = ansible_hosts
-        else:
-           try:
-               del os.environ["ANSIBLE_HOSTS"]
-           except KeyError:
-               pass
+        args_fn = os.path.join(dir, "args")
+        result_fn = os.path.join(dir, "result")
 
-        import ansible_runner
-        reload(ansible_runner)
+        open(args_fn, "w").write(pickle.dumps(args))
 
-        # Dropped support for observer_pretend - to be redone
-        runner = ansible_runner.Runner(
-            playbook=fqp,
-            run_data=opts,
-            host_file=ansible_hosts)
+        ansible_main_fn = os.path.join(os.path.dirname(__file__), "ansible_main.py")
 
-        stats,aresults = runner.run()
-    except Exception, e:
-        logger.log_exc("Exception executing playbook",extra={'exception':str(e)})
+        os.system("python %s %s %s" % (ansible_main_fn, args_fn, result_fn))
+
+        result = pickle.loads(open(result_fn).read())
+
+        if hasattr(result, "exception"):
+            logger.log_error("Exception in playbook: %s" % result["exception"])
+
+        stats = result.get("stats", None)
+        aresults = result.get("aresults", None)
+    except:
+        logger.log_exc("Exception running ansible_main")
         stats = None
         aresults = None
+    finally:
+        if not keep_temp_files:
+            if args_fn and os.path.exists(args_fn):
+                os.remove(args_fn)
+            if result_fn and os.path.exists(result_fn):
+                os.remove(result_fn)
+            os.rmdir(dir)
 
-    q.put([stats,aresults])
+    return (stats, aresults)
 
 def run_template(name, opts, path='', expected_num=None, ansible_config=None, ansible_hosts=None, run_ansible_script=None, object=None):
+    global uglylock
+    try:
+        if (uglylock):
+            pass
+    except NameError:
+        uglylock = threading.Lock()
+
+    uglylock.acquire()
+
     template = os_template_env.get_template(name)
     buffer = template.render(opts)
 
@@ -92,11 +113,18 @@
     f.write(buffer)
     f.flush()
     
+    """
     q = Queue()
     p = Process(target=run_playbook, args=(ansible_hosts, ansible_config, fqp, opts, q,))
     p.start()
     stats,aresults = q.get()
     p.join()
+    """
+    stats,aresults = run_playbook(ansible_hosts,ansible_config,fqp,opts)
+
+    uglylock.release()
+
+    error_msg = []
 
     output_file = fqp + '.out'
     try:
@@ -107,8 +135,6 @@
         total_unreachable = 0
         failed = 0
 
-        error_msg = []
-
         ofile = open(output_file, 'w')
 
         for x in aresults:
@@ -157,11 +183,14 @@
 		raise ValueError('Ansible playbook failed.')
 
     except ValueError,e:
-        try:
-            error = ' // '.join(error_msg)
-        except:
-            pass
-        raise Exception(error)
+        if error_msg:
+            try:
+                error = ' // '.join(error_msg)
+            except:
+                error = "failed to join error_msg"
+            raise Exception(error)
+        else:
+            raise
 
     
             
diff --git a/xos/synchronizers/new_base/ansible_main.py b/xos/synchronizers/new_base/ansible_main.py
new file mode 100644
index 0000000..2df23b9
--- /dev/null
+++ b/xos/synchronizers/new_base/ansible_main.py
@@ -0,0 +1,57 @@
+import os
+import pickle
+import sys
+#import json
+import traceback
+
+sys.path.append("/opt/xos")
+
+def run_playbook(ansible_hosts, ansible_config, fqp, opts):
+    try:
+        if ansible_config:
+           os.environ["ANSIBLE_CONFIG"] = ansible_config
+        else:
+           try:
+               del os.environ["ANSIBLE_CONFIG"]
+           except KeyError:
+               pass
+
+        if ansible_hosts:
+           os.environ["ANSIBLE_HOSTS"] = ansible_hosts
+        else:
+           try:
+               del os.environ["ANSIBLE_HOSTS"]
+           except KeyError:
+               pass
+
+        import ansible_runner
+        reload(ansible_runner)
+
+        # Dropped support for observer_pretend - to be redone
+        runner = ansible_runner.Runner(
+            playbook=fqp,
+            run_data=opts,
+            host_file=ansible_hosts)
+
+        stats,aresults = runner.run()
+    except Exception, e:
+        return {"stats": None, "aresults": None, "exception": traceback.format_exc()}
+
+    return {"stats": stats, "aresults": aresults}
+
+def main():
+    input_fn = sys.argv[1]
+    result_fn = sys.argv[2]
+
+    args = pickle.loads(open(input_fn).read())
+    ansible_hosts = args["ansible_hosts"]
+    ansible_config = args["ansible_config"]
+    fqp = args["fqp"]
+    opts = args["opts"]
+
+    result = run_playbook(ansible_hosts, ansible_config, fqp, opts)
+
+    open(result_fn, "w").write(pickle.dumps(result))
+
+if __name__ == "__main__":
+    main()