Index: third_party/buildbot_7_12/buildbot/process/builder.py |
diff --git a/third_party/buildbot_7_12/buildbot/process/builder.py b/third_party/buildbot_7_12/buildbot/process/builder.py |
deleted file mode 100644 |
index a7f0ca7a30c04ea0c9900d60a516c772a671e02e..0000000000000000000000000000000000000000 |
--- a/third_party/buildbot_7_12/buildbot/process/builder.py |
+++ /dev/null |
@@ -1,909 +0,0 @@ |
- |
-import random, weakref |
-from zope.interface import implements |
-from twisted.python import log, components |
-from twisted.python.failure import Failure |
-from twisted.spread import pb |
-from twisted.internet import reactor, defer |
- |
-from buildbot import interfaces |
-from buildbot.status.progress import Expectations |
-from buildbot.util import now |
-from buildbot.process import base |
-from buildbot.process.properties import Properties |
- |
-(ATTACHING, # slave attached, still checking hostinfo/etc |
- IDLE, # idle, available for use |
- PINGING, # build about to start, making sure it is still alive |
- BUILDING, # build is running |
- LATENT, # latent slave is not substantiated; similar to idle |
- SUBSTANTIATING, |
- ) = range(6) |
- |
- |
-class AbstractSlaveBuilder(pb.Referenceable): |
- """I am the master-side representative for one of the |
- L{buildbot.slave.bot.SlaveBuilder} objects that lives in a remote |
- buildbot. When a remote builder connects, I query it for command versions |
- and then make it available to any Builds that are ready to run. """ |
- |
- def __init__(self): |
- self.ping_watchers = [] |
- self.state = None # set in subclass |
- self.remote = None |
- self.slave = None |
- self.builder_name = None |
- |
- def __repr__(self): |
- r = ["<", self.__class__.__name__] |
- if self.builder_name: |
- r.extend([" builder=", self.builder_name]) |
- if self.slave: |
- r.extend([" slave=", self.slave.slavename]) |
- r.append(">") |
- return ''.join(r) |
- |
- def setBuilder(self, b): |
- self.builder = b |
- self.builder_name = b.name |
- |
- def getSlaveCommandVersion(self, command, oldversion=None): |
- if self.remoteCommands is None: |
- # the slave is 0.5.0 or earlier |
- return oldversion |
- return self.remoteCommands.get(command) |
- |
- def isAvailable(self): |
- # if this SlaveBuilder is busy, then it's definitely not available |
- if self.isBusy(): |
- return False |
- |
- # otherwise, check in with the BuildSlave |
- if self.slave: |
- return self.slave.canStartBuild() |
- |
- # no slave? not very available. |
- return False |
- |
- def isBusy(self): |
- return self.state not in (IDLE, LATENT) |
- |
- def buildStarted(self): |
- self.state = BUILDING |
- |
- def buildFinished(self): |
- self.state = IDLE |
- reactor.callLater(0, self.builder.botmaster.maybeStartAllBuilds) |
- |
- def attached(self, slave, remote, commands): |
- """ |
- @type slave: L{buildbot.buildslave.BuildSlave} |
- @param slave: the BuildSlave that represents the buildslave as a |
- whole |
- @type remote: L{twisted.spread.pb.RemoteReference} |
- @param remote: a reference to the L{buildbot.slave.bot.SlaveBuilder} |
- @type commands: dict: string -> string, or None |
- @param commands: provides the slave's version of each RemoteCommand |
- """ |
- self.state = ATTACHING |
- self.remote = remote |
- self.remoteCommands = commands # maps command name to version |
- if self.slave is None: |
- self.slave = slave |
- self.slave.addSlaveBuilder(self) |
- else: |
- assert self.slave == slave |
- log.msg("Buildslave %s attached to %s" % (slave.slavename, |
- self.builder_name)) |
- d = self.remote.callRemote("setMaster", self) |
- d.addErrback(self._attachFailure, "Builder.setMaster") |
- d.addCallback(self._attached2) |
- return d |
- |
- def _attached2(self, res): |
- d = self.remote.callRemote("print", "attached") |
- d.addErrback(self._attachFailure, "Builder.print 'attached'") |
- d.addCallback(self._attached3) |
- return d |
- |
- def _attached3(self, res): |
- # now we say they're really attached |
- self.state = IDLE |
- return self |
- |
- def _attachFailure(self, why, where): |
- assert isinstance(where, str) |
- log.msg(where) |
- log.err(why) |
- return why |
- |
- def prepare(self, builder_status): |
- return defer.succeed(None) |
- |
- def ping(self, status=None): |
- """Ping the slave to make sure it is still there. Returns a Deferred |
- that fires with True if it is. |
- |
- @param status: if you point this at a BuilderStatus, a 'pinging' |
- event will be pushed. |
- """ |
- oldstate = self.state |
- self.state = PINGING |
- newping = not self.ping_watchers |
- d = defer.Deferred() |
- self.ping_watchers.append(d) |
- if newping: |
- if status: |
- event = status.addEvent(["pinging"]) |
- d2 = defer.Deferred() |
- d2.addCallback(self._pong_status, event) |
- self.ping_watchers.insert(0, d2) |
- # I think it will make the tests run smoother if the status |
- # is updated before the ping completes |
- Ping().ping(self.remote).addCallback(self._pong) |
- |
- def reset_state(res): |
- if self.state == PINGING: |
- self.state = oldstate |
- return res |
- d.addCallback(reset_state) |
- return d |
- |
- def _pong(self, res): |
- watchers, self.ping_watchers = self.ping_watchers, [] |
- for d in watchers: |
- d.callback(res) |
- |
- def _pong_status(self, res, event): |
- if res: |
- event.text = ["ping", "success"] |
- else: |
- event.text = ["ping", "failed"] |
- event.finish() |
- |
- def detached(self): |
- log.msg("Buildslave %s detached from %s" % (self.slave.slavename, |
- self.builder_name)) |
- if self.slave: |
- self.slave.removeSlaveBuilder(self) |
- self.slave = None |
- self.remote = None |
- self.remoteCommands = None |
- |
- |
-class Ping: |
- running = False |
- |
- def ping(self, remote): |
- assert not self.running |
- self.running = True |
- log.msg("sending ping") |
- self.d = defer.Deferred() |
- # TODO: add a distinct 'ping' command on the slave.. using 'print' |
- # for this purpose is kind of silly. |
- remote.callRemote("print", "ping").addCallbacks(self._pong, |
- self._ping_failed, |
- errbackArgs=(remote,)) |
- return self.d |
- |
- def _pong(self, res): |
- log.msg("ping finished: success") |
- self.d.callback(True) |
- |
- def _ping_failed(self, res, remote): |
- log.msg("ping finished: failure") |
- # the slave has some sort of internal error, disconnect them. If we |
- # don't, we'll requeue a build and ping them again right away, |
- # creating a nasty loop. |
- remote.broker.transport.loseConnection() |
- # TODO: except, if they actually did manage to get this far, they'll |
- # probably reconnect right away, and we'll do this game again. Maybe |
- # it would be better to leave them in the PINGING state. |
- self.d.callback(False) |
- |
- |
-class SlaveBuilder(AbstractSlaveBuilder): |
- |
- def __init__(self): |
- AbstractSlaveBuilder.__init__(self) |
- self.state = ATTACHING |
- |
- def detached(self): |
- AbstractSlaveBuilder.detached(self) |
- if self.slave: |
- self.slave.removeSlaveBuilder(self) |
- self.slave = None |
- self.state = ATTACHING |
- |
- def buildFinished(self): |
- # Call the slave's buildFinished if we can; the slave may be waiting |
- # to do a graceful shutdown and needs to know when it's idle. |
- # After, we check to see if we can start other builds. |
- self.state = IDLE |
- if self.slave: |
- d = self.slave.buildFinished(self) |
- d.addCallback(lambda x: reactor.callLater(0, self.builder.botmaster.maybeStartAllBuilds)) |
- else: |
- reactor.callLater(0, self.builder.botmaster.maybeStartAllBuilds) |
- |
- |
-class LatentSlaveBuilder(AbstractSlaveBuilder): |
- def __init__(self, slave, builder): |
- AbstractSlaveBuilder.__init__(self) |
- self.slave = slave |
- self.state = LATENT |
- self.setBuilder(builder) |
- self.slave.addSlaveBuilder(self) |
- log.msg("Latent buildslave %s attached to %s" % (slave.slavename, |
- self.builder_name)) |
- |
- def prepare(self, builder_status): |
- log.msg("substantiating slave %s" % (self,)) |
- d = self.substantiate() |
- def substantiation_failed(f): |
- builder_status.addPointEvent(['removing', 'latent', |
- self.slave.slavename]) |
- self.slave.disconnect() |
- # TODO: should failover to a new Build |
- return f |
- d.addErrback(substantiation_failed) |
- return d |
- |
- def substantiate(self): |
- self.state = SUBSTANTIATING |
- d = self.slave.substantiate(self) |
- if not self.slave.substantiated: |
- event = self.builder.builder_status.addEvent( |
- ["substantiating"]) |
- def substantiated(res): |
- msg = ["substantiate", "success"] |
- if isinstance(res, basestring): |
- msg.append(res) |
- elif isinstance(res, (tuple, list)): |
- msg.extend(res) |
- event.text = msg |
- event.finish() |
- return res |
- def substantiation_failed(res): |
- event.text = ["substantiate", "failed"] |
- # TODO add log of traceback to event |
- event.finish() |
- return res |
- d.addCallbacks(substantiated, substantiation_failed) |
- return d |
- |
- def detached(self): |
- AbstractSlaveBuilder.detached(self) |
- self.state = LATENT |
- |
- def buildStarted(self): |
- AbstractSlaveBuilder.buildStarted(self) |
- self.slave.buildStarted(self) |
- |
- def buildFinished(self): |
- AbstractSlaveBuilder.buildFinished(self) |
- self.slave.buildFinished(self) |
- |
- def _attachFailure(self, why, where): |
- self.state = LATENT |
- return AbstractSlaveBuilder._attachFailure(self, why, where) |
- |
- def ping(self, status=None): |
- if not self.slave.substantiated: |
- if status: |
- status.addEvent(["ping", "latent"]).finish() |
- return defer.succeed(True) |
- return AbstractSlaveBuilder.ping(self, status) |
- |
- |
-class Builder(pb.Referenceable): |
- """I manage all Builds of a given type. |
- |
- Each Builder is created by an entry in the config file (the c['builders'] |
- list), with a number of parameters. |
- |
- One of these parameters is the L{buildbot.process.factory.BuildFactory} |
- object that is associated with this Builder. The factory is responsible |
- for creating new L{Build<buildbot.process.base.Build>} objects. Each |
- Build object defines when and how the build is performed, so a new |
- Factory or Builder should be defined to control this behavior. |
- |
- The Builder holds on to a number of L{base.BuildRequest} objects in a |
- list named C{.buildable}. Incoming BuildRequest objects will be added to |
- this list, or (if possible) merged into an existing request. When a slave |
- becomes available, I will use my C{BuildFactory} to turn the request into |
- a new C{Build} object. The C{BuildRequest} is forgotten, the C{Build} |
- goes into C{.building} while it runs. Once the build finishes, I will |
- discard it. |
- |
- I maintain a list of available SlaveBuilders, one for each connected |
- slave that the C{slavenames} parameter says we can use. Some of these |
- will be idle, some of them will be busy running builds for me. If there |
- are multiple slaves, I can run multiple builds at once. |
- |
- I also manage forced builds, progress expectation (ETA) management, and |
- some status delivery chores. |
- |
- @type buildable: list of L{buildbot.process.base.BuildRequest} |
- @ivar buildable: BuildRequests that are ready to build, but which are |
- waiting for a buildslave to be available. |
- |
- @type building: list of L{buildbot.process.base.Build} |
- @ivar building: Builds that are actively running |
- |
- @type slaves: list of L{buildbot.buildslave.BuildSlave} objects |
- @ivar slaves: the slaves currently available for building |
- """ |
- |
- expectations = None # this is created the first time we get a good build |
- CHOOSE_SLAVES_RANDOMLY = True # disabled for determinism during tests |
- |
- def __init__(self, setup, builder_status): |
- """ |
- @type setup: dict |
- @param setup: builder setup data, as stored in |
- BuildmasterConfig['builders']. Contains name, |
- slavename(s), builddir, slavebuilddir, factory, locks. |
- @type builder_status: L{buildbot.status.builder.BuilderStatus} |
- """ |
- self.name = setup['name'] |
- self.slavenames = [] |
- if setup.has_key('slavename'): |
- self.slavenames.append(setup['slavename']) |
- if setup.has_key('slavenames'): |
- self.slavenames.extend(setup['slavenames']) |
- self.builddir = setup['builddir'] |
- self.slavebuilddir = setup['slavebuilddir'] |
- self.buildFactory = setup['factory'] |
- self.nextSlave = setup.get('nextSlave') |
- if self.nextSlave is not None and not callable(self.nextSlave): |
- raise ValueError("nextSlave must be callable") |
- self.locks = setup.get("locks", []) |
- self.env = setup.get('env', {}) |
- assert isinstance(self.env, dict) |
- if setup.has_key('periodicBuildTime'): |
- raise ValueError("periodicBuildTime can no longer be defined as" |
- " part of the Builder: use scheduler.Periodic" |
- " instead") |
- self.nextBuild = setup.get('nextBuild') |
- if self.nextBuild is not None and not callable(self.nextBuild): |
- raise ValueError("nextBuild must be callable") |
- |
- # build/wannabuild slots: Build objects move along this sequence |
- self.buildable = [] |
- self.building = [] |
- # old_building holds active builds that were stolen from a predecessor |
- self.old_building = weakref.WeakKeyDictionary() |
- |
- # buildslaves which have connected but which are not yet available. |
- # These are always in the ATTACHING state. |
- self.attaching_slaves = [] |
- |
- # buildslaves at our disposal. Each SlaveBuilder instance has a |
- # .state that is IDLE, PINGING, or BUILDING. "PINGING" is used when a |
- # Build is about to start, to make sure that they're still alive. |
- self.slaves = [] |
- |
- self.builder_status = builder_status |
- self.builder_status.setSlavenames(self.slavenames) |
- |
- # for testing, to help synchronize tests |
- self.watchers = {'attach': [], 'detach': [], 'detach_all': [], |
- 'idle': []} |
- |
- def setBotmaster(self, botmaster): |
- self.botmaster = botmaster |
- |
- def compareToSetup(self, setup): |
- diffs = [] |
- setup_slavenames = [] |
- if setup.has_key('slavename'): |
- setup_slavenames.append(setup['slavename']) |
- setup_slavenames.extend(setup.get('slavenames', [])) |
- if setup_slavenames != self.slavenames: |
- diffs.append('slavenames changed from %s to %s' \ |
- % (self.slavenames, setup_slavenames)) |
- if setup['builddir'] != self.builddir: |
- diffs.append('builddir changed from %s to %s' \ |
- % (self.builddir, setup['builddir'])) |
- if setup['slavebuilddir'] != self.slavebuilddir: |
- diffs.append('slavebuilddir changed from %s to %s' \ |
- % (self.slavebuilddir, setup['slavebuilddir'])) |
- if setup['factory'] != self.buildFactory: # compare objects |
- diffs.append('factory changed') |
- if setup.get('locks', []) != self.locks: |
- diffs.append('locks changed from %s to %s' % (self.locks, setup.get('locks'))) |
- if setup.get('nextSlave') != self.nextSlave: |
- diffs.append('nextSlave changed from %s to %s' % (self.nextSlave, setup['nextSlave'])) |
- if setup.get('nextBuild') != self.nextBuild: |
- diffs.append('nextBuild changed from %s to %s' % (self.nextBuild, setup['nextBuild'])) |
- return diffs |
- |
- def __repr__(self): |
- return "<Builder '%s' at %d>" % (self.name, id(self)) |
- |
- def getOldestRequestTime(self): |
- """Returns the timestamp of the oldest build request for this builder. |
- |
- If there are no build requests, None is returned.""" |
- if self.buildable: |
- return self.buildable[0].getSubmitTime() |
- else: |
- return None |
- |
- def submitBuildRequest(self, req): |
- req.setSubmitTime(now()) |
- self.buildable.append(req) |
- req.requestSubmitted(self) |
- self.builder_status.addBuildRequest(req.status) |
- self.botmaster.maybeStartAllBuilds() |
- |
- def cancelBuildRequest(self, req): |
- if req in self.buildable: |
- self.buildable.remove(req) |
- self.builder_status.removeBuildRequest(req.status, cancelled=True) |
- return True |
- return False |
- |
- def consumeTheSoulOfYourPredecessor(self, old): |
- """Suck the brain out of an old Builder. |
- |
- This takes all the runtime state from an existing Builder and moves |
- it into ourselves. This is used when a Builder is changed in the |
- master.cfg file: the new Builder has a different factory, but we want |
- all the builds that were queued for the old one to get processed by |
- the new one. Any builds which are already running will keep running. |
- The new Builder will get as many of the old SlaveBuilder objects as |
- it wants.""" |
- |
- log.msg("consumeTheSoulOfYourPredecessor: %s feeding upon %s" % |
- (self, old)) |
- # we claim all the pending builds, removing them from the old |
- # Builder's queue. This insures that the old Builder will not start |
- # any new work. |
- log.msg(" stealing %s buildrequests" % len(old.buildable)) |
- self.buildable.extend(old.buildable) |
- old.buildable = [] |
- |
- # old.building (i.e. builds which are still running) is not migrated |
- # directly: it keeps track of builds which were in progress in the |
- # old Builder. When those builds finish, the old Builder will be |
- # notified, not us. However, since the old SlaveBuilder will point to |
- # us, it is our maybeStartBuild() that will be triggered. |
- if old.building: |
- self.builder_status.setBigState("building") |
- # however, we do grab a weakref to the active builds, so that our |
- # BuilderControl can see them and stop them. We use a weakref because |
- # we aren't the one to get notified, so there isn't a convenient |
- # place to remove it from self.building . |
- for b in old.building: |
- self.old_building[b] = None |
- for b in old.old_building: |
- self.old_building[b] = None |
- |
- # Our set of slavenames may be different. Steal any of the old |
- # buildslaves that we want to keep using. |
- for sb in old.slaves[:]: |
- if sb.slave.slavename in self.slavenames: |
- log.msg(" stealing buildslave %s" % sb) |
- self.slaves.append(sb) |
- old.slaves.remove(sb) |
- sb.setBuilder(self) |
- |
- # old.attaching_slaves: |
- # these SlaveBuilders are waiting on a sequence of calls: |
- # remote.setMaster and remote.print . When these two complete, |
- # old._attached will be fired, which will add a 'connect' event to |
- # the builder_status and try to start a build. However, we've pulled |
- # everything out of the old builder's queue, so it will have no work |
- # to do. The outstanding remote.setMaster/print call will be holding |
- # the last reference to the old builder, so it will disappear just |
- # after that response comes back. |
- # |
- # The BotMaster will ask the slave to re-set their list of Builders |
- # shortly after this function returns, which will cause our |
- # attached() method to be fired with a bunch of references to remote |
- # SlaveBuilders, some of which we already have (by stealing them |
- # from the old Builder), some of which will be new. The new ones |
- # will be re-attached. |
- |
- # Therefore, we don't need to do anything about old.attaching_slaves |
- |
- return # all done |
- |
- def getBuild(self, number): |
- for b in self.building: |
- if b.build_status and b.build_status.number == number: |
- return b |
- for b in self.old_building.keys(): |
- if b.build_status and b.build_status.number == number: |
- return b |
- return None |
- |
- def fireTestEvent(self, name, fire_with=None): |
- if fire_with is None: |
- fire_with = self |
- watchers = self.watchers[name] |
- self.watchers[name] = [] |
- for w in watchers: |
- reactor.callLater(0, w.callback, fire_with) |
- |
- def addLatentSlave(self, slave): |
- assert interfaces.ILatentBuildSlave.providedBy(slave) |
- for s in self.slaves: |
- if s == slave: |
- break |
- else: |
- sb = LatentSlaveBuilder(slave, self) |
- self.builder_status.addPointEvent( |
- ['added', 'latent', slave.slavename]) |
- self.slaves.append(sb) |
- reactor.callLater(0, self.botmaster.maybeStartAllBuilds) |
- |
- def attached(self, slave, remote, commands): |
- """This is invoked by the BuildSlave when the self.slavename bot |
- registers their builder. |
- |
- @type slave: L{buildbot.buildslave.BuildSlave} |
- @param slave: the BuildSlave that represents the buildslave as a whole |
- @type remote: L{twisted.spread.pb.RemoteReference} |
- @param remote: a reference to the L{buildbot.slave.bot.SlaveBuilder} |
- @type commands: dict: string -> string, or None |
- @param commands: provides the slave's version of each RemoteCommand |
- |
- @rtype: L{twisted.internet.defer.Deferred} |
- @return: a Deferred that fires (with 'self') when the slave-side |
- builder is fully attached and ready to accept commands. |
- """ |
- for s in self.attaching_slaves + self.slaves: |
- if s.slave == slave: |
- # already attached to them. This is fairly common, since |
- # attached() gets called each time we receive the builder |
- # list from the slave, and we ask for it each time we add or |
- # remove a builder. So if the slave is hosting builders |
- # A,B,C, and the config file changes A, we'll remove A and |
- # re-add it, triggering two builder-list requests, getting |
- # two redundant calls to attached() for B, and another two |
- # for C. |
- # |
- # Therefore, when we see that we're already attached, we can |
- # just ignore it. TODO: build a diagram of the state |
- # transitions here, I'm concerned about sb.attached() failing |
- # and leaving sb.state stuck at 'ATTACHING', and about |
- # the detached() message arriving while there's some |
- # transition pending such that the response to the transition |
- # re-vivifies sb |
- return defer.succeed(self) |
- |
- sb = SlaveBuilder() |
- sb.setBuilder(self) |
- self.attaching_slaves.append(sb) |
- d = sb.attached(slave, remote, commands) |
- d.addCallback(self._attached) |
- d.addErrback(self._not_attached, slave) |
- return d |
- |
- def _attached(self, sb): |
- # TODO: make this .addSlaveEvent(slave.slavename, ['connect']) ? |
- self.builder_status.addPointEvent(['connect', sb.slave.slavename]) |
- self.attaching_slaves.remove(sb) |
- self.slaves.append(sb) |
- |
- self.fireTestEvent('attach') |
- return self |
- |
- def _not_attached(self, why, slave): |
- # already log.err'ed by SlaveBuilder._attachFailure |
- # TODO: make this .addSlaveEvent? |
- # TODO: remove from self.slaves (except that detached() should get |
- # run first, right?) |
- self.builder_status.addPointEvent(['failed', 'connect', |
- slave.slave.slavename]) |
- # TODO: add an HTMLLogFile of the exception |
- self.fireTestEvent('attach', why) |
- |
- def detached(self, slave): |
- """This is called when the connection to the bot is lost.""" |
- for sb in self.attaching_slaves + self.slaves: |
- if sb.slave == slave: |
- break |
- else: |
- log.msg("WEIRD: Builder.detached(%s) (%s)" |
- " not in attaching_slaves(%s)" |
- " or slaves(%s)" % (slave, slave.slavename, |
- self.attaching_slaves, |
- self.slaves)) |
- return |
- if sb.state == BUILDING: |
- # the Build's .lostRemote method (invoked by a notifyOnDisconnect |
- # handler) will cause the Build to be stopped, probably right |
- # after the notifyOnDisconnect that invoked us finishes running. |
- |
- # TODO: should failover to a new Build |
- #self.retryBuild(sb.build) |
- pass |
- |
- if sb in self.attaching_slaves: |
- self.attaching_slaves.remove(sb) |
- if sb in self.slaves: |
- self.slaves.remove(sb) |
- |
- # TODO: make this .addSlaveEvent? |
- self.builder_status.addPointEvent(['disconnect', slave.slavename]) |
- sb.detached() # inform the SlaveBuilder that their slave went away |
- self.updateBigStatus() |
- self.fireTestEvent('detach') |
- if not self.slaves: |
- self.fireTestEvent('detach_all') |
- |
- def updateBigStatus(self): |
- if not self.slaves: |
- self.builder_status.setBigState("offline") |
- elif self.building: |
- self.builder_status.setBigState("building") |
- else: |
- self.builder_status.setBigState("idle") |
- self.fireTestEvent('idle') |
- |
- def maybeStartBuild(self): |
- log.msg("maybeStartBuild %s: %i request(s), %i slave(s)" % |
- (self, len(self.buildable), len(self.slaves))) |
- if not self.buildable: |
- self.updateBigStatus() |
- return # nothing to do |
- |
- # pick an idle slave |
- available_slaves = [sb for sb in self.slaves if sb.isAvailable()] |
- if not available_slaves: |
- self.updateBigStatus() |
- return |
- if self.nextSlave: |
- sb = None |
- try: |
- sb = self.nextSlave(self, available_slaves) |
- except: |
- log.msg("Exception choosing next slave") |
- log.err(Failure()) |
- |
- if not sb: |
- self.updateBigStatus() |
- return |
- elif self.CHOOSE_SLAVES_RANDOMLY: |
- sb = random.choice(available_slaves) |
- else: |
- sb = available_slaves[0] |
- |
- # there is something to build, and there is a slave on which to build |
- # it. Grab the oldest request, see if we can merge it with anything |
- # else. |
- if not self.nextBuild: |
- req = self.buildable.pop(0) |
- else: |
- try: |
- req = self.nextBuild(self, self.buildable) |
- if not req: |
- # Nothing to do |
- self.updateBigStatus() |
- return |
- self.buildable.remove(req) |
- except: |
- log.msg("Exception choosing next build") |
- log.err(Failure()) |
- self.updateBigStatus() |
- return |
- self.builder_status.removeBuildRequest(req.status) |
- mergers = [] |
- botmaster = self.botmaster |
- for br in self.buildable[:]: |
- if botmaster.shouldMergeRequests(self, req, br): |
- self.buildable.remove(br) |
- self.builder_status.removeBuildRequest(br.status) |
- mergers.append(br) |
- requests = [req] + mergers |
- enforced_sb = [request.properties.getProperty('slavename') |
- for request in requests |
- if request.properties.getProperty('slavename')] |
- enforced_sb = list(set(enforced_sb)) |
- if len(enforced_sb) == 1: |
- if enforced_sb[0] not in self.slaves: |
- # It's better to not use slaves at all than use a random one. |
- log.msg("%s: %s is not a valid slave" % (self, enforced_sb[0])) |
- return |
- sb = enforced_sb[0] |
- |
- # Create a new build from our build factory and set ourself as the |
- # builder. |
- build = self.buildFactory.newBuild(requests) |
- build.setBuilder(self) |
- build.setLocks(self.locks) |
- if len(self.env) > 0: |
- build.setSlaveEnvironment(self.env) |
- |
- # start it |
- self.startBuild(build, sb) |
- |
- def startBuild(self, build, sb): |
- """Start a build on the given slave. |
- @param build: the L{base.Build} to start |
- @param sb: the L{SlaveBuilder} which will host this build |
- |
- @return: a Deferred which fires with a |
- L{buildbot.interfaces.IBuildControl} that can be used to stop the |
- Build, or to access a L{buildbot.interfaces.IBuildStatus} which will |
- watch the Build as it runs. """ |
- |
- self.building.append(build) |
- self.updateBigStatus() |
- log.msg("starting build %s using slave %s" % (build, sb)) |
- d = sb.prepare(self.builder_status) |
- def _ping(ign): |
- # ping the slave to make sure they're still there. If they've |
- # fallen off the map (due to a NAT timeout or something), this |
- # will fail in a couple of minutes, depending upon the TCP |
- # timeout. |
- # |
- # TODO: This can unnecessarily suspend the starting of a build, in |
- # situations where the slave is live but is pushing lots of data to |
- # us in a build. |
- log.msg("starting build %s.. pinging the slave %s" % (build, sb)) |
- return sb.ping() |
- d.addCallback(_ping) |
- d.addCallback(self._startBuild_1, build, sb) |
- return d |
- |
- def _startBuild_1(self, res, build, sb): |
- if not res: |
- return self._startBuildFailed("slave ping failed", build, sb) |
- # The buildslave is ready to go. sb.buildStarted() sets its state to |
- # BUILDING (so we won't try to use it for any other builds). This |
- # gets set back to IDLE by the Build itself when it finishes. |
- sb.buildStarted() |
- d = sb.remote.callRemote("startBuild") |
- d.addCallbacks(self._startBuild_2, self._startBuildFailed, |
- callbackArgs=(build,sb), errbackArgs=(build,sb)) |
- return d |
- |
- def _startBuild_2(self, res, build, sb): |
- # create the BuildStatus object that goes with the Build |
- bs = self.builder_status.newBuild() |
- |
- # start the build. This will first set up the steps, then tell the |
- # BuildStatus that it has started, which will announce it to the |
- # world (through our BuilderStatus object, which is its parent). |
- # Finally it will start the actual build process. |
- d = build.startBuild(bs, self.expectations, sb) |
- d.addCallback(self.buildFinished, sb) |
- d.addErrback(log.err) # this shouldn't happen. if it does, the slave |
- # will be wedged |
- for req in build.requests: |
- req.buildStarted(build, bs) |
- return build # this is the IBuildControl |
- |
- def _startBuildFailed(self, why, build, sb): |
- # put the build back on the buildable list |
- log.msg("I tried to tell the slave that the build %s started, but " |
- "remote_startBuild failed: %s" % (build, why)) |
- # release the slave. This will queue a call to maybeStartBuild, which |
- # will fire after other notifyOnDisconnect handlers have marked the |
- # slave as disconnected (so we don't try to use it again). |
- sb.buildFinished() |
- |
- log.msg("re-queueing the BuildRequest") |
- self.building.remove(build) |
- for req in build.requests: |
- self.buildable.insert(0, req) # the interrupted build gets first |
- # priority |
- self.builder_status.addBuildRequest(req.status) |
- |
- |
- def buildFinished(self, build, sb): |
- """This is called when the Build has finished (either success or |
- failure). Any exceptions during the build are reported with |
- results=FAILURE, not with an errback.""" |
- |
- # by the time we get here, the Build has already released the slave |
- # (which queues a call to maybeStartBuild) |
- |
- self.building.remove(build) |
- for req in build.requests: |
- req.finished(build.build_status) |
- |
- def setExpectations(self, progress): |
- """Mark the build as successful and update expectations for the next |
- build. Only call this when the build did not fail in any way that |
- would invalidate the time expectations generated by it. (if the |
- compile failed and thus terminated early, we can't use the last |
- build to predict how long the next one will take). |
- """ |
- if self.expectations: |
- self.expectations.update(progress) |
- else: |
- # the first time we get a good build, create our Expectations |
- # based upon its results |
- self.expectations = Expectations(progress) |
- log.msg("new expectations: %s seconds" % \ |
- self.expectations.expectedBuildTime()) |
- |
- def shutdownSlave(self): |
- if self.remote: |
- self.remote.callRemote("shutdown") |
- |
- |
-class BuilderControl(components.Adapter): |
- implements(interfaces.IBuilderControl) |
- |
- def requestBuild(self, req): |
- """Submit a BuildRequest to this Builder.""" |
- self.original.submitBuildRequest(req) |
- |
- def requestBuildSoon(self, req): |
- """Submit a BuildRequest like requestBuild, but raise a |
- L{buildbot.interfaces.NoSlaveError} if no slaves are currently |
- available, so it cannot be used to queue a BuildRequest in the hopes |
- that a slave will eventually connect. This method is appropriate for |
- use by things like the web-page 'Force Build' button.""" |
- if not self.original.slaves: |
- raise interfaces.NoSlaveError |
- self.requestBuild(req) |
- |
- def resubmitBuild(self, bs, reason="<rebuild, no reason given>", extraProperties=None): |
- if not bs.isFinished(): |
- return |
- |
- ss = bs.getSourceStamp(absolute=True) |
- if extraProperties is None: |
- properties = bs.getProperties() |
- else: |
- # Make a copy so as not to modify the original build. |
- properties = Properties() |
- properties.updateFromProperties(bs.getProperties()) |
- properties.updateFromProperties(extraProperties) |
- req = base.BuildRequest(reason, ss, self.original.name, |
- properties=properties) |
- self.requestBuild(req) |
- |
- def getPendingBuilds(self): |
- # return IBuildRequestControl objects |
- retval = [] |
- for r in self.original.buildable: |
- retval.append(BuildRequestControl(self.original, r)) |
- |
- return retval |
- |
- def getBuild(self, number): |
- return self.original.getBuild(number) |
- |
- def ping(self): |
- if not self.original.slaves: |
- self.original.builder_status.addPointEvent(["ping", "no slave"]) |
- return defer.succeed(False) # interfaces.NoSlaveError |
- dl = [] |
- for s in self.original.slaves: |
- dl.append(s.ping(self.original.builder_status)) |
- d = defer.DeferredList(dl) |
- d.addCallback(self._gatherPingResults) |
- return d |
- |
- def _gatherPingResults(self, res): |
- for ignored,success in res: |
- if not success: |
- return False |
- return True |
- |
-components.registerAdapter(BuilderControl, Builder, interfaces.IBuilderControl) |
- |
-class BuildRequestControl: |
- implements(interfaces.IBuildRequestControl) |
- |
- def __init__(self, builder, request): |
- self.original_builder = builder |
- self.original_request = request |
- |
- def subscribe(self, observer): |
- raise NotImplementedError |
- |
- def unsubscribe(self, observer): |
- raise NotImplementedError |
- |
- def cancel(self): |
- self.original_builder.cancelBuildRequest(self.original_request) |