4 # Copyright (C) 2010 Jelmer Vernooij <jelmer@samba.org>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from buildfarm import (
24 from buildfarm.build import (
32 from buildfarm.hostdb import (
41 from pysqlite2 import dbapi2 as sqlite3
44 from storm.database import create_database
45 from storm.expr import EXPR, FuncExpr, compile
46 from storm.locals import Bool, Desc, Int, RawStr, Reference, Unicode
47 from storm.store import Store
51 __slots__ = ("column", "type")
54 def __init__(self, column, type):
59 def compile_count(compile, cast, state):
60 state.push("context", EXPR)
61 column = compile(cast.column, state)
63 return "CAST(%s AS %s)" % (column, cast.type)
66 class StormBuild(Build):
67 __storm_table__ = "build"
69 id = Int(primary=True)
75 upload_time = Int(name="age")
76 status_str = RawStr(name="status")
83 return BuildStatus.__deserialize__(self.status_str)
85 def revision_details(self):
88 def log_checksum(self):
92 super(StormBuild, self).remove()
93 Store.of(self).remove(self)
95 def remove_logs(self):
96 super(StormBuild, self).remove_logs()
100 class StormHost(Host):
101 __storm_table__ = "host"
103 id = Int(primary=True)
105 owner_name = Unicode(name="owner")
106 owner_email = Unicode()
111 permission = Unicode()
112 last_dead_mail = Int()
115 def _set_owner(self, value):
117 self.owner_name = None
118 self.owner_email = None
120 (self.owner_name, self.owner_email) = value
122 def _get_owner(self):
123 if self.owner_name is None:
126 return (self.owner_name, self.owner_email)
128 owner = property(_get_owner, _set_owner)
131 class StormHostDatabase(HostDatabase):
133 def __init__(self, store=None):
135 self.store = memory_store()
139 def createhost(self, name, platform=None, owner=None, owner_email=None,
140 password=None, permission=None):
141 """See `HostDatabase.createhost`."""
142 newhost = StormHost(name, owner=owner, owner_email=owner_email,
143 password=password, permission=permission, platform=platform)
145 self.store.add(newhost)
147 except sqlite3.IntegrityError:
148 raise HostAlreadyExists(name)
151 def deletehost(self, name):
153 self.store.remove(self[name])
156 """Retrieve an iterable over all hosts."""
157 return self.store.find(StormHost).order_by(StormHost.name)
159 def __getitem__(self, name):
160 result = self.store.find(StormHost,
161 Cast(StormHost.name, "TEXT") == Cast(name, "TEXT"))
164 raise NoSuchHost(name)
171 class StormCachingBuildResultStore(BuildResultStore):
173 def __init__(self, basedir, store=None):
174 super(StormCachingBuildResultStore, self).__init__(basedir)
177 store = memory_store()
181 def get_by_checksum(self, checksum):
182 result = self.store.find(StormBuild,
183 Cast(StormBuild.checksum, "TEXT") == checksum)
186 raise NoSuchBuildError(None, None, None, None)
189 def __contains__(self, build):
191 self.get_by_checksum(build.log_checksum())
193 except NoSuchBuildError:
196 def get_previous_revision(self, tree, host, compiler, revision):
197 cur_build = self.get_build(tree, host, compiler, revision)
199 result = self.store.find(StormBuild,
200 Cast(StormBuild.tree, "TEXT") == Cast(tree, "TEXT"),
201 Cast(StormBuild.host, "TEXT") == Cast(host, "TEXT"),
202 Cast(StormBuild.compiler, "TEXT") == Cast(compiler, "TEXT"),
203 Cast(StormBuild.revision, "TEXT") != Cast(revision, "TEXT"),
204 StormBuild.id < cur_build.id)
205 result = result.order_by(Desc(StormBuild.id))
206 prev_build = result.first()
207 if prev_build is None:
208 raise NoSuchBuildError(tree, host, compiler, revision)
209 return prev_build.revision
211 def get_latest_revision(self, tree, host, compiler):
212 result = self.store.find(StormBuild,
213 StormBuild.tree == tree,
214 StormBuild.host == host,
215 StormBuild.compiler == compiler)
216 result = result.order_by(Desc(StormBuild.id))
217 build = result.first()
219 raise NoSuchBuildError(tree, host, compiler)
220 return build.revision
222 def upload_build(self, build):
224 existing_build = self.get_by_checksum(build.log_checksum())
225 except NoSuchBuildError:
229 assert build.tree == existing_build.tree
230 assert build.host == existing_build.host
231 assert build.compiler == existing_build.compiler
232 return existing_build
233 rev = build.revision_details()
234 super(StormCachingBuildResultStore, self).upload_build(build)
235 new_basename = self.build_fname(build.tree, build.host, build.compiler,
237 new_build = StormBuild(new_basename, build.tree, build.host,
239 new_build.checksum = build.log_checksum()
240 new_build.upload_time = build.upload_time
241 new_build.status_str = build.status().__serialize__()
242 new_build.basename = new_basename
243 host = self.store.find(StormHost,
244 Cast(StormHost.name, "TEXT") == Cast(build.host, "TEXT")).one()
245 assert host is not None, "Unable to find host %r" % build.host
246 new_build.host_id = host.id
247 self.store.add(new_build)
250 def get_old_builds(self, tree, host, compiler):
251 result = self.store.find(StormBuild,
252 StormBuild.tree == tree,
253 StormBuild.host == host,
254 StormBuild.compiler == compiler)
255 return result.order_by(Desc(StormBuild.upload_time))
257 def get_build(self, tree, host, compiler, revision=None, checksum=None):
259 Cast(StormBuild.tree, "TEXT") == Cast(tree, "TEXT"),
260 Cast(StormBuild.host, "TEXT") == Cast(host, "TEXT"),
261 Cast(StormBuild.compiler, "TEXT") == Cast(compiler, "TEXT"),
263 if revision is not None:
264 expr.append(Cast(StormBuild.revision, "TEXT") == Cast(revision, "TEXT"))
265 if checksum is not None:
266 expr.append(Cast(StormBuild.checksum, "TEXT") == Cast(checksum, "TEXT"))
267 result = self.store.find(StormBuild, *expr).order_by(Desc(StormBuild.upload_time))
270 raise NoSuchBuildError(tree, host, compiler, revision)
274 def distinct_builds(builds):
277 key = (build.tree, build.compiler, build.host)
284 class StormCachingBuildFarm(BuildFarm):
286 def __init__(self, path=None, store=None, timeout=0.5):
287 self.timeout = timeout
289 super(StormCachingBuildFarm, self).__init__(path)
291 def _get_store(self):
292 if self.store is not None:
294 db_path = os.path.join(self.path, "db", "hostdb.sqlite")
295 db = create_database("sqlite:%s?timeout=%f" % (db_path, self.timeout))
296 self.store = Store(db)
297 setup_schema(self.store)
300 def _open_hostdb(self):
301 return StormHostDatabase(self._get_store())
303 def _open_build_results(self):
304 path = os.path.join(self.path, "data", "oldrevs")
305 return StormCachingBuildResultStore(path, self._get_store())
307 def get_host_builds(self, host):
308 result = self._get_store().find(StormBuild, StormBuild.host == host)
309 return distinct_builds(result.order_by(Desc(StormBuild.upload_time)))
311 def get_tree_builds(self, tree):
312 result = self._get_store().find(StormBuild,
313 Cast(StormBuild.tree, "TEXT") == Cast(tree, "TEXT"))
314 return distinct_builds(result.order_by(Desc(StormBuild.upload_time)))
316 def get_last_builds(self):
317 result = self._get_store().find(StormBuild)
318 return distinct_builds(result.order_by(Desc(StormBuild.upload_time)))
320 def get_revision_builds(self, tree, revision=None):
321 return self._get_store().find(StormBuild,
322 Cast(StormBuild.tree, "TEXT") == Cast(tree, "TEXT"),
323 Cast(StormBuild.revision, "TEXT") == Cast(revision, "TEXT"))
329 class StormTree(Tree):
330 __storm_table__ = "tree"
332 id = Int(primary=True)
341 class StormTest(Test):
342 __storm_table__ = "test"
344 id = Int(primary=True)
348 class StormTestResult(TestResult):
349 __storm_table__ = "test_result"
351 id = Int(primary=True)
352 build_id = Int(name="build")
353 build = Reference(build_id, StormBuild)
355 test_id = Int(name="test")
356 test = Reference(test_id, StormTest)
359 def setup_schema(db):
360 db.execute("PRAGMA foreign_keys = 1;", noresult=True)
362 CREATE TABLE IF NOT EXISTS host (
363 id integer primary key autoincrement,
374 );""", noresult=True)
375 db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_hostname ON host (name);", noresult=True)
377 CREATE TABLE IF NOT EXISTS build (
378 id integer primary key autoincrement,
384 compiler blob not null,
390 FOREIGN KEY (host_id) REFERENCES host (id),
391 FOREIGN KEY (tree_id) REFERENCES tree (id),
392 FOREIGN KEY (compiler_id) REFERENCES compiler (id)
393 );""", noresult=True)
394 db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_checksum ON build (checksum);", noresult=True)
396 CREATE TABLE IF NOT EXISTS tree (
397 id integer primary key autoincrement,
406 CREATE UNIQUE INDEX IF NOT EXISTS unique_tree_name ON tree(name);
409 CREATE TABLE IF NOT EXISTS compiler (
410 id integer primary key autoincrement,
415 CREATE UNIQUE INDEX IF NOT EXISTS unique_compiler_name ON compiler(name);
418 CREATE TABLE IF NOT EXISTS test (
419 id integer primary key autoincrement,
422 db.execute("CREATE UNIQUE INDEX IF NOT EXISTS test_name ON test(name);",
424 db.execute("""CREATE TABLE IF NOT EXISTS test_result (
428 );""", noresult=True)
429 db.execute("""CREATE UNIQUE INDEX IF NOT EXISTS build_test_result ON test_result(build, test);""", noresult=True)
433 db = create_database("sqlite:")