Add ansible as a dependency

Note that ansible is licensed under the GPL which may have
implications for the distribution of Zuul as a whole.

Actually use ansible to launch a hello world test.

Also reorganize the launcher directory to be more like
the mergers: a client and server.

Change-Id: I30f7ed2cadbb7d2381ec477e229d19c9c6e41743
This commit is contained in:
James E. Blair 2016-01-11 14:38:13 -08:00 committed by Joshua Hesketh
parent f5dbd00712
commit 82938473af
4 changed files with 70 additions and 16 deletions

View File

@ -16,3 +16,4 @@ apscheduler>=2.1.1,<3.0
PrettyTable>=0.6,<0.8 PrettyTable>=0.6,<0.8
babel>=1.0 babel>=1.0
six>=1.6.0 six>=1.6.0
ansible>=2.0.0.1

View File

@ -48,8 +48,8 @@ import zuul.connection.smtp
import zuul.scheduler import zuul.scheduler
import zuul.webapp import zuul.webapp
import zuul.rpclistener import zuul.rpclistener
import zuul.launcher.ansible import zuul.launcher.ansiblelaunchserver
import zuul.launcher.gearman import zuul.launcher.launchclient
import zuul.lib.swift import zuul.lib.swift
import zuul.lib.connections import zuul.lib.connections
import zuul.merger.client import zuul.merger.client
@ -636,7 +636,7 @@ class FakeBuild(threading.Thread):
self.worker.lock.release() self.worker.lock.release()
class RecordingLaunchServer(zuul.launcher.ansible.LaunchServer): class RecordingLaunchServer(zuul.launcher.ansiblelaunchserver.LaunchServer):
def __init__(self, *args, **kw): def __init__(self, *args, **kw):
super(RecordingLaunchServer, self).__init__(*args, **kw) super(RecordingLaunchServer, self).__init__(*args, **kw)
self.job_history = [] self.job_history = []
@ -977,8 +977,8 @@ class ZuulTestCase(BaseTestCase):
self.connections) self.connections)
self.merge_server.start() self.merge_server.start()
self.launcher = zuul.launcher.gearman.Gearman(self.config, self.sched, self.launcher = zuul.launcher.launchclient.LaunchClient(
self.swift) self.config, self.sched, self.swift)
self.merge_client = zuul.merger.client.MergeClient( self.merge_client = zuul.merger.client.MergeClient(
self.config, self.sched) self.config, self.sched)
@ -1280,7 +1280,8 @@ class ZuulTestCase(BaseTestCase):
return False return False
if server_job.waiting: if server_job.waiting:
continue continue
worker_job = self.worker.gearman_jobs.get(server_job.unique) worker_job = self.ansible_server.worker.gearman_jobs.get(
server_job.unique)
if worker_job: if worker_job:
if build.number is None: if build.number is None:
self.log.debug("%s has not reported start" % worker_job) self.log.debug("%s has not reported start" % worker_job)

View File

@ -15,25 +15,35 @@
import collections import collections
import json import json
import logging import logging
import os
import shutil import shutil
import subprocess
import tempfile import tempfile
import threading import threading
import traceback import traceback
import gear import gear
import yaml
import zuul.merger import zuul.merger
class TempDir(object): class JobDir(object):
def __init__(self): def __init__(self):
self.tmpdir = tempfile.mkdtemp() self.root = tempfile.mkdtemp()
self.git_root = os.path.join(self.root, 'git')
os.makedirs(self.git_root)
self.ansible_root = os.path.join(self.root, 'ansible')
os.makedirs(self.ansible_root)
self.inventory = os.path.join(self.ansible_root, 'inventory')
self.playbook = os.path.join(self.ansible_root, 'playbook')
self.config = os.path.join(self.ansible_root, 'ansible.cfg')
def __enter__(self): def __enter__(self):
return self.tmpdir return self
def __exit__(self, etype, value, tb): def __exit__(self, etype, value, tb):
shutil.rmtree(self.tmpdir) shutil.rmtree(self.root)
class UpdateTask(object): class UpdateTask(object):
@ -207,8 +217,9 @@ class LaunchServer(object):
def _launch(self, job): def _launch(self, job):
self.log.debug("Job %s: beginning" % (job.unique,)) self.log.debug("Job %s: beginning" % (job.unique,))
with TempDir() as root: with JobDir() as jobdir:
self.log.debug("Job %s: git root at %s" % (job.unique, root)) self.log.debug("Job %s: job root at %s" %
(job.unique, jobdir.root))
args = json.loads(job.arguments) args = json.loads(job.arguments)
tasks = [] tasks = []
for project in args['projects']: for project in args['projects']:
@ -218,9 +229,12 @@ class LaunchServer(object):
for task in tasks: for task in tasks:
task.wait() task.wait()
self.log.debug("Job %s: git updates complete" % (job.unique,)) self.log.debug("Job %s: git updates complete" % (job.unique,))
merger = self._getMerger(root) merger = self._getMerger(jobdir.git_root)
commit = merger.mergeChanges(args['items']) # noqa commit = merger.mergeChanges(args['items']) # noqa
# TODOv3: Ansible the ansible thing here. # TODOv3: Ansible the ansible thing here.
self.prepareAnsibleFiles(jobdir, args)
result = self.runAnsible(jobdir)
data = { data = {
'url': 'https://server/job', 'url': 'https://server/job',
@ -229,9 +243,47 @@ class LaunchServer(object):
job.sendWorkData(json.dumps(data)) job.sendWorkData(json.dumps(data))
job.sendWorkStatus(0, 100) job.sendWorkStatus(0, 100)
result = dict(result='SUCCESS') result = dict(result=result)
job.sendWorkComplete(json.dumps(result)) job.sendWorkComplete(json.dumps(result))
def getHostList(self, args):
# TODOv3: This should get the appropriate nodes from nodepool,
# or in the unit tests, be overriden to return localhost.
return [('localhost', dict(ansible_connection='local'))]
def prepareAnsibleFiles(self, jobdir, args):
with open(jobdir.inventory, 'w') as inventory:
for host_name, host_vars in self.getHostList(args):
inventory.write(host_name)
inventory.write(' ')
for k, v in host_vars.items():
inventory.write('%s=%s' % (k, v))
inventory.write('\n')
with open(jobdir.playbook, 'w') as playbook:
play = dict(hosts='localhost',
tasks=[dict(name='test',
shell='echo Hello world')])
playbook.write(yaml.dump([play]))
with open(jobdir.config, 'w') as config:
config.write('[defaults]\n')
config.write('hostfile = %s\n' % jobdir.inventory)
def runAnsible(self, jobdir):
proc = subprocess.Popen(
['ansible-playbook', jobdir.playbook],
cwd=jobdir.ansible_root,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
(out, err) = proc.communicate()
ret = proc.wait()
print out
print err
if ret == 0:
return 'SUCCESS'
else:
return 'FAILURE'
def cat(self, job): def cat(self, job):
args = json.loads(job.arguments) args = json.loads(job.arguments)
task = self.update(args['project'], args['url']) task = self.update(args['project'], args['url'])

View File

@ -177,8 +177,8 @@ class ZuulGearmanClient(gear.Client):
self.log.info("Done waiting for Gearman server") self.log.info("Done waiting for Gearman server")
class Gearman(object): class LaunchClient(object):
log = logging.getLogger("zuul.Gearman") log = logging.getLogger("zuul.LaunchClient")
negative_function_cache_ttl = 5 negative_function_cache_ttl = 5
def __init__(self, config, sched, swift): def __init__(self, config, sched, swift):