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.locals import Bool, Desc, Int, Unicode, RawStr
44 from storm.store import Store
47 class StormBuild(Build):
48 __storm_table__ = "build"
50 id = Int(primary=True)
56 upload_time = Int(name="age")
57 status_str = RawStr(name="status")
62 return BuildStatus.__deserialize__(self.status_str)
64 def revision_details(self):
65 return (self.revision, None)
67 def log_checksum(self):
71 super(StormBuild, self).remove()
72 Store.of(self).remove(self)
74 def remove_logs(self):
75 super(StormBuild, self).remove_logs()
79 class StormHost(Host):
80 __storm_table__ = "host"
82 id = Int(primary=True)
84 owner_name = Unicode(name="owner")
85 owner_email = Unicode()
90 permission = Unicode()
91 last_dead_mail = Int()
94 def _set_owner(self, value):
96 self.owner_name = None
97 self.owner_email = None
99 (self.owner_name, self.owner_email) = value
101 def _get_owner(self):
102 if self.owner_name is None:
105 return (self.owner_name, self.owner_email)
107 owner = property(_get_owner, _set_owner)
110 class StormHostDatabase(HostDatabase):
112 def __init__(self, store=None):
114 self.store = memory_store()
118 def createhost(self, name, platform=None, owner=None, owner_email=None,
119 password=None, permission=None):
120 """See `HostDatabase.createhost`."""
121 newhost = StormHost(name, owner=owner, owner_email=owner_email,
122 password=password, permission=permission, platform=platform)
124 self.store.add(newhost)
126 except sqlite3.IntegrityError:
127 raise HostAlreadyExists(name)
130 def deletehost(self, name):
132 self.store.remove(self[name])
135 """Retrieve an iterable over all hosts."""
136 return self.store.find(StormHost).order_by(StormHost.name)
138 def __getitem__(self, name):
139 ret = self.store.find(StormHost, StormHost.name==name).one()
141 raise NoSuchHost(name)
148 class StormCachingBuildResultStore(BuildResultStore):
150 def __init__(self, basedir, store=None):
151 super(StormCachingBuildResultStore, self).__init__(basedir)
154 store = memory_store()
158 def __contains__(self, build):
159 return (self._get_by_checksum(build) is not None)
161 def get_previous_revision(self, tree, host, compiler, revision):
162 result = self.store.find(StormBuild,
163 StormBuild.tree == tree,
164 StormBuild.host == host,
165 StormBuild.compiler == compiler,
166 StormBuild.revision == revision)
167 cur_build = result.any()
168 if cur_build is None:
169 raise NoSuchBuildError(tree, host, compiler, revision)
171 result = self.store.find(StormBuild,
172 StormBuild.tree == tree,
173 StormBuild.host == host,
174 StormBuild.compiler == compiler,
175 StormBuild.revision != revision,
176 StormBuild.id < cur_build.id)
177 result = result.order_by(Desc(StormBuild.id))
178 prev_build = result.first()
179 if prev_build is None:
180 raise NoSuchBuildError(tree, host, compiler, revision)
181 return prev_build.revision
183 def get_latest_revision(self, tree, host, compiler):
184 result = self.store.find(StormBuild,
185 StormBuild.tree == tree,
186 StormBuild.host == host,
187 StormBuild.compiler == compiler)
188 result = result.order_by(Desc(StormBuild.id))
189 build = result.first()
191 raise NoSuchBuildError(tree, host, compiler)
192 return build.revision
194 def _get_by_checksum(self, build):
195 result = self.store.find(StormBuild,
196 StormBuild.checksum == build.log_checksum())
199 def upload_build(self, build):
200 existing_build = self._get_by_checksum(build)
201 if existing_build is not None:
203 assert build.tree == existing_build.tree
204 assert build.host == existing_build.host
205 assert build.compiler == existing_build.compiler
206 return existing_build
207 rev, timestamp = build.revision_details()
208 super(StormCachingBuildResultStore, self).upload_build(build)
209 new_basename = self.build_fname(build.tree, build.host, build.compiler, rev)
210 new_build = StormBuild(new_basename, build.tree, build.host,
212 new_build.checksum = build.log_checksum()
213 new_build.upload_time = build.upload_time
214 new_build.status_str = build.status().__serialize__()
215 new_build.basename = new_basename
216 self.store.add(new_build)
219 def get_old_revs(self, tree, host, compiler):
220 return self.store.find(StormBuild,
221 StormBuild.tree == tree,
222 StormBuild.host == host,
223 StormBuild.compiler == compiler).order_by(Desc(StormBuild.upload_time))
225 def get_build(self, tree, host, compiler, revision=None, checksum=None):
227 StormBuild.tree == tree,
228 StormBuild.host == host,
229 StormBuild.compiler == compiler,
231 if revision is not None:
232 expr.append(StormBuild.revision == revision)
233 if checksum is not None:
234 expr.append(StormBuild.checksum == checksum)
235 result = self.store.find(StormBuild, *expr).order_by(Desc(StormBuild.upload_time))
238 raise NoSuchBuildError(tree, host, compiler, revision)
242 class StormCachingBuildFarm(BuildFarm):
244 def __init__(self, path=None, store=None, timeout=0.5):
245 self.timeout = timeout
247 super(StormCachingBuildFarm, self).__init__(path)
249 def _get_store(self):
250 if self.store is not None:
252 db_path = os.path.join(self.path, "db", "hostdb.sqlite")
253 db = create_database("sqlite:%s?timeout=%f" % (db_path, self.timeout))
254 self.store = Store(db)
255 setup_schema(self.store)
258 def _open_hostdb(self):
259 return StormHostDatabase(self._get_store())
261 def _open_build_results(self):
262 return StormCachingBuildResultStore(os.path.join(self.path, "data", "oldrevs"),
265 def get_host_builds(self, host):
266 return self._get_store().find(StormBuild,
267 StormBuild.host==host).group_by(StormBuild.compiler, StormBuild.tree)
269 def get_tree_builds(self, tree):
270 result = self._get_store().find(StormBuild, StormBuild.tree == tree)
271 return result.order_by(Desc(StormBuild.upload_time))
273 def get_last_builds(self):
274 result = self._get_store().find(StormBuild)
275 return result.group_by(
276 StormBuild.tree, StormBuild.compiler, StormBuild.host).order_by(
277 Desc(StormBuild.upload_time))
283 class StormTree(Tree):
284 __storm_table__ = "tree"
286 id = Int(primary=True)
295 def setup_schema(db):
296 db.execute("PRAGMA foreign_keys = 1;", noresult=True)
298 CREATE TABLE IF NOT EXISTS host (
299 id integer primary key autoincrement,
310 );""", noresult=True)
311 db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_hostname ON host (name);", noresult=True)
313 CREATE TABLE IF NOT EXISTS build (
314 id integer primary key autoincrement,
319 compiler blob not null,
324 FOREIGN KEY (host_id) REFERENCES host (id)
325 );""", noresult=True)
326 db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_checksum ON build (checksum);", noresult=True)
328 CREATE TABLE IF NOT EXISTS tree (
329 id integer primary key autoincrement,
335 );""", noresult=True)
339 db = create_database("sqlite:")