diff options
author | Sam Thursfield <sam.thursfield@codethink.co.uk> | 2015-02-11 16:11:59 +0000 |
---|---|---|
committer | Sam Thursfield <sam.thursfield@codethink.co.uk> | 2015-02-11 16:11:59 +0000 |
commit | 20a82992373be653aab0ae6d7613ef0e550bb008 (patch) | |
tree | 669fb776b7365bcdca16902809939ad79c6291c6 | |
parent | e18593c6d285130959d6463a952426ef1100a9b7 (diff) | |
download | morph-20a82992373be653aab0ae6d7613ef0e550bb008.tar.gz |
Add initial distbuild test harness
-rw-r--r-- | build-log-test.py | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/build-log-test.py b/build-log-test.py new file mode 100644 index 00000000..cd92f428 --- /dev/null +++ b/build-log-test.py @@ -0,0 +1,197 @@ +# Sam's ultra special distbuild build logs testing program test suite + + +import os +import subprocess +import tempfile +import time + + +# Some hackz if you are running Morph from the source tree. +os.environ['PYTHONPATH'] = '/src/morph' +DISTBUILD_HELPER = '/src/morph/distbuild-helper' +MORPH = '/src/morph/morph' +MORPH_CACHE_SERVER = '/src/morph/morph-cache-server' + + +controller_config = [ + '--no-default-config', + '--controller-initiator-port=7878', +] + +controller_helper_config = [ + '--no-default-config', + # The default behaviour of distbuild-helper is to be a worker helper + # process and these connect to port 3434. + '--parent-port=5656', +] + +worker_config = [ + '--no-default-config', +] + +worker_helper_config = [ + '--no-default-config', +] + +initiator_config = [ + '--no-default-config', + '--controller-initiator-address=localhost', + '--controller-initiator-port=7878', +] + + +build = [ + 'baserock:baserock/definitions', + 'c7292b7c81cdd7e5b9e85722406371748453c44f', + 'systems/base-system-x86_64-generic.morph', +] + + +def subdir(workdir, *args): + path = os.path.join(workdir, *args) + os.makedirs(path) + return path + + +class DistbuildTestHarness(object): + '''Harness for running a distbuild network on a single machine.''' + + def __init__(self): + self.process = dict() + + def run(self): + '''Start a distbuild network and run a single build on it.''' + workdir = tempfile.mkdtemp() + print('Working directory: %s' % workdir) + try: + self.start_cache_servers(workdir) + self.start_distbuild_network(workdir) + self.run_build(workdir) + finally: + self.terminate_processes() + print('Test state kept in %s' % workdir) + + def start_process(self, name, argv): + '''Start a process that will be cleaned up with the parent process.''' + self.process[name] = subprocess.Popen(argv) + print('Started %s as pid %i' % (name, self.process[name].pid)) + + def start_process_with_logs(self, workdir, name, argv): + '''Start a Morph process that will be cleaned up on exit. + + Log config will be set so that the process writes to a log file in + 'workdir'. + + ''' + log_config = [ + '--log-level=debug', + '--log=%s/morph-%s.log' % (workdir, name), + ] + self.start_process(name, argv + log_config) + + def check_processes_are_running(self): + '''Check all processes are running.''' + for name, p in self.process.iteritems(): + if p.poll() != None: + raise Exception( + '%s: exited with code %s' % (name, p.returncode)) + + def terminate_processes(self): + '''Send TERM signal to all active subprocesses.''' + for p in self.process.itervalues(): + if p.poll() == None: + p.terminate() + print('Waiting for process %i' % p.pid) + p.wait() + + def start_cache_servers(self, workdir): + # We have to make a bit of a kludge here. In a real distbuild setup, + # each worker machine runs its own instance of morph-cache-server on + # port 8080. This port is hardcoded in the controller, so in this + # test harness all workers will have to share one cache-server process. + # + # That's fine, but we still need a separate process for the *shared* + # artifact cache: artifacts are copied from workers to the shared cache + # using the /fetch method, which just wouldn't work if it had to fetch + # from itself. + self.worker_git_dir = subdir(workdir, 'worker-cache', 'gits') + self.worker_artifacts_dir = subdir(workdir, 'worker-cache', + 'artifacts') + + self.shared_git_dir = subdir(workdir, 'shared-cache', 'gits') + self.shared_artifacts_dir = subdir(workdir, 'shared-cache', + 'artifacts') + + self.start_process_with_logs( + workdir, 'worker-cache-server', + [MORPH_CACHE_SERVER, '--no-default-config', + '--repo-dir=%s' % self.worker_git_dir, + '--artifact-dir=%s' % self.worker_artifacts_dir, + '--no-fcgi']) + + self.start_process_with_logs( + workdir, 'shared-cache-server', + [MORPH_CACHE_SERVER, '--no-default-config', '--enable-writes', + '--repo-dir=%s' % self.shared_git_dir, + '--artifact-dir=%s' % self.shared_artifacts_dir, + '--no-fcgi', '--port=8081']) + + def start_distbuild_network(self, workdir, n_workers=4): + workers = [] + for n in range(0, n_workers): + worker_port = 3434 + n + workers.append('localhost:%i' % worker_port) + + self.start_process_with_logs( + workdir, 'worker-%i' % n, + [MORPH, 'worker-daemon', + '--worker-daemon-port=%i' % worker_port, + '--artifact-cache-server=http://localhost:8080', + '--morph-instance=%s --cachedir=%s' % (MORPH, self.worker_artifacts_dir)] + + worker_config) + + # Again, you'll see 'Connection refused' if you do this too + # quickly. + time.sleep(0.2) + + self.start_process_with_logs( + workdir, 'worker-%i-helper' % n, + [DISTBUILD_HELPER, '--parent-port=%i' % worker_port] + + worker_config) + + self.start_process_with_logs( + workdir, 'controller', + [MORPH, 'controller-daemon', '--worker=%s' % ','.join(workers), + '--writeable-cache-server=http://localhost:8081'] + + controller_config) + + # distbuild-helper will raise 'Connection refused' if started too soon + # after the parent process. + time.sleep(0.1) + + self.start_process_with_logs( + workdir, 'controller-helper', + [DISTBUILD_HELPER] + controller_helper_config) + + time.sleep(1) + self.check_processes_are_running() + + def run_build(self, workdir): + self.start_process_with_logs( + workdir, 'initiator', + [MORPH, 'distbuild-morphology'] + initiator_config + build) + + while self.process['initiator'].poll() == None: + time.sleep(0.1) # FIXME: use select! + if self.process['controller'].poll() != None: + raise Exception('Controller fail') + + if self.process['initiator'].returncode != 0: + #raise Exception('Initiator fail') + print('Initiator fail') + else: + print('Success!') + + +DistbuildTestHarness().run() |