Package buildbot :: Package process :: Module builder
[frames] | no frames]

Source Code for Module buildbot.process.builder

  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  import random, weakref 
 18  from zope.interface import implements 
 19  from twisted.python import log, failure 
 20  from twisted.spread import pb 
 21  from twisted.application import service, internet 
 22  from twisted.internet import defer 
 23   
 24  from buildbot import interfaces, config 
 25  from buildbot.status.progress import Expectations 
 26  from buildbot.status.builder import RETRY 
 27  from buildbot.status.buildrequest import BuildRequestStatus 
 28  from buildbot.process.properties import Properties 
 29  from buildbot.process import buildrequest, slavebuilder 
 30  from buildbot.process.slavebuilder import BUILDING 
 31  from buildbot.db import buildrequests 
32 33 -class Builder(config.ReconfigurableServiceMixin, 34 pb.Referenceable, 35 service.MultiService):
36 37 # reconfigure builders before slaves 38 reconfig_priority = 196 39
40 - def __init__(self, name):
41 service.MultiService.__init__(self) 42 self.name = name 43 44 # this is created the first time we get a good build 45 self.expectations = None 46 47 # build/wannabuild slots: Build objects move along this sequence 48 self.building = [] 49 # old_building holds active builds that were stolen from a predecessor 50 self.old_building = weakref.WeakKeyDictionary() 51 52 # buildslaves which have connected but which are not yet available. 53 # These are always in the ATTACHING state. 54 self.attaching_slaves = [] 55 56 # buildslaves at our disposal. Each SlaveBuilder instance has a 57 # .state that is IDLE, PINGING, or BUILDING. "PINGING" is used when a 58 # Build is about to start, to make sure that they're still alive. 59 self.slaves = [] 60 61 self.config = None 62 self.builder_status = None 63 64 self.reclaim_svc = internet.TimerService(10*60, self.reclaimAllBuilds) 65 self.reclaim_svc.setServiceParent(self)
66
67 - def reconfigService(self, new_config):
68 # find this builder in the config 69 for builder_config in new_config.builders: 70 if builder_config.name == self.name: 71 break 72 else: 73 assert 0, "no config found for builder '%s'" % self.name 74 75 # set up a builder status object on the first reconfig 76 if not self.builder_status: 77 self.builder_status = self.master.status.builderAdded( 78 builder_config.name, 79 builder_config.builddir, 80 builder_config.category) 81 82 self.config = builder_config 83 84 self.builder_status.setSlavenames(self.config.slavenames) 85 86 return defer.succeed(None)
87
88 - def stopService(self):
89 d = defer.maybeDeferred(lambda : 90 service.MultiService.stopService(self)) 91 def flushMaybeStartBuilds(_): 92 # at this point, self.running = False, so another maybeStartBuild 93 # invocation won't hurt anything, but it also will not complete 94 # until any currently-running invocations are done, so we know that 95 # the builder is quiescent at that time. 96 return self.maybeStartBuild()
97 d.addCallback(flushMaybeStartBuilds) 98 return d
99
100 - def __repr__(self):
101 return "<Builder '%r' at %d>" % (self.name, id(self))
102 103 @defer.deferredGenerator
104 - def getOldestRequestTime(self):
105 106 """Returns the submitted_at of the oldest unclaimed build request for 107 this builder, or None if there are no build requests. 108 109 @returns: datetime instance or None, via Deferred 110 """ 111 wfd = defer.waitForDeferred( 112 self.master.db.buildrequests.getBuildRequests( 113 buildername=self.name, claimed=False)) 114 yield wfd 115 unclaimed = wfd.getResult() 116 117 if unclaimed: 118 unclaimed = [ brd['submitted_at'] for brd in unclaimed ] 119 unclaimed.sort() 120 yield unclaimed[0] 121 else: 122 yield None
123
124 - def reclaimAllBuilds(self):
125 brids = set() 126 for b in self.building: 127 brids.update([br.id for br in b.requests]) 128 for b in self.old_building: 129 brids.update([br.id for br in b.requests]) 130 131 if not brids: 132 return defer.succeed(None) 133 134 d = self.master.db.buildrequests.reclaimBuildRequests(brids) 135 d.addErrback(log.err, 'while re-claiming running BuildRequests') 136 return d
137
138 - def getBuild(self, number):
139 for b in self.building: 140 if b.build_status and b.build_status.number == number: 141 return b 142 for b in self.old_building.keys(): 143 if b.build_status and b.build_status.number == number: 144 return b 145 return None
146
147 - def addLatentSlave(self, slave):
148 assert interfaces.ILatentBuildSlave.providedBy(slave) 149 for s in self.slaves: 150 if s == slave: 151 break 152 else: 153 sb = slavebuilder.LatentSlaveBuilder(slave, self) 154 self.builder_status.addPointEvent( 155 ['added', 'latent', slave.slavename]) 156 self.slaves.append(sb) 157 self.botmaster.maybeStartBuildsForBuilder(self.name)
158
159 - def attached(self, slave, remote, commands):
160 """This is invoked by the BuildSlave when the self.slavename bot 161 registers their builder. 162 163 @type slave: L{buildbot.buildslave.BuildSlave} 164 @param slave: the BuildSlave that represents the buildslave as a whole 165 @type remote: L{twisted.spread.pb.RemoteReference} 166 @param remote: a reference to the L{buildbot.slave.bot.SlaveBuilder} 167 @type commands: dict: string -> string, or None 168 @param commands: provides the slave's version of each RemoteCommand 169 170 @rtype: L{twisted.internet.defer.Deferred} 171 @return: a Deferred that fires (with 'self') when the slave-side 172 builder is fully attached and ready to accept commands. 173 """ 174 for s in self.attaching_slaves + self.slaves: 175 if s.slave == slave: 176 # already attached to them. This is fairly common, since 177 # attached() gets called each time we receive the builder 178 # list from the slave, and we ask for it each time we add or 179 # remove a builder. So if the slave is hosting builders 180 # A,B,C, and the config file changes A, we'll remove A and 181 # re-add it, triggering two builder-list requests, getting 182 # two redundant calls to attached() for B, and another two 183 # for C. 184 # 185 # Therefore, when we see that we're already attached, we can 186 # just ignore it. 187 return defer.succeed(self) 188 189 sb = slavebuilder.SlaveBuilder() 190 sb.setBuilder(self) 191 self.attaching_slaves.append(sb) 192 d = sb.attached(slave, remote, commands) 193 d.addCallback(self._attached) 194 d.addErrback(self._not_attached, slave) 195 return d
196
197 - def _attached(self, sb):
198 self.builder_status.addPointEvent(['connect', sb.slave.slavename]) 199 self.attaching_slaves.remove(sb) 200 self.slaves.append(sb) 201 202 self.updateBigStatus() 203 204 return self
205
206 - def _not_attached(self, why, slave):
207 # already log.err'ed by SlaveBuilder._attachFailure 208 # TODO: remove from self.slaves (except that detached() should get 209 # run first, right?) 210 log.err(why, 'slave failed to attach') 211 self.builder_status.addPointEvent(['failed', 'connect', 212 slave.slavename])
213 # TODO: add an HTMLLogFile of the exception 214
215 - def detached(self, slave):
216 """This is called when the connection to the bot is lost.""" 217 for sb in self.attaching_slaves + self.slaves: 218 if sb.slave == slave: 219 break 220 else: 221 log.msg("WEIRD: Builder.detached(%s) (%s)" 222 " not in attaching_slaves(%s)" 223 " or slaves(%s)" % (slave, slave.slavename, 224 self.attaching_slaves, 225 self.slaves)) 226 return 227 if sb.state == BUILDING: 228 # the Build's .lostRemote method (invoked by a notifyOnDisconnect 229 # handler) will cause the Build to be stopped, probably right 230 # after the notifyOnDisconnect that invoked us finishes running. 231 pass 232 233 if sb in self.attaching_slaves: 234 self.attaching_slaves.remove(sb) 235 if sb in self.slaves: 236 self.slaves.remove(sb) 237 238 self.builder_status.addPointEvent(['disconnect', slave.slavename]) 239 sb.detached() # inform the SlaveBuilder that their slave went away 240 self.updateBigStatus()
241
242 - def updateBigStatus(self):
243 if not self.slaves: 244 self.builder_status.setBigState("offline") 245 elif self.building or self.old_building: 246 self.builder_status.setBigState("building") 247 else: 248 self.builder_status.setBigState("idle")
249 250 @defer.deferredGenerator
251 - def _startBuildFor(self, slavebuilder, buildrequests):
252 """Start a build on the given slave. 253 @param build: the L{base.Build} to start 254 @param sb: the L{SlaveBuilder} which will host this build 255 256 @return: (via Deferred) boolean indicating that the build was 257 succesfully started. 258 """ 259 260 # as of the Python versions supported now, try/finally can't be used 261 # with a generator expression. So instead, we push cleanup functions 262 # into a list so that, at any point, we can abort this operation. 263 cleanups = [] 264 def run_cleanups(): 265 while cleanups: 266 fn = cleanups.pop() 267 fn()
268 269 # the last cleanup we want to perform is to update the big 270 # status based on any other cleanup 271 cleanups.append(lambda : self.updateBigStatus()) 272 273 build = self.config.factory.newBuild(buildrequests) 274 build.setBuilder(self) 275 log.msg("starting build %s using slave %s" % (build, slavebuilder)) 276 277 # set up locks 278 build.setLocks(self.config.locks) 279 cleanups.append(lambda : slavebuilder.slave.releaseLocks()) 280 281 if len(self.config.env) > 0: 282 build.setSlaveEnvironment(self.config.env) 283 284 # append the build to self.building 285 self.building.append(build) 286 cleanups.append(lambda : self.building.remove(build)) 287 288 # update the big status accordingly 289 self.updateBigStatus() 290 291 try: 292 wfd = defer.waitForDeferred( 293 slavebuilder.prepare(self.builder_status, build)) 294 yield wfd 295 ready = wfd.getResult() 296 except: 297 log.err(failure.Failure(), 'while preparing slavebuilder:') 298 ready = False 299 300 # If prepare returns True then it is ready and we start a build 301 # If it returns false then we don't start a new build. 302 if not ready: 303 log.msg("slave %s can't build %s after all; re-queueing the " 304 "request" % (build, slavebuilder)) 305 run_cleanups() 306 yield False 307 return 308 309 # ping the slave to make sure they're still there. If they've 310 # fallen off the map (due to a NAT timeout or something), this 311 # will fail in a couple of minutes, depending upon the TCP 312 # timeout. 313 # 314 # TODO: This can unnecessarily suspend the starting of a build, in 315 # situations where the slave is live but is pushing lots of data to 316 # us in a build. 317 log.msg("starting build %s.. pinging the slave %s" 318 % (build, slavebuilder)) 319 try: 320 wfd = defer.waitForDeferred( 321 slavebuilder.ping()) 322 yield wfd 323 ping_success = wfd.getResult() 324 except: 325 log.err(failure.Failure(), 'while pinging slave before build:') 326 ping_success = False 327 328 if not ping_success: 329 log.msg("slave ping failed; re-queueing the request") 330 run_cleanups() 331 yield False 332 return 333 334 # The buildslave is ready to go. slavebuilder.buildStarted() sets its 335 # state to BUILDING (so we won't try to use it for any other builds). 336 # This gets set back to IDLE by the Build itself when it finishes. 337 slavebuilder.buildStarted() 338 cleanups.append(lambda : slavebuilder.buildFinished()) 339 340 # tell the remote that it's starting a build, too 341 try: 342 wfd = defer.waitForDeferred( 343 slavebuilder.remote.callRemote("startBuild")) 344 yield wfd 345 wfd.getResult() 346 except: 347 log.err(failure.Failure(), 'while calling remote startBuild:') 348 run_cleanups() 349 yield False 350 return 351 352 # create the BuildStatus object that goes with the Build 353 bs = self.builder_status.newBuild() 354 355 # record the build in the db - one row per buildrequest 356 try: 357 bids = [] 358 for req in build.requests: 359 wfd = defer.waitForDeferred( 360 self.master.db.builds.addBuild(req.id, bs.number)) 361 yield wfd 362 bids.append(wfd.getResult()) 363 except: 364 log.err(failure.Failure(), 'while adding rows to build table:') 365 run_cleanups() 366 yield False 367 return 368 369 # let status know 370 self.master.status.build_started(req.id, self.name, bs) 371 372 # start the build. This will first set up the steps, then tell the 373 # BuildStatus that it has started, which will announce it to the world 374 # (through our BuilderStatus object, which is its parent). Finally it 375 # will start the actual build process. This is done with a fresh 376 # Deferred since _startBuildFor should not wait until the build is 377 # finished. 378 d = build.startBuild(bs, self.expectations, slavebuilder) 379 d.addCallback(self.buildFinished, slavebuilder, bids) 380 # this shouldn't happen. if it does, the slave will be wedged 381 d.addErrback(log.err) 382 383 # make sure the builder's status is represented correctly 384 self.updateBigStatus() 385 386 yield True 387
388 - def setupProperties(self, props):
389 props.setProperty("buildername", self.name, "Builder") 390 if len(self.config.properties) > 0: 391 for propertyname in self.config.properties: 392 props.setProperty(propertyname, 393 self.config.properties[propertyname], 394 "Builder")
395
396 - def buildFinished(self, build, sb, bids):
397 """This is called when the Build has finished (either success or 398 failure). Any exceptions during the build are reported with 399 results=FAILURE, not with an errback.""" 400 401 # by the time we get here, the Build has already released the slave, 402 # which will trigger a check for any now-possible build requests 403 # (maybeStartBuilds) 404 405 # mark the builds as finished, although since nothing ever reads this 406 # table, it's not too important that it complete successfully 407 d = self.master.db.builds.finishBuilds(bids) 408 d.addErrback(log.err, 'while marking builds as finished (ignored)') 409 410 results = build.build_status.getResults() 411 self.building.remove(build) 412 if results == RETRY: 413 self._resubmit_buildreqs(build).addErrback(log.err) 414 else: 415 brids = [br.id for br in build.requests] 416 db = self.master.db 417 d = db.buildrequests.completeBuildRequests(brids, results) 418 d.addCallback( 419 lambda _ : self._maybeBuildsetsComplete(build.requests)) 420 # nothing in particular to do with this deferred, so just log it if 421 # it fails.. 422 d.addErrback(log.err, 'while marking build requests as completed') 423 424 if sb.slave: 425 sb.slave.releaseLocks() 426 427 self.updateBigStatus()
428 429 @defer.deferredGenerator
430 - def _maybeBuildsetsComplete(self, requests):
431 # inform the master that we may have completed a number of buildsets 432 for br in requests: 433 wfd = defer.waitForDeferred( 434 self.master.maybeBuildsetComplete(br.bsid)) 435 yield wfd 436 wfd.getResult()
437
438 - def _resubmit_buildreqs(self, build):
439 brids = [br.id for br in build.requests] 440 return self.master.db.buildrequests.unclaimBuildRequests(brids)
441
442 - def setExpectations(self, progress):
443 """Mark the build as successful and update expectations for the next 444 build. Only call this when the build did not fail in any way that 445 would invalidate the time expectations generated by it. (if the 446 compile failed and thus terminated early, we can't use the last 447 build to predict how long the next one will take). 448 """ 449 if self.expectations: 450 self.expectations.update(progress) 451 else: 452 # the first time we get a good build, create our Expectations 453 # based upon its results 454 self.expectations = Expectations(progress) 455 log.msg("new expectations: %s seconds" % \ 456 self.expectations.expectedBuildTime())
457 458 # Build Creation 459 460 @defer.deferredGenerator
461 - def maybeStartBuild(self):
462 # This method is called by the botmaster whenever this builder should 463 # check for and potentially start new builds. Do not call this method 464 # directly - use master.botmaster.maybeStartBuildsForBuilder, or one 465 # of the other similar methods if more appropriate 466 467 # first, if we're not running, then don't start builds; stopService 468 # uses this to ensure that any ongoing maybeStartBuild invocations 469 # are complete before it stops. 470 if not self.running: 471 return 472 473 # Check for available slaves. If there are no available slaves, then 474 # there is no sense continuing 475 available_slavebuilders = [ sb for sb in self.slaves 476 if sb.isAvailable() ] 477 if not available_slavebuilders: 478 self.updateBigStatus() 479 return 480 481 # now, get the available build requests 482 wfd = defer.waitForDeferred( 483 self.master.db.buildrequests.getBuildRequests( 484 buildername=self.name, claimed=False)) 485 yield wfd 486 unclaimed_requests = wfd.getResult() 487 488 if not unclaimed_requests: 489 self.updateBigStatus() 490 return 491 492 # sort by submitted_at, so the first is the oldest 493 unclaimed_requests.sort(key=lambda brd : brd['submitted_at']) 494 495 # get the mergeRequests function for later 496 mergeRequests_fn = self._getMergeRequestsFn() 497 498 # match them up until we're out of options 499 while available_slavebuilders and unclaimed_requests: 500 # first, choose a slave (using nextSlave) 501 wfd = defer.waitForDeferred( 502 self._chooseSlave(available_slavebuilders)) 503 yield wfd 504 slavebuilder = wfd.getResult() 505 506 if not slavebuilder: 507 break 508 509 if slavebuilder not in available_slavebuilders: 510 log.msg(("nextSlave chose a nonexistent slave for builder " 511 "'%s'; cannot start build") % self.name) 512 break 513 514 # then choose a request (using nextBuild) 515 wfd = defer.waitForDeferred( 516 self._chooseBuild(unclaimed_requests)) 517 yield wfd 518 brdict = wfd.getResult() 519 520 if not brdict: 521 break 522 523 if brdict not in unclaimed_requests: 524 log.msg(("nextBuild chose a nonexistent request for builder " 525 "'%s'; cannot start build") % self.name) 526 break 527 528 # merge the chosen request with any compatible requests in the 529 # queue 530 wfd = defer.waitForDeferred( 531 self._mergeRequests(brdict, unclaimed_requests, 532 mergeRequests_fn)) 533 yield wfd 534 brdicts = wfd.getResult() 535 536 # try to claim the build requests 537 brids = [ brdict['brid'] for brdict in brdicts ] 538 try: 539 wfd = defer.waitForDeferred( 540 self.master.db.buildrequests.claimBuildRequests(brids)) 541 yield wfd 542 wfd.getResult() 543 except buildrequests.AlreadyClaimedError: 544 # one or more of the build requests was already claimed; 545 # re-fetch the now-partially-claimed build requests and keep 546 # trying to match them 547 self._breakBrdictRefloops(unclaimed_requests) 548 wfd = defer.waitForDeferred( 549 self.master.db.buildrequests.getBuildRequests( 550 buildername=self.name, claimed=False)) 551 yield wfd 552 unclaimed_requests = wfd.getResult() 553 554 # go around the loop again 555 continue 556 557 # claim was successful, so initiate a build for this set of 558 # requests. Note that if the build fails from here on out (e.g., 559 # because a slave has failed), it will be handled outside of this 560 # loop. TODO: test that! 561 562 # _startBuildFor expects BuildRequest objects, so cook some up 563 wfd = defer.waitForDeferred( 564 defer.gatherResults([ self._brdictToBuildRequest(brdict) 565 for brdict in brdicts ])) 566 yield wfd 567 breqs = wfd.getResult() 568 569 wfd = defer.waitForDeferred( 570 self._startBuildFor(slavebuilder, breqs)) 571 yield wfd 572 build_started = wfd.getResult() 573 574 if not build_started: 575 # build was not started, so unclaim the build requests 576 wfd = defer.waitForDeferred( 577 self.master.db.buildrequests.unclaimBuildRequests(brids)) 578 yield wfd 579 wfd.getResult() 580 581 # and try starting builds again. If we still have a working slave, 582 # then this may re-claim the same buildrequests 583 self.botmaster.maybeStartBuildsForBuilder(self.name) 584 585 # finally, remove the buildrequests and slavebuilder from the 586 # respective queues 587 self._breakBrdictRefloops(brdicts) 588 for brdict in brdicts: 589 unclaimed_requests.remove(brdict) 590 available_slavebuilders.remove(slavebuilder) 591 592 self._breakBrdictRefloops(unclaimed_requests) 593 self.updateBigStatus() 594 return
595 596 # a few utility functions to make the maybeStartBuild a bit shorter and 597 # easier to read 598
599 - def _chooseSlave(self, available_slavebuilders):
600 """ 601 Choose the next slave, using the C{nextSlave} configuration if 602 available, and falling back to C{random.choice} otherwise. 603 604 @param available_slavebuilders: list of slavebuilders to choose from 605 @returns: SlaveBuilder or None via Deferred 606 """ 607 if self.config.nextSlave: 608 return defer.maybeDeferred(lambda : 609 self.config.nextSlave(self, available_slavebuilders)) 610 else: 611 return defer.succeed(random.choice(available_slavebuilders))
612
613 - def _chooseBuild(self, buildrequests):
614 """ 615 Choose the next build from the given set of build requests (represented 616 as dictionaries). Defaults to returning the first request (earliest 617 submitted). 618 619 @param buildrequests: sorted list of build request dictionaries 620 @returns: a build request dictionary or None via Deferred 621 """ 622 if self.config.nextBuild: 623 # nextBuild expects BuildRequest objects, so instantiate them here 624 # and cache them in the dictionaries 625 d = defer.gatherResults([ self._brdictToBuildRequest(brdict) 626 for brdict in buildrequests ]) 627 d.addCallback(lambda requestobjects : 628 self.config.nextBuild(self, requestobjects)) 629 def to_brdict(brobj): 630 # get the brdict for this object back 631 return brobj.brdict
632 d.addCallback(to_brdict) 633 return d 634 else: 635 return defer.succeed(buildrequests[0]) 636
637 - def _getMergeRequestsFn(self):
638 """Helper function to determine which mergeRequests function to use 639 from L{_mergeRequests}, or None for no merging""" 640 # first, seek through builder, global, and the default 641 mergeRequests_fn = self.config.mergeRequests 642 if mergeRequests_fn is None: 643 mergeRequests_fn = self.master.config.mergeRequests 644 if mergeRequests_fn is None: 645 mergeRequests_fn = True 646 647 # then translate False and True properly 648 if mergeRequests_fn is False: 649 mergeRequests_fn = None 650 elif mergeRequests_fn is True: 651 mergeRequests_fn = Builder._defaultMergeRequestFn 652 653 return mergeRequests_fn
654
655 - def _defaultMergeRequestFn(self, req1, req2):
656 return req1.canBeMergedWith(req2)
657 658 @defer.deferredGenerator
659 - def _mergeRequests(self, breq, unclaimed_requests, mergeRequests_fn):
660 """Use C{mergeRequests_fn} to merge C{breq} against 661 C{unclaimed_requests}, where both are build request dictionaries""" 662 # short circuit if there is no merging to do 663 if not mergeRequests_fn or len(unclaimed_requests) == 1: 664 yield [ breq ] 665 return 666 667 # we'll need BuildRequest objects, so get those first 668 wfd = defer.waitForDeferred( 669 defer.gatherResults( 670 [ self._brdictToBuildRequest(brdict) 671 for brdict in unclaimed_requests ])) 672 yield wfd 673 unclaimed_request_objects = wfd.getResult() 674 breq_object = unclaimed_request_objects.pop( 675 unclaimed_requests.index(breq)) 676 677 # gather the mergeable requests 678 merged_request_objects = [breq_object] 679 for other_breq_object in unclaimed_request_objects: 680 wfd = defer.waitForDeferred( 681 defer.maybeDeferred(lambda : 682 mergeRequests_fn(self, breq_object, other_breq_object))) 683 yield wfd 684 if wfd.getResult(): 685 merged_request_objects.append(other_breq_object) 686 687 # convert them back to brdicts and return 688 merged_requests = [ br.brdict for br in merged_request_objects ] 689 yield merged_requests
690
691 - def _brdictToBuildRequest(self, brdict):
692 """ 693 Convert a build request dictionary to a L{buildrequest.BuildRequest} 694 object, caching the result in the dictionary itself. The resulting 695 buildrequest will have a C{brdict} attribute pointing back to this 696 dictionary. 697 698 Note that this does not perform any locking - be careful that it is 699 only called once at a time for each build request dictionary. 700 701 @param brdict: dictionary to convert 702 703 @returns: L{buildrequest.BuildRequest} via Deferred 704 """ 705 if 'brobj' in brdict: 706 return defer.succeed(brdict['brobj']) 707 d = buildrequest.BuildRequest.fromBrdict(self.master, brdict) 708 def keep(buildrequest): 709 brdict['brobj'] = buildrequest 710 buildrequest.brdict = brdict 711 return buildrequest
712 d.addCallback(keep) 713 return d 714
715 - def _breakBrdictRefloops(self, requests):
716 """Break the reference loops created by L{_brdictToBuildRequest}""" 717 for brdict in requests: 718 try: 719 del brdict['brobj'].brdict 720 except KeyError: 721 pass
722
723 724 -class BuilderControl:
725 implements(interfaces.IBuilderControl) 726
727 - def __init__(self, builder, master):
728 self.original = builder 729 self.master = master
730
731 - def submitBuildRequest(self, ss, reason, props=None):
732 d = ss.getSourceStampSetId(self.master.master) 733 def add_buildset(sourcestampsetid): 734 return self.master.master.addBuildset( 735 builderNames=[self.original.name], 736 sourcestampsetid=sourcestampsetid, reason=reason, properties=props)
737 d.addCallback(add_buildset) 738 def get_brs((bsid,brids)): 739 brs = BuildRequestStatus(self.original.name, 740 brids[self.original.name], 741 self.master.master.status) 742 return brs
743 d.addCallback(get_brs) 744 return d 745
746 - def rebuildBuild(self, bs, reason="<rebuild, no reason given>", extraProperties=None):
747 if not bs.isFinished(): 748 return 749 750 # Make a copy of the properties so as not to modify the original build. 751 properties = Properties() 752 # Don't include runtime-set properties in a rebuild request 753 properties.updateFromPropertiesNoRuntime(bs.getProperties()) 754 if extraProperties is None: 755 properties.updateFromProperties(extraProperties) 756 757 properties_dict = dict((k,(v,s)) for (k,v,s) in properties.asList()) 758 ss = bs.getSourceStamp(absolute=True) 759 d = ss.getSourceStampSetId(self.master.master) 760 def add_buildset(sourcestampsetid): 761 return self.master.master.addBuildset( 762 builderNames=[self.original.name], 763 sourcestampsetid=sourcestampsetid, reason=reason, properties=properties_dict)
764 d.addCallback(add_buildset) 765 return d 766 767 @defer.deferredGenerator
768 - def getPendingBuildRequestControls(self):
769 master = self.original.master 770 wfd = defer.waitForDeferred( 771 master.db.buildrequests.getBuildRequests( 772 buildername=self.original.name, 773 claimed=False)) 774 yield wfd 775 brdicts = wfd.getResult() 776 777 # convert those into BuildRequest objects 778 buildrequests = [ ] 779 for brdict in brdicts: 780 wfd = defer.waitForDeferred( 781 buildrequest.BuildRequest.fromBrdict(self.master.master, 782 brdict)) 783 yield wfd 784 buildrequests.append(wfd.getResult()) 785 786 # and return the corresponding control objects 787 yield [ buildrequest.BuildRequestControl(self.original, r) 788 for r in buildrequests ]
789
790 - def getBuild(self, number):
791 return self.original.getBuild(number)
792
793 - def ping(self):
794 if not self.original.slaves: 795 self.original.builder_status.addPointEvent(["ping", "no slave"]) 796 return defer.succeed(False) # interfaces.NoSlaveError 797 dl = [] 798 for s in self.original.slaves: 799 dl.append(s.ping(self.original.builder_status)) 800 d = defer.DeferredList(dl) 801 d.addCallback(self._gatherPingResults) 802 return d
803
804 - def _gatherPingResults(self, res):
805 for ignored,success in res: 806 if not success: 807 return False 808 return True
809