1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """
17 Support for buildsets in the database
18 """
19
20 import sqlalchemy as sa
21 from twisted.internet import reactor
22 from buildbot.util import json
23 from buildbot.db import base
24 from buildbot.util import epoch2datetime
25
28
30 """
31 A DBConnectorComponent to handle getting buildsets into and out of the
32 database. An instance is available at C{master.db.buildsets}.
33 """
34
35 - def addBuildset(self, ssid, reason, properties, builderNames,
36 external_idstring=None, _reactor=reactor):
37 """
38 Add a new Buildset to the database, along with the buildrequests for
39 each named builder, returning the resulting bsid via a Deferred.
40 Arguments should be specified by keyword.
41
42 The return value is a tuple C{(bsid, brids)} where C{bsid} is the
43 inserted buildset ID and C{brids} is a dictionary mapping buildernames
44 to build request IDs.
45
46 @param ssid: id of the SourceStamp for this buildset
47 @type ssid: integer
48
49 @param reason: reason for this buildset
50 @type reason: short unicode string
51
52 @param properties: properties for this buildset
53 @type properties: dictionary, where values are tuples of (value,
54 source)
55
56 @param builderNames: builders specified by this buildset
57 @type builderNames: list of strings
58
59 @param external_idstring: external key to identify this buildset;
60 defaults to None
61 @type external_idstring: unicode string
62
63 @param _reactor: for testing
64
65 @returns: buildset ID and buildrequest IDs, via a Deferred
66 """
67 def thd(conn):
68 submitted_at = _reactor.seconds()
69
70 transaction = conn.begin()
71
72
73 r = conn.execute(self.db.model.buildsets.insert(), dict(
74 sourcestampid=ssid, submitted_at=submitted_at,
75 reason=reason, complete=0, complete_at=None, results=-1,
76 external_idstring=external_idstring))
77 bsid = r.inserted_primary_key[0]
78
79
80 if properties:
81 conn.execute(self.db.model.buildset_properties.insert(), [
82 dict(buildsetid=bsid, property_name=k,
83 property_value=json.dumps([v,s]))
84 for k,(v,s) in properties.iteritems() ])
85
86
87
88
89
90 brids = {}
91 ins = self.db.model.buildrequests.insert()
92 for buildername in builderNames:
93 r = conn.execute(ins,
94 dict(buildsetid=bsid, buildername=buildername, priority=0,
95 claimed_at=0, claimed_by_name=None,
96 claimed_by_incarnation=None, complete=0, results=-1,
97 submitted_at=submitted_at, complete_at=None))
98
99 brids[buildername] = r.inserted_primary_key[0]
100
101 transaction.commit()
102
103 return (bsid, brids)
104 return self.db.pool.do(thd)
105
107 """
108 Complete a buildset, marking it with the given C{results} and setting
109 its C{completed_at} to the current time.
110
111 @param bsid: buildset ID to complete
112 @type bsid: integer
113
114 @param results: integer result code
115 @type results: integer
116
117 @param _reactor: reactor to use (for testing)
118
119 @returns: Deferred
120 @raises KeyError: if the row does not exist or is already complete
121 """
122 def thd(conn):
123 tbl = self.db.model.buildsets
124
125 q = tbl.update(whereclause=(
126 (tbl.c.id == bsid) &
127 ((tbl.c.complete == None) | (tbl.c.complete != 1))))
128 res = conn.execute(q,
129 complete=1,
130 results=results,
131 complete_at=_reactor.seconds())
132
133 if res.rowcount != 1:
134 raise KeyError
135 return self.db.pool.do(thd)
136
138 """
139 Get a dictionary representing the given buildset, or None
140 if no such buildset exists.
141
142 The dictionary has keys C{bsid}, C{external_idstring}, C{reason},
143 C{sourcestampid}, C{submitted_at}, C{complete}, C{complete_at}, and
144 C{results}. The C{*_at} keys point to datetime objects. Use
145 L{getBuildsetProperties} to fetch the properties for a buildset.
146
147 Note that buildsets are not cached, as the values in the database are
148 not fixed.
149
150 @param bsid: buildset ID
151
152 @returns: dictionary as above, or None, via Deferred
153 """
154 def thd(conn):
155 bs_tbl = self.db.model.buildsets
156 q = bs_tbl.select(whereclause=(bs_tbl.c.id == bsid))
157 res = conn.execute(q)
158 row = res.fetchone()
159 if not row:
160 return None
161 return self._row2dict(row)
162 return self.db.pool.do(thd)
163
165 """
166 Get a list of buildset dictionaries (see L{getBuildset}) matching
167 the given criteria.
168
169 Since this method is often used to detect changed build requests, it
170 always bypasses the cache.
171
172 @param complete: if True, return only complete buildsets; if False,
173 return only incomplete buildsets; if None or omitted, return all
174 buildsets
175
176 @returns: list of dictionaries, via Deferred
177 """
178 def thd(conn):
179 bs_tbl = self.db.model.buildsets
180 q = bs_tbl.select()
181 if complete is not None:
182 if complete:
183 q = q.where(bs_tbl.c.complete != 0)
184 else:
185 q = q.where((bs_tbl.c.complete == 0) |
186 (bs_tbl.c.complete == None))
187 res = conn.execute(q)
188 return [ self._row2dict(row) for row in res.fetchall() ]
189 return self.db.pool.do(thd)
190
192 """
193 Return the properties for a buildset, in the same format they were
194 given to L{addBuildset}.
195
196 Note that this method does not distinguish a nonexistent buildset from
197 a buildset with no properties, and returns C{{}} in either case.
198
199 @param buildsetid: buildset ID
200
201 @returns: dictionary mapping property name to (value, source), via
202 Deferred
203 """
204 def thd(conn):
205 bsp_tbl = self.db.model.buildset_properties
206 q = sa.select(
207 [ bsp_tbl.c.property_name, bsp_tbl.c.property_value ],
208 whereclause=(bsp_tbl.c.buildsetid == buildsetid))
209 return dict([ (row.property_name,
210 tuple(json.loads(row.property_value)))
211 for row in conn.execute(q) ])
212 return self.db.pool.do(thd)
213
215 """
216 Add a row to C{scheduler_upstream_buildsets} indicating that
217 C{schedulerid} is interested in buildset C{bsid}.
218
219 @param schedulerid: downstream scheduler
220 @type schedulerid: integer
221
222 @param buildsetid: buildset id the scheduler is subscribing to
223 @type buildsetid: integer
224
225 @returns: Deferred
226 """
227 def thd(conn):
228 conn.execute(self.db.model.scheduler_upstream_buildsets.insert(),
229 schedulerid=schedulerid,
230 buildsetid=buildsetid,
231 active=1)
232 return self.db.pool.do(thd)
233
235 """
236 The opposite of L{subscribeToBuildset}, this removes the subcription
237 row from the database, rather than simply marking it as inactive.
238
239 @param schedulerid: downstream scheduler
240 @type schedulerid: integer
241
242 @param buildsetid: buildset id the scheduler is subscribing to
243 @type buildsetid: integer
244
245 @returns: Deferred
246 """
247 def thd(conn):
248 tbl = self.db.model.scheduler_upstream_buildsets
249 conn.execute(tbl.delete(
250 (tbl.c.schedulerid == schedulerid) &
251 (tbl.c.buildsetid == buildsetid)))
252 return self.db.pool.do(thd)
253
255 """
256 Get the set of buildsets to which this scheduler is subscribed, along
257 with the buildsets' current results. This will exclude any rows marked
258 as not active.
259
260 The return value is a list of tuples, each containing a buildset ID, a
261 sourcestamp ID, a boolean indicating that the buildset is complete, and
262 the buildset's result.
263
264 @param schedulerid: downstream scheduler
265 @type schedulerid: integer
266
267 @returns: list as described, via Deferred
268 """
269 def thd(conn):
270 bs_tbl = self.db.model.buildsets
271 upstreams_tbl = self.db.model.scheduler_upstream_buildsets
272 q = sa.select(
273 [bs_tbl.c.id, bs_tbl.c.sourcestampid,
274 bs_tbl.c.results, bs_tbl.c.complete],
275 whereclause=(
276 (upstreams_tbl.c.schedulerid == schedulerid) &
277 (upstreams_tbl.c.buildsetid == bs_tbl.c.id) &
278 (upstreams_tbl.c.active != 0)),
279 distinct=True)
280 return [ (row.id, row.sourcestampid, row.complete, row.results)
281 for row in conn.execute(q).fetchall() ]
282 return self.db.pool.do(thd)
283
285 def mkdt(epoch):
286 if epoch:
287 return epoch2datetime(epoch)
288 return BsDict(external_idstring=row.external_idstring,
289 reason=row.reason, sourcestampid=row.sourcestampid,
290 submitted_at=mkdt(row.submitted_at),
291 complete=bool(row.complete),
292 complete_at=mkdt(row.complete_at), results=row.results,
293 bsid=row.id)
294