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]',
--- a/bitten/trac_ext/web_ui.py
+++ b/bitten/trac_ext/web_ui.py
@@ -112,7 +112,7 @@
       </li><?cs
      /each ?>
     </ul><?cs
-   else ?><p>None</p><?cs
+   else ?><p>(None)</p><?cs
    /if ?></div><?cs
    if:build.can_modify ?><div class="buttons">
     <form method="get" action=""><div>
Copyright (C) 2012-2017 Edgewall Software