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 (
23 from buildfarm.data import (
29 from buildfarm.hostdb import (
38 from pysqlite2 import dbapi2 as sqlite3
41 from storm.database import create_database
42 from storm.locals import Bool, Desc, Int, Unicode, RawStr
43 from storm.store import Store
46 class StormBuild(Build):
47 __storm_table__ = "build"
49 id = Int(primary=True)
56 status_str = Unicode(name="status")
57 commit_revision = RawStr()
60 return BuildStatus.__deserialize__(self.status_str)
62 def revision_details(self):
63 return (self.commit_revision, None)
65 def log_checksum(self):
69 super(StormBuild, self).remove()
70 Store.of(self).remove(self)
73 class StormHost(Host):
74 __storm_table__ = "host"
76 name = Unicode(primary=True)
77 owner_name = Unicode(name="owner")
78 owner_email = Unicode()
83 permission = Unicode()
84 last_dead_mail = Int()
87 def _set_owner(self, value):
89 self.owner_name = None
90 self.owner_email = None
92 (self.owner_name, self.owner_email) = value
95 if self.owner_name is None:
98 return (self.owner_name, self.owner_email)
100 owner = property(_get_owner, _set_owner)
103 class StormHostDatabase(HostDatabase):
105 def __init__(self, store=None):
107 self.store = memory_store()
111 def createhost(self, name, platform=None, owner=None, owner_email=None,
112 password=None, permission=None):
113 """See `HostDatabase.createhost`."""
114 newhost = StormHost(unicode(name), owner=owner, owner_email=owner_email, password=password, permission=permission, platform=platform)
116 self.store.add(newhost)
118 except sqlite3.IntegrityError:
119 raise HostAlreadyExists(name)
122 def deletehost(self, name):
124 host = self.host(name)
125 self.store.remove(host)
128 """Retrieve an iterable over all hosts."""
129 return self.store.find(StormHost).order_by(StormHost.name)
131 def host(self, name):
132 ret = self.store.find(StormHost, StormHost.name==unicode(name)).one()
134 raise NoSuchHost(name)
141 class StormCachingBuildResultStore(BuildResultStore):
143 def __init__(self, basedir, store=None):
144 super(StormCachingBuildResultStore, self).__init__(basedir)
147 store = memory_store()
151 def __contains__(self, build):
152 return (self._get_by_checksum(build) is not None)
154 def get_previous_revision(self, tree, host, compiler, revision):
155 result = self.store.find(StormBuild,
156 StormBuild.tree == unicode(tree),
157 StormBuild.host == unicode(host),
158 StormBuild.compiler == unicode(compiler),
159 StormBuild.commit_revision == revision)
160 cur_build = result.any()
161 if cur_build is None:
162 raise NoSuchBuildError(tree, host, compiler, revision)
164 result = self.store.find(StormBuild,
165 StormBuild.tree == unicode(tree),
166 StormBuild.host == unicode(host),
167 StormBuild.compiler == unicode(compiler),
168 StormBuild.commit_revision != revision,
169 StormBuild.id < cur_build.id)
170 result = result.order_by(Desc(StormBuild.id))
171 prev_build = result.first()
172 if prev_build is None:
173 raise NoSuchBuildError(tree, host, compiler, revision)
174 return prev_build.commit_revision
176 def get_latest_revision(self, tree, host, compiler):
177 result = self.store.find(StormBuild,
178 StormBuild.tree == unicode(tree),
179 StormBuild.host == unicode(host),
180 StormBuild.compiler == unicode(compiler))
181 result = result.order_by(Desc(StormBuild.id))
182 build = result.first()
184 raise NoSuchBuildError(tree, host, compiler)
185 return build.revision
187 def _get_by_checksum(self, build):
188 result = self.store.find(StormBuild,
189 StormBuild.checksum == build.log_checksum())
192 def upload_build(self, build):
193 existing_build = self._get_by_checksum(build)
194 if existing_build is not None:
196 assert build.tree == existing_build.tree
197 assert build.host == existing_build.host
198 assert build.compiler == existing_build.compiler
199 return existing_build
200 rev, timestamp = build.revision_details()
201 super(StormCachingBuildResultStore, self).upload_build(build)
202 new_basename = self.build_fname(build.tree, build.host, build.compiler, rev)
203 new_build = StormBuild(new_basename, unicode(build.tree), unicode(build.host),
204 unicode(build.compiler), rev)
205 new_build.checksum = build.log_checksum()
206 new_build.age = build.age_mtime()
207 new_build.status_str = unicode(build.status().__serialize__())
208 self.store.add(new_build)
211 def get_old_revs(self, tree, host, compiler):
212 return self.store.find(StormBuild,
213 StormBuild.tree == unicode(tree),
214 StormBuild.host == unicode(host),
215 StormBuild.compiler == unicode(compiler)).order_by(Desc(StormBuild.age))
218 class StormCachingBuildFarm(BuildFarm):
220 def __init__(self, path=None, store=None, timeout=0.5):
221 self.timeout = timeout
223 super(StormCachingBuildFarm, self).__init__(path)
225 def _get_store(self):
226 if self.store is not None:
228 db_path = os.path.join(self.path, "db", "hostdb.sqlite")
229 umask = os.umask(0664)
231 db = create_database("sqlite:%s?timeout=%f" % (db_path, self.timeout))
232 self.store = Store(db)
233 setup_schema(self.store)
238 def _open_hostdb(self):
239 return StormHostDatabase(self._get_store())
241 def _open_build_results(self):
242 return StormCachingBuildResultStore(os.path.join(self.path, "data", "oldrevs"),
245 def get_host_builds(self, host):
246 return self._get_store().find(StormBuild,
247 StormBuild.host == host).group_by(StormBuild.compiler, StormBuild.tree)
249 def get_last_builds(self, tree):
250 extra_expr = [StormBuild.tree == tree]
251 return self._get_store().find(StormBuild, *extra_expr)
257 def setup_schema(db):
258 db.execute("CREATE TABLE IF NOT EXISTS host (name text, owner text, owner_email text, password text, ssh_access int, fqdn text, platform text, permission text, last_dead_mail int, join_time int);", noresult=True)
259 db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_hostname ON host (name);", noresult=True)
260 db.execute("CREATE TABLE IF NOT EXISTS build (id integer primary key autoincrement, tree text, revision text, host text, compiler text, checksum text, age int, status text, commit_revision text);", noresult=True)
261 db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_checksum ON build (checksum);", noresult=True)
265 db = create_database("sqlite:")