a3bd951bfb
is still kicking off. Should not happen except in modules that are somewhat slow to load and probably can be fixed better than the included sleep, i.e. some IPC communication that the process has launched and is ok to exit. This works pretty well for now though.
179 lines
5.1 KiB
Python
Executable file
179 lines
5.1 KiB
Python
Executable file
#!/usr/bin/python
|
|
|
|
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>, and others
|
|
#
|
|
# This file is part of Ansible
|
|
#
|
|
# Ansible 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 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
try:
|
|
import json
|
|
except ImportError:
|
|
import simplejson as json
|
|
import shlex
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import datetime
|
|
import traceback
|
|
import signal
|
|
import time
|
|
|
|
def daemonize_self():
|
|
# daemonizing code: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
|
|
# logger.info("cobblerd started")
|
|
try:
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
# exit first parent
|
|
sys.exit(0)
|
|
except OSError, e:
|
|
print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
|
|
sys.exit(1)
|
|
|
|
# decouple from parent environment
|
|
os.chdir("/")
|
|
os.setsid()
|
|
os.umask(022)
|
|
|
|
# do second fork
|
|
try:
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
# print "Daemon PID %d" % pid
|
|
sys.exit(0)
|
|
except OSError, e:
|
|
print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
|
|
sys.exit(1)
|
|
|
|
dev_null = file('/dev/null','rw')
|
|
os.dup2(dev_null.fileno(), sys.stdin.fileno())
|
|
os.dup2(dev_null.fileno(), sys.stdout.fileno())
|
|
os.dup2(dev_null.fileno(), sys.stderr.fileno())
|
|
|
|
if len(sys.argv) < 3:
|
|
print json.dumps({
|
|
"failed" : True,
|
|
"msg" : "usage: async_wrapper <jid> <time_limit> <modulescript> <argsfile>. Humans, do not call directly!"
|
|
})
|
|
sys.exit(1)
|
|
|
|
jid = sys.argv[1]
|
|
time_limit = sys.argv[2]
|
|
wrapped_module = sys.argv[3]
|
|
argsfile = sys.argv[4]
|
|
cmd = "%s %s" % (wrapped_module, argsfile)
|
|
|
|
# setup logging directory
|
|
logdir = os.path.expanduser("~/.ansible_async")
|
|
log_path = os.path.join(logdir, jid)
|
|
|
|
if not os.path.exists(logdir):
|
|
try:
|
|
os.makedirs(logdir)
|
|
except:
|
|
print json.dumps({
|
|
"failed" : 1,
|
|
"msg" : "could not create: %s" % logdir
|
|
})
|
|
|
|
def _run_command(wrapped_cmd, jid, log_path):
|
|
|
|
logfile = open(log_path, "w")
|
|
logfile.write(json.dumps({ "started" : 1, "ansible_job_id" : jid }))
|
|
logfile.close()
|
|
logfile = open(log_path, "w")
|
|
result = {}
|
|
|
|
outdata = ''
|
|
try:
|
|
cmd = shlex.split(wrapped_cmd)
|
|
script = subprocess.Popen(cmd, shell=False,
|
|
stdin=None, stdout=logfile, stderr=logfile)
|
|
script.communicate()
|
|
outdata = file(log_path).read()
|
|
result = json.loads(outdata)
|
|
|
|
except (OSError, IOError), e:
|
|
result = {
|
|
"failed": 1,
|
|
"cmd" : wrapped_cmd,
|
|
"msg": str(e),
|
|
}
|
|
result['ansible_job_id'] = jid
|
|
logfile.write(json.dumps(result))
|
|
except:
|
|
result = {
|
|
"failed" : 1,
|
|
"cmd" : wrapped_cmd,
|
|
"data" : outdata, # temporary debug only
|
|
"msg" : traceback.format_exc()
|
|
}
|
|
result['ansible_job_id'] = jid
|
|
logfile.write(json.dumps(result))
|
|
logfile.close()
|
|
|
|
# immediately exit this process, leaving an orphaned process
|
|
# running which immediately forks a supervisory timing process
|
|
|
|
pid = os.fork()
|
|
if pid != 0:
|
|
# the parent indicates the job has started
|
|
# print "RETURNING SUCCESS IN PARENT"
|
|
print json.dumps({ "started" : 1, "ansible_job_id" : jid, "results_file" : log_path })
|
|
sys.stdout.flush()
|
|
# we need to not return immmediately such that the launched command has an attempt
|
|
# to initialize PRIOR to ansible trying to clean up the launch directory (and argsfile)
|
|
# this probably could be done with some IPC later. Modules should always read
|
|
# the argsfile at the very first start of their execution anyway
|
|
time.sleep(1)
|
|
sys.exit(0)
|
|
else:
|
|
# the kid manages the job
|
|
# WARNING: the following call may be total overkill
|
|
daemonize_self()
|
|
|
|
# we are now daemonized in this other fork but still
|
|
# want to create a supervisory process
|
|
|
|
#print "DAEMONIZED KID MAKING MORE KIDS"
|
|
sub_pid = os.fork()
|
|
if sub_pid == 0:
|
|
#print "RUNNING IN KID A"
|
|
_run_command(cmd, jid, log_path)
|
|
#print "KID A COMPLETE"
|
|
sys.stdout.flush()
|
|
sys.exit(0)
|
|
else:
|
|
#print "WATCHING IN KID B"
|
|
remaining = int(time_limit)
|
|
if os.path.exists("/proc/%s" % sub_pid):
|
|
#print "STILL RUNNING"
|
|
time.sleep(1)
|
|
remaining = remaining - 1
|
|
else:
|
|
#print "DONE IN KID B"
|
|
sys.stdout.flush()
|
|
sys.exit(0)
|
|
if remaining == 0:
|
|
#print "SLAYING IN KID B"
|
|
os.kill(sub_pid, signals.SIGKILL)
|
|
sys.stdout.flush()
|
|
sys.exit(1)
|
|
|
|
sys.stdout.flush()
|
|
sys.exit(0)
|
|
|
|
|