summaryrefslogtreecommitdiff
path: root/scripts/release-build
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/release-build')
-rwxr-xr-xscripts/release-build192
1 files changed, 192 insertions, 0 deletions
diff --git a/scripts/release-build b/scripts/release-build
new file mode 100755
index 00000000..cb62f661
--- /dev/null
+++ b/scripts/release-build
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+# Copyright (C) 2014 Codethink Limited
+#
+# This program 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; version 2 of the License.
+#
+# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+import cliapp
+import morphlib
+import os
+import subprocess
+import sys
+import time
+
+
+class Build(object):
+ '''A single distbuild instance.'''
+
+ def __init__(self, name, arch, app):
+ self.system_name = name
+
+ controller_netloc = app.controllers[arch].split(':')
+ controller_args = [
+ '--controller-initiator-address=%s' % controller_netloc[0],
+ ]
+ if len(controller_netloc) > 1:
+ controller_args.append(
+ '--controller-initiator-port=%s' % controller_netloc[1])
+
+ self.command = ['morph', 'distbuild', '--local-changes=ignore']
+ self.command += controller_args + [self.system_name]
+
+ def start(self):
+ self.process = subprocess.Popen(self.command)
+
+ def completed(self):
+ return (self.process.poll() is not None)
+
+
+class ReleaseApp(cliapp.Application):
+
+ '''Cliapp app that handles distbuilding and deploying a cluster.'''
+
+ def add_settings(self):
+ self.settings.string_list(['controllers'],
+ 'a list of distbuild controllers and their '
+ 'architecture')
+
+ self.settings.string(['trove-host'],
+ 'hostname of Trove instance')
+
+ self.settings.string(['artifact-cache-server'],
+ 'server to fetch artifacts from', default=None)
+
+ self.settings.string(['release-number'],
+ 'Baserock version of the systems being built',
+ default='yy.ww')
+
+ def error(self, message):
+ raise cliapp.AppException(message)
+
+ def check_args(self, args):
+ if len(args) == 0:
+ self.error(
+ "Please pass the name of the release cluster (e.g. "
+ "clusters/release.morph)")
+
+ if len(args) > 1:
+ self.error("Too many arguments given.")
+
+ def process_args(self, args):
+ '''Process the command line'''
+ self.controllers = {}
+ controllers_list = self.settings['controllers']
+
+ for item in controllers_list:
+ arch, controller = item.split(':', 1)
+ self.controllers[arch] = controller
+
+ defs_repo = morphlib.definitions_repo.open(
+ '.', search_for_root=True)
+ self.loader = defs_repo.get_morphology_loader()
+ self.finder = morphlib.morphologyfinder.MorphologyFinder(defs_repo)
+
+ self.check_args(args)
+
+ cluster_name = args[0]
+ cluster, cluster_path = self.load_morphology(cluster_name)
+
+ builds = self.prepare_builds(cluster)
+ for build in builds:
+ build.start()
+
+ while not all(build.completed() for build in builds):
+ time.sleep(1)
+
+ fail = False
+ for build in builds:
+ if build.process.returncode != 0:
+ fail = True
+ sys.stderr.write(
+ 'Building failed for %s\n' % build.system_name)
+ if fail:
+ raise cliapp.AppException('Building of systems failed')
+
+ if not os.path.exists('release'):
+ os.mkdir('release')
+ self.deploy_images(cluster, cluster_path)
+
+ def load_morphology(self, name, kind=None):
+ path = morphlib.util.sanitise_morphology_path(name)
+ morph = self.loader.load_from_string(
+ self.finder.read_file(path))
+ if kind:
+ assert morph['kind'] == kind
+ return morph, path
+
+ def iterate_systems(self, system_list):
+ for system in system_list:
+ yield system['morph']
+ if 'subsystems' in system:
+ for subsystem in self.iterate_systems(system['subsystems']):
+ yield subsystem
+
+ def prepare_builds(self, cluster):
+ '''Prepare a list of builds'''
+ systems = set(self.iterate_systems(cluster['systems']))
+ builds = []
+ for system_name in systems:
+ system, _ = self.load_morphology(system_name)
+ if system['arch'] in self.controllers:
+ builds.append(Build(system_name, system['arch'], self))
+ else:
+ print("Unable to build %s: no %s distbuild available" %
+ (system_name, system['arch']))
+ return builds
+
+ def deploy_images(self, cluster, cluster_path):
+ version_label = 'baserock-%s' % self.settings['release-number']
+ outputs = {}
+
+ for system in cluster['systems']:
+ morphology_name = system['morph']
+ morphology = self.load_morphology(morphology_name)[0]
+ if morphology['arch'] not in self.controllers:
+ continue
+
+ for deployment_name, deployment_info in system['deploy'].iteritems():
+ # The release.morph cluster must specify a basename for the file,
+ # of name and extension. This script knows about name, but it
+ # can't find out the appropriate file extension without second
+ # guessing the behaviour of write extensions.
+ basename = deployment_info['location']
+
+ if '/' in basename or basename.startswith(version_label):
+ raise cliapp.AppException(
+ 'In %s: system %s.location should be just the base name, '
+ 'e.g. "%s.img"' % (cluster_path, deployment_name, deployment_name))
+
+ filename = os.path.join('release', '%s-%s' % (version_label, basename))
+ if os.path.exists(filename):
+ self.output.write('Reusing existing deployment of %s\n' % filename)
+ else:
+ self.output.write('Creating %s from release.morph\n' % filename)
+ self.deploy_single_image(cluster_path, deployment_name, filename, version_label)
+
+ def deploy_single_image(self, cluster_path, name, location, version_label):
+ deploy_command = [
+ 'morph', 'deploy', cluster_path, name,
+ '--trove-host=%s' % self.settings['trove-host']]
+ artifact_server = self.settings['artifact-cache-server']
+ if artifact_server is not None:
+ deploy_command.append('--artifact-cache-server=%s' % artifact_server)
+ deploy_command.extend((
+ '%s.location=%s' % (name, location),
+ '%s.VERSION_LABEL=%s' % (name, version_label)
+ ))
+
+ cliapp.runcmd(deploy_command, stdout=sys.stdout)
+
+
+ReleaseApp().run()