Mercurial > bitten > bitten-test
changeset 56:033366d81def
Build slave now executes the build. Closes #10.
author | cmlenz |
---|---|
date | Sun, 26 Jun 2005 17:34:27 +0000 |
parents | 649626cabc23 |
children | ef78d71667ad |
files | bitten/master.py bitten/model.py bitten/slave.py bitten/trac_ext/web_ui.py |
diffstat | 4 files changed, 91 insertions(+), 42 deletions(-) [+] |
line wrap: on
line diff
--- a/bitten/master.py +++ b/bitten/master.py @@ -23,7 +23,6 @@ import time from trac.env import Environment -from bitten import __version__ as VERSION from bitten.model import Build, BuildConfig from bitten.util import archive, beep, xmlio @@ -60,17 +59,22 @@ for config in BuildConfig.select(self.env): node = repos.get_node(config.path) + for path, rev, chg in node.get_history(): + # Check whether the latest revision of that configuration has + # already been built + builds = Build.select(self.env, config.name, rev) + if not list(builds): + logging.info('Enqueuing build of configuration "%s" as ' + 'of revision [%s]', config.name, rev) + build = Build(self.env) + build.config = config.name - # Check whether the latest revision of that configuration has - # already been built - builds = Build.select(self.env, config.name, node.rev) - if not list(builds): - logging.info('Enqueuing build of configuration "%s" as of revision [%s]', - config.name, node.rev) - build = Build(self.env) - build.config = config.name - build.rev = node.rev - build.insert() + chgset = repos.get_changeset(rev) + build.rev = rev + build.rev_time = chgset.date + + build.insert() + break finally: repos.close() @@ -87,7 +91,7 @@ active_builds = Build.select(self.env, slave=slave.name, status=Build.IN_PROGRESS) if not list(active_builds): - slave.initiate_build(build) + slave.send_initiation(build) break def get_snapshot(self, build, type, encoding): @@ -106,16 +110,22 @@ self.snapshots[(build.config, build.rev, type, encoding)] = snapshot return self.snapshots[(build.config, build.rev, type, encoding)] + class OrchestrationProfileHandler(beep.ProfileHandler): """Handler for communication on the Bitten build orchestration profile from the perspective of the build master. """ URI = 'http://bitten.cmlenz.net/beep/orchestration' + IDLE = 0 + STARTING = 1 + STARTED = 2 + def handle_connect(self): self.master = self.session.listener assert self.master self.name = None + self.state = self.IDLE def handle_disconnect(self): del self.master.slaves[self.name] @@ -153,11 +163,11 @@ logging.info('Registered slave "%s" (%s running %s %s [%s])', self.name, platform, os, os_version, os_family) - def initiate_build(self, build): + def send_initiation(self, build): logging.debug('Initiating build of "%s" on slave %s', build.config, self.name) - def handle_reply(cmd, msgno, msg): + def handle_reply(cmd, msgno, ansno, msg): if cmd == 'ERR': if msg.get_content_type() == beep.BEEP_XML: elem = xmlio.parse(msg.get_payload()) @@ -176,6 +186,7 @@ ('application/tar', None), ('application/zip', None)): break + type = None if not type: xml = xmlio.Element('error', code=550)[ 'None of the supported archive formats accepted' @@ -188,7 +199,7 @@ self.channel.send_msg(beep.MIMEMessage(xml), handle_reply=handle_reply) def send_snapshot(self, build, type, encoding): - def handle_reply(cmd, msgno, msg): + def handle_reply(cmd, msgno, ansno, msg): if cmd == 'ERR': if msg.get_content_type() == beep.BEEP_XML: elem = xmlio.parse(msg.get_payload()) @@ -196,12 +207,26 @@ logging.warning('Slave did not accept archive: %s (%d)', elem.gettext(), int(elem.attr['code'])) return - build.slave = self.name - build.time = int(time.time()) - build.status = Build.IN_PROGRESS - build.update() - logging.info('Slave %s started build of "%s" at [%s]', self.name, - build.config, build.rev) + if cmd == 'ANS': + if ansno == 0: + build.slave = self.name + build.time = int(time.time()) + build.status = Build.IN_PROGRESS + build.update() + logging.info('Slave %s started build of "%s" as of [%s]', + self.name, build.config, build.rev) + else: + elem = xmlio.parse(msg.get_payload()) + assert elem.name == 'step' + logging.info('Slave completed step "%s"', elem.attr['id']) + if elem.attr['result'] == 'failure': + logging.warning('Step failed: %s', elem.gettext()) + elif cmd == 'NUL': + logging.info('Slave %s completed build of "%s" as of [%s]', + self.name, build.config, build.rev) + build.duration = int(time.time()) - build.time + build.status = Build.SUCCESS # FIXME: or failure? + build.update() # TODO: should not block while reading the file; rather stream it using # asyncore push_with_producer() @@ -214,6 +239,7 @@ def main(): + from bitten import __version__ as VERSION from optparse import OptionParser parser = OptionParser(usage='usage: %prog [options] env-path',
--- a/bitten/model.py +++ b/bitten/model.py @@ -122,7 +122,7 @@ _table = Table('bitten_build', key=('config', 'rev', 'slave'))[ Column('config'), Column('rev'), Column('slave'), Column('time', type='int'), Column('duration', type='int'), - Column('status', size='1') + Column('status', size='1'), Column('rev_time', type='int') ] PENDING = 'P' @@ -132,11 +132,12 @@ def __init__(self, env, config=None, rev=None, slave=None, db=None): self.env = env - self.config = self.rev = self.slave = self.time = self.duration = None + self.config = self.rev = self.slave = None + self.time = self.duration = self.rev_time = None if config and rev and slave: self._fetch(config, rev, slave, db) else: - self.time = self.duration = 0 + self.time = self.duration = self.rev_time = 0 self.status = self.PENDING def _fetch(self, config, rev, slave, db=None): @@ -144,7 +145,7 @@ db = self.env.get_db_cnx() cursor = db.cursor() - cursor.execute("SELECT time,duration,status FROM bitten_build " + cursor.execute("SELECT time,duration,status,rev_time FROM bitten_build " "WHERE config=%s AND rev=%s AND slave=%s", (config, rev, slave)) row = cursor.fetchone() @@ -156,6 +157,7 @@ self.time = row[0] and int(row[0]) self.duration = row[1] and int(row[1]) self.status = row[2] + self.rev_time = int(row[3]) completed = property(fget=lambda self: self.status != Build.IN_PROGRESS) successful = property(fget=lambda self: self.status == Build.SUCCESS) @@ -183,16 +185,16 @@ else: handle_ta = False - assert self.config and self.rev + assert self.config and self.rev and self.rev_time assert self.status in (self.PENDING, self.IN_PROGRESS, self.SUCCESS, self.FAILURE) if not self.slave: assert self.status == self.PENDING cursor = db.cursor() - cursor.execute("INSERT INTO bitten_build VALUES (%s,%s,%s,%s,%s,%s)", + cursor.execute("INSERT INTO bitten_build VALUES (%s,%s,%s,%s,%s,%s,%s)", (self.config, self.rev, self.slave or '', self.time or 0, - self.duration or 0, self.status)) + self.duration or 0, self.status, self.rev_time)) if handle_ta: db.commit() @@ -237,10 +239,10 @@ where = "" cursor = db.cursor() - cursor.execute("SELECT config,rev,slave,time,duration,status " - "FROM bitten_build %s ORDER BY config,rev,slave" % where, - [wc[1] for wc in where_clauses]) - for config, rev, slave, time, duration, status in cursor: + cursor.execute("SELECT config,rev,slave,time,duration,status,rev_time " + "FROM bitten_build %s ORDER BY config,rev_time DESC," + "slave" % where, [wc[1] for wc in where_clauses]) + for config, rev, slave, time, duration, status, rev_time in cursor: build = Build(env) build.config = config build.rev = rev @@ -248,5 +250,6 @@ build.time = time and int(time) or 0 build.duration = duration and int(duration) or 0 build.status = status + build.rev_time = int(rev_time) yield build select = classmethod(select)
--- a/bitten/slave.py +++ b/bitten/slave.py @@ -24,7 +24,8 @@ import tempfile import time -from bitten import __version__ as VERSION +from bitten import BuildError +from bitten.recipe import Recipe from bitten.util import archive, beep, xmlio @@ -49,7 +50,7 @@ """Register with the build master.""" self.recipe_path = None - def handle_reply(cmd, msgno, msg): + def handle_reply(cmd, msgno, ansno, msg): if cmd == 'ERR': if msg.get_content_type() == beep.BEEP_XML: elem = xmlio.parse(msg.get_payload()) @@ -116,18 +117,37 @@ for filename in files: os.chmod(os.path.join(root, filename), 0400) - xml = xmlio.Element('ok') - self.channel.send_rpy(msgno, beep.MIMEMessage(xml)) + self.execute_build(msgno, path, self.recipe_path) - self.execute_build(path, self.recipe_path) + def execute_build(self, msgno, basedir, recipe_path): + logging.info('Building in directory %s using recipe %s', basedir, + recipe_path) - def execute_build(self, basedir, recipe): - logging.info('Would now build in directory %s using recipe %s', - basedir, recipe) - # TODO: Start the build process + recipe = Recipe(recipe_path, basedir) + + xml = xmlio.Element('started') + self.channel.send_ans(msgno, beep.MIMEMessage(xml)) + + for step in recipe: + logging.info('Executing build step "%s"', step.id) + try: + for function, args in step: + logging.debug('Executing command "%s"', function) + function(recipe.basedir, **args) + + xml = xmlio.Element('step', id=step.id, result='success', + description=step.description) + self.channel.send_ans(msgno, beep.MIMEMessage(xml)) + except BuildError, e: + xml = xmlio.Element('step', id=step.id, result='failure', + description=step.description)[e] + self.channel.send_ans(msgno, beep.MIMEMessage(xml)) + + self.channel.send_nul(msgno) def main(): + from bitten import __version__ as VERSION from optparse import OptionParser parser = OptionParser(usage='usage: %prog [options] host [port]',