# HG changeset patch # User cmlenz # Date 1119966807 0 # Node ID 2332aedba328b14cfc9f27229e2e60f1c817c287 # Parent d108f780b76c6ef9c1a83a21f4ae8f4c61759dfa * Allow specifying a different name for a build slave (default is the host name). * Improve handling of aborted or failed builds. diff --git a/bitten/master.py b/bitten/master.py --- a/bitten/master.py +++ b/bitten/master.py @@ -90,7 +90,7 @@ status=Build.IN_PROGRESS) if not list(active_builds): slave.send_initiation(build) - break + return def get_snapshot(self, build, type, encoding): formats = { @@ -182,7 +182,7 @@ type = None if not type: xml = xmlio.Element('error', code=550)[ - 'None of the supported archive formats accepted' + 'None of the accepted archive formats supported' ] self.channel.send_err(beep.MIMEMessage(xml)) return @@ -192,16 +192,18 @@ self.channel.send_msg(beep.MIMEMessage(xml), handle_reply=handle_reply) def send_snapshot(self, build, type, encoding): + def handle_reply(cmd, msgno, ansno, msg): if cmd == 'ERR': - if msg.get_content_type() == beep.BEEP_XML: - elem = xmlio.parse(msg.get_payload()) - if elem.name == 'error': - logging.warning('Slave did not accept archive: %s (%d)', - elem.gettext(), int(elem.attr['code'])) - return + assert msg.get_content_type() == beep.BEEP_XML + elem = xmlio.parse(msg.get_payload()) + if elem.name == 'error': + logging.warning('Slave did not accept archive: %s (%d)', + elem.gettext(), int(elem.attr['code'])) if cmd == 'ANS': - if ansno == 0: + elem = xmlio.parse(msg.get_payload()) + logging.debug('Received build answer <%s>' % elem.name) + if elem.name == 'started': self.steps = [] build.slave = self.name build.time = int(time.time()) @@ -209,21 +211,34 @@ 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']) + elif elem.name == 'step': + logging.info('Slave completed step "%s"', + elem.attr['id']) if elem.attr['result'] == 'failure': logging.warning('Step failed: %s', elem.gettext()) - self.steps.append((elem.attr['id'], elem.attr['result'])) + self.steps.append((elem.attr['id'], + elem.attr['result'])) + elif elem.name == 'abort': + logging.info('Slave "%s" aborted build', self.name) + build.slave = None + build.time = 0 + build.status = Build.PENDING + elif elem.name == 'error': + build.status = Build.FAILURE 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 - if [step for step in self.steps if step[1] == 'failure']: - build.status = Build.FAILURE - else: - build.status = Build.SUCCESS + if build.status != Build.PENDING: # Completed + logging.info('Slave %s completed build of "%s" as of [%s]', + self.name, build.config, build.rev) + build.duration = int(time.time()) - build.time + if build.status is Build.IN_PROGRESS: + # Find out whether the build failed or succeeded + if [st for st in self.steps if st[1] == 'failure']: + build.status = Build.FAILURE + else: + build.status = Build.SUCCESS + else: # Aborted + build.slave = None + build.time = 0 build.update() # TODO: should not block while reading the file; rather stream it using diff --git a/bitten/slave.py b/bitten/slave.py --- a/bitten/slave.py +++ b/bitten/slave.py @@ -25,13 +25,17 @@ import time from bitten.build import BuildError -from bitten.recipe import Recipe +from bitten.recipe import Recipe, InvalidRecipeError from bitten.util import archive, beep, xmlio class Slave(beep.Initiator): """Build slave.""" + def __init__(self, ip, port, name=None): + beep.Initiator.__init__(self, ip, port) + self.name = name + def greeting_received(self, profiles): if OrchestrationProfileHandler.URI not in profiles: err = 'Peer does not support the Bitten orchestration profile' @@ -61,6 +65,8 @@ logging.info('Registration successful') sysname, nodename, release, version, machine = os.uname() + if self.session.name is not None: + nodename = self.session.name logging.info('Registering with build master as %s', nodename) xml = xmlio.Element('register', name=nodename)[ xmlio.Element('platform')[machine], @@ -123,26 +129,39 @@ logging.info('Building in directory %s using recipe %s', basedir, recipe_path) - recipe = Recipe(recipe_path, basedir) - - xml = xmlio.Element('started') - self.channel.send_ans(msgno, beep.MIMEMessage(xml)) + try: + recipe = Recipe(recipe_path, basedir) - 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.ctxt, **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)) + xml = xmlio.Element('started') + self.channel.send_ans(msgno, beep.MIMEMessage(xml)) - self.channel.send_nul(msgno) + 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.ctxt, **args) + xml = xmlio.Element('step', id=step.id, result='success', + description=step.description) + self.channel.send_ans(msgno, beep.MIMEMessage(xml)) + except (BuildError, InvalidRecipeError), 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) + + except InvalidRecipeError, e: + xml = xmlio.Element('error')[e] + self.channel.send_ans(msgno, beep.MIMEMessage(xml)) + self.channel.send_nul(msgno) + + except (KeyboardInterrupt, SystemExit), e: + xml = xmlio.Element('abort')['Build cancelled'] + self.channel.send_ans(msgno, beep.MIMEMessage(xml)) + self.channel.send_nul(msgno) + + raise beep.TerminateSession, 'Cancelled' def main(): @@ -151,6 +170,8 @@ parser = OptionParser(usage='usage: %prog [options] host [port]', version='%%prog %s' % VERSION) + parser.add_option('-n', '--name', action='store', dest='name', + help='name of this slave (defaults to host name)') parser.add_option('--debug', action='store_const', dest='loglevel', const=logging.DEBUG, help='enable debugging output') parser.add_option('-v', '--verbose', action='store_const', dest='loglevel', @@ -174,7 +195,7 @@ logging.getLogger().setLevel(options.loglevel) - slave = Slave(host, port) + slave = Slave(host, port, options.name) try: slave.run() except KeyboardInterrupt: