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.data import (
30 from buildfarm.hostdb import (
39 from pysqlite2 import dbapi2 as sqlite3
42 from storm.database import create_database
43 from storm.expr import EXPR, FuncExpr, compile
44 from storm.locals import Bool, Desc, Int, Unicode, RawStr
45 from storm.store import Store
49 __slots__ = ("column", "type")
52 def __init__(self, column, type):
57 def compile_count(compile, cast, state):
58 state.push("context", EXPR)
59 column = compile(cast.column, state)
61 return "CAST(%s AS %s)" % (column, cast.type)
64 class StormBuild(Build):
65 __storm_table__ = "build"
67 id = Int(primary=True)
73 upload_time = Int(name="age")
74 status_str = RawStr(name="status")
79 return BuildStatus.__deserialize__(self.status_str)
81 def revision_details(self):
84 def log_checksum(self):
88 super(StormBuild, self).remove()
89 Store.of(self).remove(self)
91 def remove_logs(self):
92 super(StormBuild, self).remove_logs()
96 class StormHost(Host):
97 __storm_table__ = "host"
99 id = Int(primary=True)
101 owner_name = Unicode(name="owner")
102 owner_email = Unicode()
107 permission = Unicode()
108 last_dead_mail = Int()
111 def _set_owner(self, value):
113 self.owner_name = None
114 self.owner_email = None
116 (self.owner_name, self.owner_email) = value
118 def _get_owner(self):
119 if self.owner_name is None:
122 return (self.owner_name, self.owner_email)
124 owner = property(_get_owner, _set_owner)
127 class StormHostDatabase(HostDatabase):
129 def __init__(self, store=None):
131 self.store = memory_store()
135 def createhost(self, name, platform=None, owner=None, owner_email=None,
136 password=None, permission=None):
137 """See `HostDatabase.createhost`."""
138 newhost = StormHost(name, owner=owner, owner_email=owner_email,
139 password=password, permission=permission, platform=platform)
141 self.store.add(newhost)
143 except sqlite3.IntegrityError:
144 raise HostAlreadyExists(name)
147 def deletehost(self, name):
149 self.store.remove(self[name])
152 """Retrieve an iterable over all hosts."""
153 return self.store.find(StormHost).order_by(StormHost.name)
155 def __getitem__(self, name):
156 ret = self.store.find(StormHost, StormHost.name==name).one()
158 raise NoSuchHost(name)
165 class StormCachingBuildResultStore(BuildResultStore):
167 def __init__(self, basedir, store=None):
168 super(StormCachingBuildResultStore, self).__init__(basedir)
171 store = memory_store()
175 def __contains__(self, build):
176 return (self._get_by_checksum(build) is not None)
178 def get_previous_revision(self, tree, host, compiler, revision):
179 result = self.store.find(StormBuild,
180 StormBuild.tree == tree,
181 StormBuild.host == host,
182 StormBuild.compiler == compiler,
183 Cast(StormBuild.revision, "TEXT") == revision)
184 cur_build = result.any()
185 if cur_build is None:
186 raise NoSuchBuildError(tree, host, compiler, revision)
188 result = self.store.find(StormBuild,
189 StormBuild.tree == tree,
190 StormBuild.host == host,
191 StormBuild.compiler == compiler,
192 Cast(StormBuild.revision, "TEXT") != revision,
193 StormBuild.id < cur_build.id)
194 result = result.order_by(Desc(StormBuild.id))
195 prev_build = result.first()
196 if prev_build is None:
197 raise NoSuchBuildError(tree, host, compiler, revision)
198 return prev_build.revision
200 def get_latest_revision(self, tree, host, compiler):
201 result = self.store.find(StormBuild,
202 StormBuild.tree == tree,
203 StormBuild.host == host,
204 StormBuild.compiler == compiler)
205 result = result.order_by(Desc(StormBuild.id))
206 build = result.first()
208 raise NoSuchBuildError(tree, host, compiler)
209 return build.revision
211 def _get_by_checksum(self, build):
212 result = self.store.find(StormBuild,
213 Cast(StormBuild.checksum, "TEXT") == build.log_checksum())
216 def upload_build(self, build):
217 existing_build = self._get_by_checksum(build)
218 if existing_build is not None:
220 assert build.tree == existing_build.tree
221 assert build.host == existing_build.host
222 assert build.compiler == existing_build.compiler
223 return existing_build
224 rev = build.revision_details()
225 super(StormCachingBuildResultStore, self).upload_build(build)
226 new_basename = self.build_fname(build.tree, build.host, build.compiler, rev)
227 new_build = StormBuild(new_basename, build.tree, build.host,
229 new_build.checksum = build.log_checksum()
230 new_build.upload_time = build.upload_time
231 new_build.status_str = build.status().__serialize__()
232 new_build.basename = new_basename
233 self.store.add(new_build)
236 def get_old_builds(self, tree, host, compiler):
237 result = self.store.find(StormBuild,
238 StormBuild.tree == tree,
239 StormBuild.host == host,
240 StormBuild.compiler == compiler)
241 return result.order_by(Desc(StormBuild.upload_time))
243 def get_build(self, tree, host, compiler, revision=None, checksum=None):
245 StormBuild.tree == tree,
246 StormBuild.host == host,
247 StormBuild.compiler == compiler,
249 if revision is not None:
250 expr.append(Cast(StormBuild.revision, "TEXT") == revision)
251 if checksum is not None:
252 expr.append(Cast(StormBuild.checksum, "TEXT") == checksum)
253 result = self.store.find(StormBuild, *expr).order_by(Desc(StormBuild.upload_time))
256 raise NoSuchBuildError(tree, host, compiler, revision)
260 def distinct_builds(builds):
263 key = (build.tree, build.compiler, build.host)
270 class StormCachingBuildFarm(BuildFarm):
272 def __init__(self, path=None, store=None, timeout=0.5):
273 self.timeout = timeout
275 super(StormCachingBuildFarm, self).__init__(path)
277 def _get_store(self):
278 if self.store is not None:
280 db_path = os.path.join(self.path, "db", "hostdb.sqlite")
281 db = create_database("sqlite:%s?timeout=%f" % (db_path, self.timeout))
282 self.store = Store(db)
283 setup_schema(self.store)
286 def _open_hostdb(self):
287 return StormHostDatabase(self._get_store())
289 def _open_build_results(self):
290 path = os.path.join(self.path, "data", "oldrevs")
291 return StormCachingBuildResultStore(path, self._get_store())
293 def get_host_builds(self, host):
294 result = self._get_store().find(StormBuild, StormBuild.host == host)
295 return distinct_builds(result.order_by(Desc(StormBuild.upload_time)))
297 def get_tree_builds(self, tree):
298 result = self._get_store().find(StormBuild, Cast(StormBuild.tree, "TEXT") == tree)
299 return distinct_builds(result.order_by(Desc(StormBuild.upload_time)))
301 def get_last_builds(self):
302 result = self._get_store().find(StormBuild)
303 return distinct_builds(result.order_by(Desc(StormBuild.upload_time)))
305 def get_revision_builds(self, tree, revision=None):
306 return self._get_store().find(StormBuild,
307 Cast(StormBuild.tree, "TEXT") == tree,
308 Cast(StormBuild.revision, "TEXT") == revision)
314 class StormTree(Tree):
315 __storm_table__ = "tree"
317 id = Int(primary=True)
326 def setup_schema(db):
327 db.execute("PRAGMA foreign_keys = 1;", noresult=True)
329 CREATE TABLE IF NOT EXISTS host (
330 id integer primary key autoincrement,
341 );""", noresult=True)
342 db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_hostname ON host (name);", noresult=True)
344 CREATE TABLE IF NOT EXISTS build (
345 id integer primary key autoincrement,
350 compiler blob not null,
355 FOREIGN KEY (host_id) REFERENCES host (id)
356 );""", noresult=True)
357 db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_checksum ON build (checksum);", noresult=True)
359 CREATE TABLE IF NOT EXISTS tree (
360 id integer primary key autoincrement,
366 );""", noresult=True)
370 db = create_database("sqlite:")