Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/buildbot/buildbot/process/builder.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildbot/buildbot/process/builder.py')
-rw-r--r--buildbot/buildbot/process/builder.py874
1 files changed, 0 insertions, 874 deletions
diff --git a/buildbot/buildbot/process/builder.py b/buildbot/buildbot/process/builder.py
deleted file mode 100644
index cb26ccb..0000000
--- a/buildbot/buildbot/process/builder.py
+++ /dev/null
@@ -1,874 +0,0 @@
-
-import random, weakref
-from zope.interface import implements
-from twisted.python import log, components
-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
-
-(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
- ) = range(5)
-
-
-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 ping(self, timeout, 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, timeout).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
- timer = None
-
- def ping(self, remote, timeout):
- 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,))
-
- # We use either our own timeout or the (long) TCP timeout to detect
- # silently-missing slaves. This might happen because of a NAT
- # timeout or a routing loop. If the slave just shuts down (and we
- # somehow missed the FIN), we should get a "connection refused"
- # message.
- self.timer = reactor.callLater(timeout, self._ping_timeout, remote)
- return self.d
-
- def _ping_timeout(self, remote):
- log.msg("ping timeout")
- # force the BuildSlave to disconnect, since this indicates that
- # the bot is unreachable.
- del self.timer
- remote.broker.transport.loseConnection()
- # the forcibly-lost connection will now cause the ping to fail
-
- def _stopTimer(self):
- if not self.running:
- return
- self.running = False
-
- if self.timer:
- self.timer.cancel()
- del self.timer
-
- def _pong(self, res):
- log.msg("ping finished: success")
- self._stopTimer()
- self.d.callback(True)
-
- def _ping_failed(self, res, remote):
- log.msg("ping finished: failure")
- self._stopTimer()
- # 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 substantiate(self, build):
- 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, timeout, status=None):
- if not self.slave.substantiated:
- if status:
- status.addEvent(["ping", "latent"]).finish()
- return defer.succeed(True)
- return AbstractSlaveBuilder.ping(self, timeout, 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.
-
- I am persisted in C{BASEDIR/BUILDERNAME/builder}, so I can remember how
- long a build usually takes to run (in my C{expectations} attribute). This
- pickle also includes the L{buildbot.status.builder.BuilderStatus} object,
- which remembers the set of historic builds.
-
- @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
- START_BUILD_TIMEOUT = 10
- 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, 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.buildFactory = setup['factory']
- 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")
-
- # 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['factory'] != self.buildFactory: # compare objects
- diffs.append('factory changed')
- oldlocks = [(lock.__class__, lock.name)
- for lock in self.locks]
- newlocks = [(lock.__class__, lock.name)
- for lock in setup.get('locks',[])]
- if oldlocks != newlocks:
- diffs.append('locks changed from %s to %s' % (oldlocks, newlocks))
- 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.maybeStartBuild()
-
- def cancelBuildRequest(self, req):
- if req in self.buildable:
- self.buildable.remove(req)
- self.builder_status.removeBuildRequest(req.status)
- return True
- return False
-
- def __getstate__(self):
- d = self.__dict__.copy()
- # TODO: note that d['buildable'] can contain Deferreds
- del d['building'] # TODO: move these back to .buildable?
- del d['slaves']
- return d
-
- def __setstate__(self, d):
- self.__dict__ = d
- self.building = []
- self.slaves = []
-
- 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.number == number:
- return b
- for b in self.old_building.keys():
- if 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.maybeStartBuild)
-
- 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)
- reactor.callLater(0, self.maybeStartBuild)
-
- 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."""
- log.msg("%s.detached" % self, slave.slavename)
- 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: %s %s" %
- (self, self.buildable, 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:
- log.msg("%s: want to start build, but we don't have a remote"
- % self)
- self.updateBigStatus()
- return
- if self.CHOOSE_SLAVES_RANDOMLY:
- # TODO prefer idle over latent? maybe other sorting preferences?
- 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.
- req = self.buildable.pop(0)
- 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
-
- # 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()
- if isinstance(sb, LatentSlaveBuilder):
- log.msg("starting build %s.. substantiating the slave %s" %
- (build, sb))
- d = sb.substantiate(build)
- def substantiated(res):
- return sb.ping(self.START_BUILD_TIMEOUT)
- def substantiation_failed(res):
- self.builder_status.addPointEvent(
- ['removing', 'latent', sb.slave.slavename])
- sb.slave.disconnect()
- # TODO: should failover to a new Build
- #self.retryBuild(sb.build)
- d.addCallbacks(substantiated, substantiation_failed)
- else:
- log.msg("starting build %s.. pinging the slave %s" % (build, sb))
- d = sb.ping(self.START_BUILD_TIMEOUT)
- # ping the slave to make sure they're still there. If they're 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: consider
- # making this time out faster, or at least characterize the likely
- # duration.
- 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>"):
- if not bs.isFinished():
- return
-
- ss = bs.getSourceStamp(absolute=True)
- req = base.BuildRequest(reason, ss, self.original.name)
- self.requestBuild(req)
-
- def getPendingBuilds(self):
- # return IBuildRequestControl objects
- raise NotImplementedError
-
- def getBuild(self, number):
- return self.original.getBuild(number)
-
- def ping(self, timeout=30):
- 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(timeout, 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)