aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildrequestdistributor.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildrequestdistributor.py')
-rw-r--r--lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildrequestdistributor.py544
1 files changed, 0 insertions, 544 deletions
diff --git a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildrequestdistributor.py b/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildrequestdistributor.py
deleted file mode 100644
index afae3b8e..00000000
--- a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildrequestdistributor.py
+++ /dev/null
@@ -1,544 +0,0 @@
-# This file is part of Buildbot. Buildbot is free software: you can
-# redistribute it and/or modify it under the terms of the GNU General Public
-# License as published by the Free Software Foundation, version 2.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-# details.
-#
-# You should have received a copy of the GNU General Public License along with
-# this program; if not, write to the Free Software Foundation, Inc., 51
-# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Copyright Buildbot Team Members
-
-
-from twisted.python import log
-from twisted.python.failure import Failure
-from twisted.internet import defer
-from twisted.application import service
-
-from buildbot.process import metrics
-from buildbot.process.buildrequest import BuildRequest
-from buildbot.db.buildrequests import AlreadyClaimedError
-
-import random
-
-class BuildChooserBase(object):
- #
- # WARNING: This API is experimental and in active development.
- #
- # This internal object selects a new build+slave pair. It acts as a
- # generator, initializing its state on creation and offering up new
- # pairs until exhaustion. The object can be destroyed at any time
- # (eg, before the list exhausts), and can be "restarted" by abandoning
- # an old instance and creating a new one.
- #
- # The entry point is:
- # * bc.chooseNextBuild() - get the next (slave, [breqs]) or (None, None)
- #
- # The default implementation of this class implements a default
- # chooseNextBuild() that delegates out to two other functions:
- # * bc.popNextBuild() - get the next (slave, breq) pair
- # * bc.mergeRequests(breq) - perform a merge for this breq and return
- # the list of breqs consumed by the merge (including breq itself)
-
- def __init__(self, bldr, master):
- self.bldr = bldr
- self.master = master
- self.breqCache = {}
- self.unclaimedBrdicts = None
-
- @defer.inlineCallbacks
- def chooseNextBuild(self):
- # Return the next build, as a (slave, [breqs]) pair
-
- slave, breq = yield self.popNextBuild()
- if not slave or not breq:
- defer.returnValue((None, None))
- return
-
- breqs = yield self.mergeRequests(breq)
- for b in breqs:
- self._removeBuildRequest(b)
-
- defer.returnValue((slave, breqs))
-
-
- # Must be implemented by subclass
- def popNextBuild(self):
- # Pick the next (slave, breq) pair; note this is pre-merge, so
- # it's just one breq
- raise NotImplementedError("Subclasses must implement this!")
-
- # Must be implemented by subclass
- def mergeRequests(self, breq):
- # Merge the chosen breq with any other breqs that are compatible
- # Returns a list of the breqs chosen (and should include the
- # original breq as well!)
- raise NotImplementedError("Subclasses must implement this!")
-
-
- # - Helper functions that are generally useful to all subclasses -
-
- @defer.inlineCallbacks
- def _fetchUnclaimedBrdicts(self):
- # Sets up a cache of all the unclaimed brdicts. The cache is
- # saved at self.unclaimedBrdicts cache. If the cache already
- # exists, this function does nothing. If a refetch is desired, set
- # the self.unclaimedBrdicts to None before calling."""
-
- if self.unclaimedBrdicts is None:
- brdicts = yield self.master.db.buildrequests.getBuildRequests(
- buildername=self.bldr.name, claimed=False)
- # sort by submitted_at, so the first is the oldest
- brdicts.sort(key=lambda brd : brd['submitted_at'])
- self.unclaimedBrdicts = brdicts
- defer.returnValue(self.unclaimedBrdicts)
-
- @defer.inlineCallbacks
- def _getBuildRequestForBrdict(self, brdict):
- # Turn a brdict into a BuildRequest into a brdict. This is useful
- # for API like 'nextBuild', which operate on BuildRequest objects.
-
- breq = self.breqCache.get(brdict['brid'])
- if not breq:
- breq = yield BuildRequest.fromBrdict(self.master, brdict)
- if breq:
- self.breqCache[brdict['brid']] = breq
- defer.returnValue(breq)
-
- def _getBrdictForBuildRequest(self, breq):
- # Turn a BuildRequest back into a brdict. This operates from the
- # cache, which must be set up once via _fetchUnclaimedBrdicts
-
- if breq is None:
- return None
-
- brid = breq.id
- for brdict in self.unclaimedBrdicts:
- if brid == brdict['brid']:
- return brdict
- return None
-
- def _removeBuildRequest(self, breq):
- # Remove a BuildrRequest object (and its brdict)
- # from the caches
-
- if breq is None:
- return
-
- brdict = self._getBrdictForBuildRequest(breq)
- if brdict is not None:
- self.unclaimedBrdicts.remove(brdict)
-
- if breq.id in self.breqCache:
- del self.breqCache[breq.id]
-
- def _getUnclaimedBuildRequests(self):
- # Retrieve the list of BuildRequest objects for all unclaimed builds
- return defer.gatherResults([
- self._getBuildRequestForBrdict(brdict)
- for brdict in self.unclaimedBrdicts ])
-
-class BasicBuildChooser(BuildChooserBase):
- # BasicBuildChooser generates build pairs via the configuration points:
- # * config.nextSlave (or random.choice if not set)
- # * config.nextBuild (or "pop top" if not set)
- #
- # For N slaves, this will call nextSlave at most N times. If nextSlave
- # returns a slave that cannot satisfy the build chosen by nextBuild,
- # it will search for a slave that can satisfy the build. If one is found,
- # the slaves that cannot be used are "recycled" back into a list
- # to be tried, in order, for the next chosen build.
- #
- # There are two tests performed on the slave:
- # * can the slave start a generic build for the Builder?
- # * if so, can the slave start the chosen build on the Builder?
- # Slaves that cannot meet the first criterion are saved into the
- # self.rejectedSlaves list and will be used as a last resort. An example
- # of this test is whether the slave can grab the Builder's locks.
- #
- # If all slaves fail the first test, then the algorithm will assign the
- # slaves in the order originally generated. By setting self.rejectedSlaves
- # to None, the behavior will instead refuse to ever assign to a slave that
- # fails the generic test.
-
- def __init__(self, bldr, master):
- BuildChooserBase.__init__(self, bldr, master)
-
- self.nextSlave = self.bldr.config.nextSlave
- if not self.nextSlave:
- self.nextSlave = lambda _,slaves: random.choice(slaves) if slaves else None
-
- self.slavepool = self.bldr.getAvailableSlaves()
-
- # Pick slaves one at a time from the pool, and if the Builder says
- # they're usable (eg, locks can be satisfied), then prefer those slaves;
- # otherwise they go in the 'last resort' bucket, and we'll use them if
- # we need to. (Setting rejectedSlaves to None disables that feature)
- self.preferredSlaves = []
- self.rejectedSlaves = []
-
- self.nextBuild = self.bldr.config.nextBuild
-
- self.mergeRequestsFn = self.bldr.getMergeRequestsFn()
-
- @defer.inlineCallbacks
- def popNextBuild(self):
- nextBuild = (None, None)
-
- while 1:
- # 1. pick a slave
- slave = yield self._popNextSlave()
- if not slave:
- break
-
- # 2. pick a build
- breq = yield self._getNextUnclaimedBuildRequest()
- if not breq:
- break
-
- # either satisfy this build or we leave it for another day
- self._removeBuildRequest(breq)
-
- # 3. make sure slave+ is usable for the breq
- recycledSlaves = []
- while slave:
- canStart = yield self.canStartBuild(slave, breq)
- if canStart:
- break
- # try a different slave
- recycledSlaves.append(slave)
- slave = yield self._popNextSlave()
-
- # recycle the slaves that we didnt use to the head of the queue
- # this helps ensure we run 'nextSlave' only once per slave choice
- if recycledSlaves:
- self._unpopSlaves(recycledSlaves)
-
- # 4. done? otherwise we will try another build
- if slave:
- nextBuild = (slave, breq)
- break
-
- defer.returnValue(nextBuild)
-
- @defer.inlineCallbacks
- def mergeRequests(self, breq):
- mergedRequests = [ breq ]
-
- # short circuit if there is no merging to do
- if not self.mergeRequestsFn or not self.unclaimedBrdicts:
- defer.returnValue(mergedRequests)
- return
-
- # we'll need BuildRequest objects, so get those first
- unclaimedBreqs = yield self._getUnclaimedBuildRequests()
-
- # gather the mergeable requests
- for req in unclaimedBreqs:
- canMerge = yield self.mergeRequestsFn(self.bldr, breq, req)
- if canMerge:
- mergedRequests.append(req)
-
- defer.returnValue(mergedRequests)
-
-
- @defer.inlineCallbacks
- def _getNextUnclaimedBuildRequest(self):
- # ensure the cache is there
- yield self._fetchUnclaimedBrdicts()
- if not self.unclaimedBrdicts:
- defer.returnValue(None)
- return
-
- if self.nextBuild:
- # nextBuild expects BuildRequest objects
- breqs = yield self._getUnclaimedBuildRequests()
- try:
- nextBreq = yield self.nextBuild(self.bldr, breqs)
- if nextBreq not in breqs:
- nextBreq = None
- except Exception:
- nextBreq = None
- else:
- # otherwise just return the first build
- brdict = self.unclaimedBrdicts[0]
- nextBreq = yield self._getBuildRequestForBrdict(brdict)
-
- defer.returnValue(nextBreq)
-
- @defer.inlineCallbacks
- def _popNextSlave(self):
- # use 'preferred' slaves first, if we have some ready
- if self.preferredSlaves:
- slave = self.preferredSlaves.pop(0)
- defer.returnValue(slave)
- return
-
- while self.slavepool:
- try:
- slave = yield self.nextSlave(self.bldr, self.slavepool)
- except Exception:
- slave = None
-
- if not slave or slave not in self.slavepool:
- # bad slave or no slave returned
- break
-
- self.slavepool.remove(slave)
-
- canStart = yield self.bldr.canStartWithSlavebuilder(slave)
- if canStart:
- defer.returnValue(slave)
- return
-
- # save as a last resort, just in case we need them later
- if self.rejectedSlaves is not None:
- self.rejectedSlaves.append(slave)
-
- # if we chewed through them all, use as last resort:
- if self.rejectedSlaves:
- slave = self.rejectedSlaves.pop(0)
- defer.returnValue(slave)
- return
-
- defer.returnValue(None)
-
- def _unpopSlaves(self, slaves):
- # push the slaves back to the front
- self.preferredSlaves[:0] = slaves
-
- def canStartBuild(self, slave, breq):
- return self.bldr.canStartBuild(slave, breq)
-
-
-class BuildRequestDistributor(service.Service):
- """
- Special-purpose class to handle distributing build requests to builders by
- calling their C{maybeStartBuild} method.
-
- This takes account of the C{prioritizeBuilders} configuration, and is
- highly re-entrant; that is, if a new build request arrives while builders
- are still working on the previous build request, then this class will
- correctly re-prioritize invocations of builders' C{maybeStartBuild}
- methods.
- """
-
- BuildChooser = BasicBuildChooser
-
- def __init__(self, botmaster):
- self.botmaster = botmaster
- self.master = botmaster.master
-
- # lock to ensure builders are only sorted once at any time
- self.pending_builders_lock = defer.DeferredLock()
-
- # sorted list of names of builders that need their maybeStartBuild
- # method invoked.
- self._pending_builders = []
- self.activity_lock = defer.DeferredLock()
- self.active = False
-
- self._pendingMSBOCalls = []
-
- @defer.inlineCallbacks
- def stopService(self):
- # Lots of stuff happens asynchronously here, so we need to let it all
- # quiesce. First, let the parent stopService succeed between
- # activities; then the loop will stop calling itself, since
- # self.running is false.
- yield self.activity_lock.run(service.Service.stopService, self)
-
- # now let any outstanding calls to maybeStartBuildsOn to finish, so
- # they don't get interrupted in mid-stride. This tends to be
- # particularly painful because it can occur when a generator is gc'd.
- if self._pendingMSBOCalls:
- yield defer.DeferredList(self._pendingMSBOCalls)
-
- def maybeStartBuildsOn(self, new_builders):
- """
- Try to start any builds that can be started right now. This function
- returns immediately, and promises to trigger those builders
- eventually.
-
- @param new_builders: names of new builders that should be given the
- opportunity to check for new requests.
- """
- if not self.running:
- return
-
- d = self._maybeStartBuildsOn(new_builders)
- self._pendingMSBOCalls.append(d)
- @d.addBoth
- def remove(x):
- self._pendingMSBOCalls.remove(d)
- return x
- d.addErrback(log.err, "while strting builds on %s" % (new_builders,))
-
- def _maybeStartBuildsOn(self, new_builders):
- new_builders = set(new_builders)
- existing_pending = set(self._pending_builders)
-
- # if we won't add any builders, there's nothing to do
- if new_builders < existing_pending:
- return defer.succeed(None)
-
- # reset the list of pending builders
- @defer.inlineCallbacks
- def resetPendingBuildersList(new_builders):
- try:
- # re-fetch existing_pending, in case it has changed
- # while acquiring the lock
- existing_pending = set(self._pending_builders)
-
- # then sort the new, expanded set of builders
- self._pending_builders = \
- yield self._sortBuilders(
- list(existing_pending | new_builders))
-
- # start the activity loop, if we aren't already
- # working on that.
- if not self.active:
- self._activityLoop()
- except Exception:
- log.err(Failure(),
- "while attempting to start builds on %s" % self.name)
-
- return self.pending_builders_lock.run(
- resetPendingBuildersList, new_builders)
-
- @defer.inlineCallbacks
- def _defaultSorter(self, master, builders):
- timer = metrics.Timer("BuildRequestDistributor._defaultSorter()")
- timer.start()
- # perform an asynchronous schwarzian transform, transforming None
- # into sys.maxint so that it sorts to the end
- def xform(bldr):
- d = defer.maybeDeferred(lambda :
- bldr.getOldestRequestTime())
- d.addCallback(lambda time :
- (((time is None) and None or time),bldr))
- return d
- xformed = yield defer.gatherResults(
- [ xform(bldr) for bldr in builders ])
-
- # sort the transformed list synchronously, comparing None to the end of
- # the list
- def nonecmp(a,b):
- if a[0] is None: return 1
- if b[0] is None: return -1
- return cmp(a,b)
- xformed.sort(cmp=nonecmp)
-
- # and reverse the transform
- rv = [ xf[1] for xf in xformed ]
- timer.stop()
- defer.returnValue(rv)
-
- @defer.inlineCallbacks
- def _sortBuilders(self, buildernames):
- timer = metrics.Timer("BuildRequestDistributor._sortBuilders()")
- timer.start()
- # note that this takes and returns a list of builder names
-
- # convert builder names to builders
- builders_dict = self.botmaster.builders
- builders = [ builders_dict.get(n)
- for n in buildernames
- if n in builders_dict ]
-
- # find a sorting function
- sorter = self.master.config.prioritizeBuilders
- if not sorter:
- sorter = self._defaultSorter
-
- # run it
- try:
- builders = yield defer.maybeDeferred(lambda :
- sorter(self.master, builders))
- except Exception:
- log.err(Failure(), "prioritizing builders; order unspecified")
-
- # and return the names
- rv = [ b.name for b in builders ]
- timer.stop()
- defer.returnValue(rv)
-
- @defer.inlineCallbacks
- def _activityLoop(self):
- self.active = True
-
- timer = metrics.Timer('BuildRequestDistributor._activityLoop()')
- timer.start()
-
- while 1:
- yield self.activity_lock.acquire()
-
- # lock pending_builders, pop an element from it, and release
- yield self.pending_builders_lock.acquire()
-
- # bail out if we shouldn't keep looping
- if not self.running or not self._pending_builders:
- self.pending_builders_lock.release()
- self.activity_lock.release()
- break
-
- bldr_name = self._pending_builders.pop(0)
- self.pending_builders_lock.release()
-
- # get the actual builder object
- bldr = self.botmaster.builders.get(bldr_name)
- try:
- if bldr:
- yield self._maybeStartBuildsOnBuilder(bldr)
- except Exception:
- log.err(Failure(),
- "from maybeStartBuild for builder '%s'" % (bldr_name,))
-
- self.activity_lock.release()
-
- timer.stop()
-
- self.active = False
- self._quiet()
-
- @defer.inlineCallbacks
- def _maybeStartBuildsOnBuilder(self, bldr):
- # create a chooser to give us our next builds
- # this object is temporary and will go away when we're done
-
- bc = self.createBuildChooser(bldr, self.master)
-
- while 1:
- slave, breqs = yield bc.chooseNextBuild()
- if not slave or not breqs:
- break
-
- # claim brid's
- brids = [ br.id for br in breqs ]
- try:
- yield self.master.db.buildrequests.claimBuildRequests(brids)
- except AlreadyClaimedError:
- # some brids were already claimed, so start over
- bc = self.createBuildChooser(bldr, self.master)
- continue
-
- buildStarted = yield bldr.maybeStartBuild(slave, breqs)
-
- if not buildStarted:
- yield self.master.db.buildrequests.unclaimBuildRequests(brids)
-
- # and try starting builds again. If we still have a working slave,
- # then this may re-claim the same buildrequests
- self.botmaster.maybeStartBuildsForBuilder(self.name)
-
- def createBuildChooser(self, bldr, master):
- # just instantiate the build chooser requested
- return self.BuildChooser(bldr, master)
-
- def _quiet(self):
- # shim for tests
- pass # pragma: no cover