summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2015-02-11 16:11:59 +0000
committerSam Thursfield <sam.thursfield@codethink.co.uk>2015-02-11 16:11:59 +0000
commit20a82992373be653aab0ae6d7613ef0e550bb008 (patch)
tree669fb776b7365bcdca16902809939ad79c6291c6
parente18593c6d285130959d6463a952426ef1100a9b7 (diff)
downloadmorph-20a82992373be653aab0ae6d7613ef0e550bb008.tar.gz
Add initial distbuild test harness
-rw-r--r--build-log-test.py197
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()