Package buildbot :: Package db :: Module buildsets
[frames] | no frames]

Source Code for Module buildbot.db.buildsets

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 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   
26 -class BsDict(dict):
27 pass
28
29 -class BuildsetsConnectorComponent(base.DBConnectorComponent):
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 # insert the buildset itself 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 # add any properties 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 # and finish with a build request for each builder. Note that 87 # sqlalchemy and the Python DBAPI do not provide a way to recover 88 # inserted IDs from a multi-row insert, so this is done one row at 89 # a time. 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
106 - def completeBuildset(self, bsid, results, _reactor=reactor):
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
137 - def getBuildset(self, bsid):
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
164 - def getBuildsets(self, complete=None):
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
191 - def getBuildsetProperties(self, buildsetid):
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
214 - def subscribeToBuildset(self, schedulerid, buildsetid):
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
234 - def unsubscribeFromBuildset(self, schedulerid, buildsetid):
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
254 - def getSubscribedBuildsets(self, schedulerid):
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
284 - def _row2dict(self, row):
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