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