1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
38 reconfig_priority = 196
39
41 service.MultiService.__init__(self)
42 self.name = name
43
44
45 self.expectations = None
46
47
48 self.building = []
49
50 self.old_building = weakref.WeakKeyDictionary()
51
52
53
54 self.attaching_slaves = []
55
56
57
58
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
68
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
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
89 d = defer.maybeDeferred(lambda :
90 service.MultiService.stopService(self))
91 def flushMaybeStartBuilds(_):
92
93
94
95
96 return self.maybeStartBuild()
97 d.addCallback(flushMaybeStartBuilds)
98 return d
99
101 return "<Builder '%r' at %d>" % (self.name, id(self))
102
103 @defer.deferredGenerator
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
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
146
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
177
178
179
180
181
182
183
184
185
186
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
205
213
214
241
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
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
261
262
263 cleanups = []
264 def run_cleanups():
265 while cleanups:
266 fn = cleanups.pop()
267 fn()
268
269
270
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
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
285 self.building.append(build)
286 cleanups.append(lambda : self.building.remove(build))
287
288
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
301
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
310
311
312
313
314
315
316
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
335
336
337 slavebuilder.buildStarted()
338 cleanups.append(lambda : slavebuilder.buildFinished())
339
340
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
353 bs = self.builder_status.newBuild()
354
355
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
370 self.master.status.build_started(req.id, self.name, bs)
371
372
373
374
375
376
377
378 d = build.startBuild(bs, self.expectations, slavebuilder)
379 d.addCallback(self.buildFinished, slavebuilder, bids)
380
381 d.addErrback(log.err)
382
383
384 self.updateBigStatus()
385
386 yield True
387
395
428
429 @defer.deferredGenerator
431
432 for br in requests:
433 wfd = defer.waitForDeferred(
434 self.master.maybeBuildsetComplete(br.bsid))
435 yield wfd
436 wfd.getResult()
437
441
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
453
454 self.expectations = Expectations(progress)
455 log.msg("new expectations: %s seconds" % \
456 self.expectations.expectedBuildTime())
457
458
459
460 @defer.deferredGenerator
462
463
464
465
466
467
468
469
470 if not self.running:
471 return
472
473
474
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
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
493 unclaimed_requests.sort(key=lambda brd : brd['submitted_at'])
494
495
496 mergeRequests_fn = self._getMergeRequestsFn()
497
498
499 while available_slavebuilders and unclaimed_requests:
500
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
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
529
530 wfd = defer.waitForDeferred(
531 self._mergeRequests(brdict, unclaimed_requests,
532 mergeRequests_fn))
533 yield wfd
534 brdicts = wfd.getResult()
535
536
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
545
546
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
555 continue
556
557
558
559
560
561
562
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
576 wfd = defer.waitForDeferred(
577 self.master.db.buildrequests.unclaimBuildRequests(brids))
578 yield wfd
579 wfd.getResult()
580
581
582
583 self.botmaster.maybeStartBuildsForBuilder(self.name)
584
585
586
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
597
598
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
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
624
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
631 return brobj.brdict
632 d.addCallback(to_brdict)
633 return d
634 else:
635 return defer.succeed(buildrequests[0])
636
638 """Helper function to determine which mergeRequests function to use
639 from L{_mergeRequests}, or None for no merging"""
640
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
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
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
663 if not mergeRequests_fn or len(unclaimed_requests) == 1:
664 yield [ breq ]
665 return
666
667
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
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
688 merged_requests = [ br.brdict for br in merged_request_objects ]
689 yield merged_requests
690
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
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
743 d.addCallback(get_brs)
744 return d
745
746 - def rebuildBuild(self, bs, reason="<rebuild, no reason given>", extraProperties=None):
764 d.addCallback(add_buildset)
765 return d
766
767 @defer.deferredGenerator
789
792
794 if not self.original.slaves:
795 self.original.builder_status.addPointEvent(["ping", "no slave"])
796 return defer.succeed(False)
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
805 for ignored,success in res:
806 if not success:
807 return False
808 return True
809