Cope with builds already existing.
[amitay/build-farm.git] / buildfarm / sqldb.py
1 #!/usr/bin/python
2
3 # Samba.org buildfarm
4 # Copyright (C) 2010 Jelmer Vernooij <jelmer@samba.org>
5 #
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.
10 #
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.
15 #
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/>.
18 #
19
20 from buildfarm import (
21     BuildFarm,
22     )
23 from buildfarm.data import (
24     Build,
25     BuildResultStore,
26     NoSuchBuildError,
27     )
28 from buildfarm.hostdb import (
29     Host,
30     HostDatabase,
31     HostAlreadyExists,
32     NoSuchHost,
33     )
34
35 import os
36 try:
37     import sqlite3
38 except ImportError:
39     from pysqlite2 import dbapi2 as sqlite3
40 from storm.database import create_database
41 from storm.locals import Bool, Desc, Int, Unicode, RawStr
42 from storm.store import Store
43
44
45 class StormBuild(Build):
46     __storm_table__ = "build"
47
48     id = Int(primary=True)
49     tree = Unicode()
50     revision = RawStr()
51     host = Unicode()
52     compiler = Unicode()
53     checksum = RawStr()
54     age = Int()
55     status = Unicode()
56     commit_revision = RawStr()
57
58     def remove(self):
59         super(StormBuild, self).remove()
60         Store.of(self).remove(self)
61
62
63 class StormHost(Host):
64     __storm_table__ = "host"
65
66     name = Unicode(primary=True)
67     owner_name = Unicode(name="owner")
68     owner_email = Unicode()
69     password = Unicode()
70     ssh_access = Bool()
71     fqdn = RawStr()
72     platform = Unicode()
73     permission = Unicode()
74     last_dead_mail = Int()
75     join_time = Int()
76
77     def _set_owner(self, value):
78         if value is None:
79             self.owner_name = None
80             self.owner_email = None
81         else:
82             (self.owner_name, self.owner_email) = value
83
84     def _get_owner(self):
85         if self.owner_name is None:
86             return None
87         else:
88             return (self.owner_name, self.owner_email)
89
90     owner = property(_get_owner, _set_owner)
91
92
93 class StormHostDatabase(HostDatabase):
94
95     def __init__(self, store=None):
96         if store is None:
97             self.store = memory_store()
98         else:
99             self.store = store
100
101     def createhost(self, name, platform=None, owner=None, owner_email=None, password=None, permission=None):
102         newhost = StormHost(unicode(name), owner=owner, owner_email=owner_email, password=password, permission=permission, platform=platform)
103         try:
104             self.store.add(newhost)
105             self.store.flush()
106         except sqlite3.IntegrityError:
107             raise HostAlreadyExists(name)
108         return newhost
109
110     def deletehost(self, name):
111         """Remove a host."""
112         host = self.host(name)
113         self.store.remove(host)
114
115     def hosts(self):
116         """Retrieve an iterable over all hosts."""
117         return self.store.find(StormHost).order_by(StormHost.name)
118
119     def host(self, name):
120         ret = self.hosts().find(StormHost.name==name).one()
121         if ret is None:
122             raise NoSuchHost(name)
123         return ret
124
125     def commit(self):
126         self.store.commit()
127
128
129 class StormCachingBuildResultStore(BuildResultStore):
130
131     def __init__(self, basedir, store=None):
132         super(StormCachingBuildResultStore, self).__init__(basedir)
133
134         if store is None:
135             store = memory_store()
136
137         self.store = store
138
139     def get_previous_revision(self, tree, host, compiler, revision):
140         result = self.store.find(StormBuild,
141             StormBuild.tree == unicode(tree),
142             StormBuild.host == unicode(host),
143             StormBuild.compiler == unicode(compiler),
144             StormBuild.commit_revision == revision)
145         cur_build = result.any()
146         if cur_build is None:
147             raise NoSuchBuildError(tree, host, compiler, revision)
148
149         result = self.store.find(StormBuild,
150             StormBuild.tree == unicode(tree),
151             StormBuild.host == unicode(host),
152             StormBuild.compiler == unicode(compiler),
153             StormBuild.commit_revision != revision,
154             StormBuild.id < cur_build.id)
155         result = result.order_by(Desc(StormBuild.id))
156         prev_build = result.first()
157         if prev_build is None:
158             raise NoSuchBuildError(tree, host, compiler, revision)
159         return prev_build.commit_revision
160
161     def get_latest_revision(self, tree, host, compiler):
162         result = self.store.find(StormBuild,
163             StormBuild.tree == unicode(tree),
164             StormBuild.host == unicode(host),
165             StormBuild.compiler == unicode(compiler))
166         result = result.order_by(Desc(StormBuild.id))
167         build = result.first()
168         if build is None:
169             raise NoSuchBuildError(tree, host, compiler)
170         return build.revision
171
172     def upload_build(self, build):
173         super(StormCachingBuildResultStore, self).upload_build(build)
174         rev, timestamp = build.revision_details()
175         result = self.store.find(StormBuild, StormBuild.checksum == build.log_checksum())
176         existing_build = result.one()
177         if existing_build is not None:
178             # Already present
179             assert build.tree == existing_build.tree
180             assert build.host == existing_build.host
181             assert build.compiler == existing_build.compiler
182             assert rev == existing_build.revision
183             return
184         new_basename = self.build_fname(build.tree, build.host, build.compiler, rev)
185         new_build = StormBuild(new_basename, unicode(build.tree), unicode(build.host), unicode(build.compiler), rev)
186         new_build.checksum = build.log_checksum()
187         new_build.age = build.age_mtime()
188         new_build.status = unicode(str(build.status()))
189         self.store.add(new_build)
190
191
192 class StormCachingBuildFarm(BuildFarm):
193
194     def __init__(self, path=None, store=None):
195         self.store = store
196         super(StormCachingBuildFarm, self).__init__(path)
197
198     def _get_store(self):
199         if self.store is not None:
200             return self.store
201         else:
202             db = create_database("sqlite:" + os.path.join(self.path, "hostdb.sqlite"))
203             self.store = Store(db)
204             setup_schema(self.store)
205             return self.store
206
207     def _open_hostdb(self):
208         return StormHostDatabase(self._get_store())
209
210     def _open_build_results(self):
211         return StormCachingBuildResultStore(os.path.join(self.path, "data", "oldrevs"),
212             self._get_store())
213
214     def commit(self):
215         self.store.commit()
216
217
218 def setup_schema(db):
219     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)
220     db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_hostname ON host (name);", noresult=True)
221     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)
222     db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_checksum ON build (checksum);", noresult=True)
223
224
225 def memory_store():
226     db = create_database("sqlite:")
227     store = Store(db)
228     setup_schema(store)
229     return store