# HG changeset patch # User cmlenz # Date 1120585935 0 # Node ID ffa1ffd8c7dbbfdd13a5d8db8c179476fcdf0d07 # Parent 177d1c52ed48ff800c9cc811be95de56b480ca07 * Implement basic slave selection based on configured target platforms. Closes #15. * Simple build status overview in config view. diff --git a/bitten/master.py b/bitten/master.py --- a/bitten/master.py +++ b/bitten/master.py @@ -20,6 +20,7 @@ import logging import os.path +import re import time from trac.env import Environment @@ -52,25 +53,33 @@ def _check_build_triggers(self, master, when): self.schedule(self.TRIGGER_INTERVAL, self._check_build_triggers) - logging.debug('Checking for build triggers...') repos = self.env.get_repository() try: repos.sync() for config in BuildConfig.select(self.env): + logging.debug('Checking for changes to "%s" at %s', + config.label, config.path) 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 - build.rev = rev - build.rev_time = repos.get_changeset(rev).date - build.insert() + enqueued = False + for platform in TargetPlatform.select(self.env, config.name): + # Check whether the latest revision of the configuration + # has already been built on this platform + builds = Build.select(self.env, config.name, rev, + platform.id) + if not list(builds): + logging.info('Enqueuing build of configuration "%s"' + 'at revision [%s] on %s', config.name, + rev, platform.name) + build = Build(self.env) + build.config = config.name + build.rev = rev + build.rev_time = repos.get_changeset(rev).date + build.platform = platform.id + build.insert() + enqueued = True + if enqueued: break finally: repos.close() @@ -83,11 +92,31 @@ logging.debug('Checking for pending builds...') for build in Build.select(self.env, status=Build.PENDING): for slave in self.slaves.values(): - active_builds = Build.select(self.env, slave=slave.name, - status=Build.IN_PROGRESS) - if not list(active_builds): - slave.send_initiation(build) - return + matches_platform = True + platform = TargetPlatform(self.env, build.platform) + logging.debug('Matching slave %s against rules: %s', + slave.name, platform.rules) + for property, pattern in platform.rules: + if not property or not pattern: + continue + try: + if not re.match(pattern, slave.props.get(property)): + logging.debug('Rule (%s, %s) did not match "%s"', + property, pattern, slave.props.get(property)) + matches_platform = False + break + except re.error, e: + logging.error('Invalid platform matching pattern "%s"', + pattern, exc_info=True) + matches_platform = False + break + + if matches_platform: + active_builds = Build.select(self.env, slave=slave.name, + status=Build.IN_PROGRESS) + if not list(active_builds): + slave.send_initiation(build) + return def get_snapshot(self, build, type, encoding): formats = { @@ -212,7 +241,7 @@ if elem.name == 'started': self.steps = [] build.slave = self.name - build.slave_info = self.props + build.slave_info.update(self.props) build.started = int(time.time()) build.status = Build.IN_PROGRESS build.update() diff --git a/bitten/model.py b/bitten/model.py --- a/bitten/model.py +++ b/bitten/model.py @@ -242,9 +242,10 @@ _schema = [ Table('bitten_build', key='id')[ Column('id', auto_increment=True), Column('config'), Column('rev'), - Column('rev_time', type='int'), Column('slave'), - Column('started', type='int'), Column('stopped', type='int'), - Column('status', size='1'), Index(['config', 'rev', 'slave']) + Column('rev_time', type='int'), Column('platform', type='int'), + Column('slave'), Column('started', type='int'), + Column('stopped', type='int'), Column('status', size='1'), + Index(['config', 'rev', 'slave']) ], Table('bitten_slave', key=('build', 'propname'))[ Column('build', type='int'), Column('propname'), Column('propvalue') @@ -272,7 +273,7 @@ if id is not None: self._fetch(id, db) else: - self.id = self.config = self.rev = self.slave = None + self.id = self.config = self.rev = self.platform = self.slave = None self.started = self.stopped = self.rev_time = 0 self.status = self.PENDING @@ -281,8 +282,8 @@ db = self.env.get_db_cnx() cursor = db.cursor() - cursor.execute("SELECT config,rev,rev_time,slave,started,stopped," - "status FROM bitten_build WHERE id=%s", (id,)) + cursor.execute("SELECT config,rev,rev_time,platform,slave,started," + "stopped,status FROM bitten_build WHERE id=%s", (id,)) row = cursor.fetchone() if not row: raise Exception, "Build %s not found" % id @@ -290,10 +291,11 @@ self.config = row[0] self.rev = row[1] self.rev_time = int(row[2]) - self.slave = row[3] - self.started = row[4] and int(row[4]) - self.stopped = row[5] and int(row[5]) - self.status = row[6] + self.platform = int(row[3]) + self.slave = row[4] + self.started = row[5] and int(row[5]) + self.stopped = row[6] and int(row[6]) + self.status = row[7] cursor.execute("SELECT propname,propvalue FROM bitten_slave " "WHERE build=%s", (self.id,)) @@ -325,18 +327,19 @@ else: handle_ta = False - assert self.config and self.rev and self.rev_time + assert self.config and self.rev and self.rev_time and self.platform 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 (config,rev,rev_time,slave," - "started,stopped,status) " - "VALUES (%s,%s,%s,%s,%s,%s,%s)", - (self.config, self.rev, self.rev_time, self.slave or '', - self.started or 0, self.stopped or 0, self.status)) + cursor.execute("INSERT INTO bitten_build (config,rev,rev_time,platform," + "slave,started,stopped,status) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", + (self.config, self.rev, self.rev_time, self.platform, + self.slave or '', self.started or 0, self.stopped or 0, + self.status)) self.id = db.get_last_id('bitten_build') cursor.executemany("INSERT INTO bitten_slave VALUES (%s,%s,%s)", [(self.id, name, value) for name, value @@ -363,11 +366,15 @@ "stopped=%s,status=%s WHERE id=%s", (self.slave or '', self.started or 0, self.stopped or 0, self.status, self.id)) + cursor.execute("DELETE FROM bitten_slave WHERE build=%s", (self.id)) + cursor.executemany("INSERT INTO bitten_slave VALUES (%s,%s,%s)", + [(self.id, name, value) for name, value + in self.slave_info.items()]) if handle_ta: db.commit() - def select(cls, env, config=None, rev=None, slave=None, status=None, - db=None): + def select(cls, env, config=None, rev=None, platform=None, slave=None, + status=None, db=None): if not db: db = env.get_db_cnx() @@ -376,6 +383,8 @@ where_clauses.append(("config=%s", config)) if rev is not None: where_clauses.append(("rev=%s", rev)) + if platform is not None: + where_clauses.append(("platform=%s", platform)) if slave is not None: where_clauses.append(("slave=%s", slave)) if status is not None: @@ -386,16 +395,17 @@ where = "" cursor = db.cursor() - cursor.execute("SELECT id,config,rev,slave,started,stopped,status," - "rev_time FROM bitten_build %s " + cursor.execute("SELECT id,config,rev,platform,slave,started,stopped," + "status,rev_time FROM bitten_build %s " "ORDER BY config,rev_time DESC,slave" % where, [wc[1] for wc in where_clauses]) - for id, config, rev, slave, started, stopped, status, rev_time \ - in cursor: + for id, config, rev, platform, slave, started, stopped, status, \ + rev_time in cursor: build = Build(env) build.id = id build.config = config build.rev = rev + build.platform = platform build.slave = slave build.started = started and int(started) or 0 build.stopped = stopped and int(stopped) or 0 diff --git a/bitten/tests/model.py b/bitten/tests/model.py --- a/bitten/tests/model.py +++ b/bitten/tests/model.py @@ -134,15 +134,16 @@ build.config = 'test' build.rev = '42' build.rev_time = 12039 + build.platform = 1 build.slave_info[Build.IP_ADDRESS] = '127.0.0.1' build.slave_info[Build.MAINTAINER] = 'joe@example.org' build.insert() db = self.env.get_db_cnx() cursor = db.cursor() - cursor.execute("SELECT config,rev,slave,started,stopped,status " - "FROM bitten_build WHERE id=%s" % build.id) - self.assertEqual(('test', '42', '', 0, 0, 'P'), cursor.fetchone()) + cursor.execute("SELECT config,rev,platform,slave,started,stopped,status" + " FROM bitten_build WHERE id=%s" % build.id) + self.assertEqual(('test', '42', 1, '', 0, 0, 'P'), cursor.fetchone()) cursor.execute("SELECT propname,propvalue FROM bitten_slave") expected = {Build.IP_ADDRESS: '127.0.0.1', @@ -150,23 +151,36 @@ for propname, propvalue in cursor: self.assertEqual(expected[propname], propvalue) - def test_insert_no_config_or_rev_or_rev_time(self): + def test_insert_no_config_or_rev_or_rev_time_or_platform(self): build = Build(self.env) self.assertRaises(AssertionError, build.insert) + # No config + build = Build(self.env) + build.rev = '42' + build.rev_time = 12039 + build.platform = 1 + self.assertRaises(AssertionError, build.insert) + + # No rev build = Build(self.env) build.config = 'test' build.rev_time = 12039 + build.platform = 1 self.assertRaises(AssertionError, build.insert) - build = Build(self.env) - build.rev = '42' - build.rev_time = 12039 - self.assertRaises(AssertionError, build.insert) - + # No rev time build = Build(self.env) build.config = 'test' build.rev = '42' + build.platform = 1 + self.assertRaises(AssertionError, build.insert) + + # No platform + build = Build(self.env) + build.config = 'test' + build.rev = '42' + build.rev_time = 12039 self.assertRaises(AssertionError, build.insert) def test_insert_no_slave(self): @@ -174,6 +188,7 @@ build.config = 'test' build.rev = '42' build.rev_time = 12039 + build.platform = 1 build.status = Build.SUCCESS self.assertRaises(AssertionError, build.insert) build.status = Build.FAILURE @@ -194,9 +209,10 @@ def test_fetch(self): db = self.env.get_db_cnx() cursor = db.cursor() - cursor.execute("INSERT INTO bitten_build (config,rev,rev_time,slave," - "started,stopped,status) VALUES (%s,%s,%s,%s,%s,%s,%s)", - ('test', '42', 12039, 'tehbox', 15006, 16007, + cursor.execute("INSERT INTO bitten_build (config,rev,rev_time,platform," + "slave,started,stopped,status) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", + ('test', '42', 12039, 1, 'tehbox', 15006, 16007, Build.SUCCESS)) build_id = db.get_last_id('bitten_build') cursor.executemany("INSERT INTO bitten_slave VALUES (%s,%s,%s)", diff --git a/bitten/trac_ext/web_ui.py b/bitten/trac_ext/web_ui.py --- a/bitten/trac_ext/web_ui.py +++ b/bitten/trac_ext/web_ui.py @@ -136,18 +136,19 @@ add_stylesheet(req, 'build.css') db = self.env.get_db_cnx() cursor = db.cursor() - cursor.execute("SELECT id,config,label,rev,slave,stopped,status " - "FROM bitten_build " - " INNER JOIN bitten_config ON (name=config) " - "WHERE stopped>=%s AND stopped<=%s " - "AND status IN (%s, %s) ORDER BY stopped", + cursor.execute("SELECT b.id,b.config,c.label,b.rev,p.name,b.slave," + "b.stopped,b.status FROM bitten_build AS b" + " INNER JOIN bitten_config AS c ON (c.name=b.config)" + " INNER JOIN bitten_platform AS p ON (p.id=b.platform) " + "WHERE b.stopped>=%s AND b.stopped<=%s " + "AND b.status IN (%s, %s) ORDER BY b.stopped", (start, stop, Build.SUCCESS, Build.FAILURE)) event_kinds = {Build.SUCCESS: 'successbuild', Build.FAILURE: 'failedbuild'} - for id, config, label, rev, slave, stopped, status in cursor: - title = 'Build %s by %s %s' \ - % (escape(rev), escape(label), escape(id), - escape(slave), self._status_label[status]) + for id, config, label, rev, platform, slave, stopped, status in cursor: + title = 'Build of %s [%s] by %s (%s) %s' \ + % (escape(label), escape(rev), escape(slave), + escape(platform), self._status_label[status]) href = self.env.href.build(config, id) yield event_kinds[status], href, title, stopped, None, '' @@ -307,16 +308,21 @@ req.hdf['build.mode'] = 'view_config' platforms = TargetPlatform.select(self.env, config=config_name) - req.hdf['build.platforms'] = [platform.name for platform in platforms] + req.hdf['build.platforms'] = [ + {'name': platform.name, 'id': platform.id} for platform in platforms + ] repos = self.env.get_repository(req.authname) root = repos.get_node(config.path) num = 0 for idx, (path, rev, chg) in enumerate(root.get_history()): - prefix = 'build.config.builds.%d' % rev + prefix = 'build.builds.%d' % rev + req.hdf[prefix + '.href'] = self.env.href.changeset(rev) for build in Build.select(self.env, config=config.name, rev=rev): - req.hdf[prefix + '.' + build.slave] = self._build_to_hdf(build) - if idx > 5: + if build.status == Build.PENDING: + continue + req.hdf['%s.%s' % (prefix, build.platform)] = self._build_to_hdf(build) + if idx > 4: break def _render_config_form(self, req, config_name=None): @@ -375,8 +381,10 @@ } def _build_to_hdf(self, build): - hdf = {'name': build.slave, 'status': self._status_label[build.status], - 'rev': build.rev, + hdf = {'id': build.id, 'name': build.slave, 'rev': build.rev, + 'status': self._status_label[build.status], + 'cls': self._status_label[build.status].replace(' ', '-'), + 'href': self.env.href.build(build.config, build.id), 'chgset_href': self.env.href.changeset(build.rev)} if build.started: hdf['started'] = strftime('%x %X', localtime(build.started)) diff --git a/htdocs/build.css b/htdocs/build.css --- a/htdocs/build.css +++ b/htdocs/build.css @@ -8,3 +8,10 @@ #content.build form.platforms ul { list-style: none; padding-left: 1em; } #content.build #builds { margin-top: 2em; } +#content.build #builds th.rev { width: 6em; } +#content.build #builds td :link, #content.build #builds td :visited { + font-weight: bold; +} +#content.build #builds td.completed { background: #9d9; } +#content.build #builds td.failed { background: #d99; } +#content.build #builds td.in-progress { background: #ff9; } diff --git a/templates/build.cs b/templates/build.cs --- a/templates/build.cs +++ b/templates/build.cs @@ -87,9 +87,28 @@ -
Build -
Changeset + []
started agotook