Work in progress on credential/job updates.

This commit is contained in:
Chris Church
2013-04-23 16:21:29 -04:00
parent 3a9533ffa0
commit cc25d55121
7 changed files with 675 additions and 25 deletions

View File

@@ -14,8 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with Ansible Commander. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
import select
import subprocess
import time
import traceback
from celery import task
from django.conf import settings
@@ -23,8 +26,59 @@ from lib.main.models import *
__all__ = ['run_job']
logger = logging.getLogger('lib.tasks')
class Timeout(object):
def __init__(self, duration=None):
# If initializing from another instance, create a new timeout from the
# remaining time on the other instance.
if isinstance(duration, Timeout):
duration = duration.remaining
self.reset(duration)
def __repr__(self):
if self._duration is None:
return 'Timeout(None)'
else:
return 'Timeout(%f)' % self._duration
def __hash__(self):
return self._duration
def __nonzero__(self):
return self.block
def reset(self, duration=False):
if duration is not False:
self._duration = float(max(0, duration)) if duration is not None else None
self._begin = time.time()
def expire(self):
self._begin = time.time() - max(0, self._duration or 0.0)
@property
def duration(self):
return self._duration
@property
def elapsed(self):
return float(max(0, time.time() - self._begin))
@property
def remaining(self):
if self._duration is None:
return None
else:
return float(max(0, self._duration + self._begin - time.time()))
@property
def block(self):
return bool(self.remaining or self.remaining is None)
@task(name='run_job')
def run_job(job_pk):
def run_job(job_pk, **kwargs):
job = Job.objects.get(pk=job_pk)
job.status = 'running'
job.save(update_fields=['status'])
@@ -50,17 +104,89 @@ def run_job(job_pk):
if hasattr(settings, 'ANSIBLE_TRANSPORT'):
env['ANSIBLE_TRANSPORT'] = getattr(settings, 'ANSIBLE_TRANSPORT')
creds = job.credential
username = creds.ssh_username
#sudo_username = job.credential.sudo_username
cwd = job.project.local_path
cmdline = ['ansible-playbook', '-i', inventory_script]
if job.job_type == 'check':
cmdline.append('--check')
if job.use_sudo:
cmdline.append('--sudo')
if job.forks: # FIXME: Max limit?
cmdline.append('--forks=%d' % job.forks)
if job.limit:
cmdline.append('--limit=%s' % job.limit)
if job.verbosity:
cmdline.append('-%s' % ('v' * min(3, job.verbosity)))
if job.extra_vars:
# FIXME: escaping!
extra_vars = ' '.join(['%s=%s' % (str(k), str(v)) for k,v in
job.extra_vars.items()])
cmdline.append('-e', extra_vars)
cmdline.append(job.playbook) # relative path to project.local_path
# FIXME: How to cancel/interrupt job? (not that important for now)
proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, cwd=cwd, env=env)
stdout, stderr = proc.communicate()
status = 'successful' if proc.returncode == 0 else 'failed'
# stdout, stderr = proc.communicate()
proc_canceled = False
while proc.returncode is None:
new_stdout, new_stderr = '', ''
timeout = Timeout(1.0)
while timeout:
# FIXME: Probably want to use poll (when on Linux), needs to be tested.
if hasattr(select, 'poll') and False:
poll = select.poll()
poll.register(proc.stdout.fileno(), select.POLLIN or select.POLLPRI)
poll.register(proc.stderr.fileno(), select.POLLIN or select.POLLPRI)
fd_events = poll.poll(1.0)
if not fd_events:
break
for fd, evt in fd_events:
if fd == proc.stdout.fileno() and evt > 0:
new_stdout += proc.stdout.read(1)
elif fd == proc.stderr.fileno() and evt > 0:
new_stderr += proc.stderr.read(1)
else:
stdout_byte, stderr_byte = '', ''
fdlist = [proc.stdout.fileno(), proc.stderr.fileno()]
rwx = select.select(fdlist, [], [], timeout.remaining)
if proc.stdout.fileno() in rwx[0]:
stdout_byte = proc.stdout.read(1)
new_stdout += stdout_byte
if proc.stderr.fileno() in rwx[0]:
stderr_byte = proc.stderr.read(1)
new_stderr += stderr_byte
if not stdout_byte and not stderr_byte:
break
job = Job.objects.get(pk=job_pk)
update_fields = []
if new_stdout:
stdout += new_stdout
job.result_stdout = stdout
update_fields.append('result_stdout')
if new_stderr:
stderr += new_stderr
job.result_stderr = stderr
update_fields.append('result_stderr')
if update_fields:
job.save(update_fields=update_fields)
proc.poll()
if job.cancel_flag and not proc_canceled:
proc.terminate()
proc_canceled = True
stdout += proc.stdout.read()
stderr += proc.stderr.read()
if proc_canceled:
status = 'canceled'
elif proc.returncode == 0:
status = 'successful'
else:
status = 'failed'
except Exception:
tb = traceback.format_exc()