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