Index: third_party/buildbot_7_12/buildbot/master.py |
diff --git a/third_party/buildbot_7_12/buildbot/master.py b/third_party/buildbot_7_12/buildbot/master.py |
deleted file mode 100644 |
index 1e09e4929d273b83bf60b9b89712e930ff86ce09..0000000000000000000000000000000000000000 |
--- a/third_party/buildbot_7_12/buildbot/master.py |
+++ /dev/null |
@@ -1,1029 +0,0 @@ |
-# -*- test-case-name: buildbot.test.test_run -*- |
- |
-import os |
-signal = None |
-try: |
- import signal |
-except ImportError: |
- pass |
-from cPickle import load |
-import warnings |
- |
-from zope.interface import implements |
-from twisted.python import log, components |
-from twisted.python.failure import Failure |
-from twisted.internet import defer, reactor |
-from twisted.spread import pb |
-from twisted.cred import portal, checkers |
-from twisted.application import service, strports |
-from twisted.persisted import styles |
- |
-import buildbot |
-# sibling imports |
-from buildbot.util import now, safeTranslate |
-from buildbot.pbutil import NewCredPerspective |
-from buildbot.process.builder import Builder, IDLE |
-from buildbot.process.base import BuildRequest |
-from buildbot.status.builder import Status |
-from buildbot.changes.changes import Change, ChangeMaster, TestChangeMaster |
-from buildbot.sourcestamp import SourceStamp |
-from buildbot.buildslave import BuildSlave |
-from buildbot import interfaces, locks |
-from buildbot.process.properties import Properties |
-from buildbot.config import BuilderConfig |
- |
-######################################## |
- |
-class BotMaster(service.MultiService): |
- |
- """This is the master-side service which manages remote buildbot slaves. |
- It provides them with BuildSlaves, and distributes file change |
- notification messages to them. |
- """ |
- |
- debug = 0 |
- |
- def __init__(self): |
- service.MultiService.__init__(self) |
- self.builders = {} |
- self.builderNames = [] |
- # builders maps Builder names to instances of bb.p.builder.Builder, |
- # which is the master-side object that defines and controls a build. |
- # They are added by calling botmaster.addBuilder() from the startup |
- # code. |
- |
- # self.slaves contains a ready BuildSlave instance for each |
- # potential buildslave, i.e. all the ones listed in the config file. |
- # If the slave is connected, self.slaves[slavename].slave will |
- # contain a RemoteReference to their Bot instance. If it is not |
- # connected, that attribute will hold None. |
- self.slaves = {} # maps slavename to BuildSlave |
- self.statusClientService = None |
- self.watchers = {} |
- |
- # self.locks holds the real Lock instances |
- self.locks = {} |
- |
- # self.mergeRequests is the callable override for merging build |
- # requests |
- self.mergeRequests = None |
- |
- # self.prioritizeBuilders is the callable override for builder order |
- # traversal |
- self.prioritizeBuilders = None |
- |
- # these four are convenience functions for testing |
- |
- def waitUntilBuilderAttached(self, name): |
- b = self.builders[name] |
- #if b.slaves: |
- # return defer.succeed(None) |
- d = defer.Deferred() |
- b.watchers['attach'].append(d) |
- return d |
- |
- def waitUntilBuilderDetached(self, name): |
- b = self.builders.get(name) |
- if not b or not b.slaves: |
- return defer.succeed(None) |
- d = defer.Deferred() |
- b.watchers['detach'].append(d) |
- return d |
- |
- def waitUntilBuilderFullyDetached(self, name): |
- b = self.builders.get(name) |
- # TODO: this looks too deeply inside the Builder object |
- if not b or not b.slaves: |
- return defer.succeed(None) |
- d = defer.Deferred() |
- b.watchers['detach_all'].append(d) |
- return d |
- |
- def waitUntilBuilderIdle(self, name): |
- b = self.builders[name] |
- # TODO: this looks way too deeply inside the Builder object |
- for sb in b.slaves: |
- if sb.state != IDLE: |
- d = defer.Deferred() |
- b.watchers['idle'].append(d) |
- return d |
- return defer.succeed(None) |
- |
- def loadConfig_Slaves(self, new_slaves): |
- old_slaves = [c for c in list(self) |
- if interfaces.IBuildSlave.providedBy(c)] |
- |
- # identify added/removed slaves. For each slave we construct a tuple |
- # of (name, password, class), and we consider the slave to be already |
- # present if the tuples match. (we include the class to make sure |
- # that BuildSlave(name,pw) is different than |
- # SubclassOfBuildSlave(name,pw) ). If the password or class has |
- # changed, we will remove the old version of the slave and replace it |
- # with a new one. If anything else has changed, we just update the |
- # old BuildSlave instance in place. If the name has changed, of |
- # course, it looks exactly the same as deleting one slave and adding |
- # an unrelated one. |
- old_t = {} |
- for s in old_slaves: |
- old_t[(s.slavename, s.password, s.__class__)] = s |
- new_t = {} |
- for s in new_slaves: |
- new_t[(s.slavename, s.password, s.__class__)] = s |
- removed = [old_t[t] |
- for t in old_t |
- if t not in new_t] |
- added = [new_t[t] |
- for t in new_t |
- if t not in old_t] |
- remaining_t = [t |
- for t in new_t |
- if t in old_t] |
- # removeSlave will hang up on the old bot |
- dl = [] |
- for s in removed: |
- dl.append(self.removeSlave(s)) |
- d = defer.DeferredList(dl, fireOnOneErrback=True) |
- def _add(res): |
- for s in added: |
- self.addSlave(s) |
- for t in remaining_t: |
- old_t[t].update(new_t[t]) |
- d.addCallback(_add) |
- return d |
- |
- def addSlave(self, s): |
- s.setServiceParent(self) |
- s.setBotmaster(self) |
- self.slaves[s.slavename] = s |
- |
- def removeSlave(self, s): |
- # TODO: technically, disownServiceParent could return a Deferred |
- s.disownServiceParent() |
- d = self.slaves[s.slavename].disconnect() |
- del self.slaves[s.slavename] |
- return d |
- |
- def slaveLost(self, bot): |
- for name, b in self.builders.items(): |
- if bot.slavename in b.slavenames: |
- b.detached(bot) |
- |
- def getBuildersForSlave(self, slavename): |
- return [b |
- for b in self.builders.values() |
- if slavename in b.slavenames] |
- |
- def getBuildernames(self): |
- return self.builderNames |
- |
- def getBuilders(self): |
- allBuilders = [self.builders[name] for name in self.builderNames] |
- return allBuilders |
- |
- def setBuilders(self, builders): |
- self.builders = {} |
- self.builderNames = [] |
- for b in builders: |
- for slavename in b.slavenames: |
- # this is actually validated earlier |
- assert slavename in self.slaves |
- self.builders[b.name] = b |
- self.builderNames.append(b.name) |
- b.setBotmaster(self) |
- d = self._updateAllSlaves() |
- return d |
- |
- def _updateAllSlaves(self): |
- """Notify all buildslaves about changes in their Builders.""" |
- dl = [s.updateSlave() for s in self.slaves.values()] |
- return defer.DeferredList(dl) |
- |
- def maybeStartAllBuilds(self): |
- builders = self.builders.values() |
- if self.prioritizeBuilders is not None: |
- try: |
- builders = self.prioritizeBuilders(self.parent, builders) |
- except: |
- log.msg("Exception prioritizing builders") |
- log.err(Failure()) |
- return |
- else: |
- def _sortfunc(b1, b2): |
- t1 = b1.getOldestRequestTime() |
- t2 = b2.getOldestRequestTime() |
- # If t1 or t2 is None, then there are no build requests, |
- # so sort it at the end |
- if t1 is None: |
- return 1 |
- if t2 is None: |
- return -1 |
- return cmp(t1, t2) |
- builders.sort(_sortfunc) |
- try: |
- for b in builders: |
- b.maybeStartBuild() |
- except: |
- log.msg("Exception starting builds") |
- log.err(Failure()) |
- |
- def shouldMergeRequests(self, builder, req1, req2): |
- """Determine whether two BuildRequests should be merged for |
- the given builder. |
- |
- """ |
- if self.mergeRequests is not None: |
- return self.mergeRequests(builder, req1, req2) |
- return req1.canBeMergedWith(req2) |
- |
- def getPerspective(self, slavename): |
- return self.slaves[slavename] |
- |
- def shutdownSlaves(self): |
- # TODO: make this into a bot method rather than a builder method |
- for b in self.slaves.values(): |
- b.shutdownSlave() |
- |
- def stopService(self): |
- for b in self.builders.values(): |
- b.builder_status.addPointEvent(["master", "shutdown"]) |
- b.builder_status.saveYourself() |
- return service.Service.stopService(self) |
- |
- def getLockByID(self, lockid): |
- """Convert a Lock identifier into an actual Lock instance. |
- @param lockid: a locks.MasterLock or locks.SlaveLock instance |
- @return: a locks.RealMasterLock or locks.RealSlaveLock instance |
- """ |
- assert isinstance(lockid, (locks.MasterLock, locks.SlaveLock)) |
- if not lockid in self.locks: |
- self.locks[lockid] = lockid.lockClass(lockid) |
- # if the master.cfg file has changed maxCount= on the lock, the next |
- # time a build is started, they'll get a new RealLock instance. Note |
- # that this requires that MasterLock and SlaveLock (marker) instances |
- # be hashable and that they should compare properly. |
- return self.locks[lockid] |
- |
-######################################## |
- |
- |
- |
-class DebugPerspective(NewCredPerspective): |
- def attached(self, mind): |
- return self |
- def detached(self, mind): |
- pass |
- |
- def perspective_requestBuild(self, buildername, reason, branch, revision, properties={}): |
- c = interfaces.IControl(self.master) |
- bc = c.getBuilder(buildername) |
- ss = SourceStamp(branch, revision) |
- bpr = Properties() |
- bpr.update(properties, "remote requestBuild") |
- br = BuildRequest(reason, ss, builderName=buildername, properties=bpr) |
- bc.requestBuild(br) |
- |
- def perspective_pingBuilder(self, buildername): |
- c = interfaces.IControl(self.master) |
- bc = c.getBuilder(buildername) |
- bc.ping() |
- |
- def perspective_fakeChange(self, file, revision=None, who="fakeUser", |
- branch=None): |
- change = Change(who, [file], "some fake comments\n", |
- branch=branch, revision=revision) |
- c = interfaces.IControl(self.master) |
- c.addChange(change) |
- |
- def perspective_setCurrentState(self, buildername, state): |
- builder = self.botmaster.builders.get(buildername) |
- if not builder: return |
- if state == "offline": |
- builder.statusbag.currentlyOffline() |
- if state == "idle": |
- builder.statusbag.currentlyIdle() |
- if state == "waiting": |
- builder.statusbag.currentlyWaiting(now()+10) |
- if state == "building": |
- builder.statusbag.currentlyBuilding(None) |
- def perspective_reload(self): |
- print "doing reload of the config file" |
- self.master.loadTheConfigFile() |
- def perspective_pokeIRC(self): |
- print "saying something on IRC" |
- from buildbot.status import words |
- for s in self.master: |
- if isinstance(s, words.IRC): |
- bot = s.f |
- for channel in bot.channels: |
- print " channel", channel |
- bot.p.msg(channel, "Ow, quit it") |
- |
- def perspective_print(self, msg): |
- print "debug", msg |
- |
-class Dispatcher: |
- implements(portal.IRealm) |
- |
- def __init__(self): |
- self.names = {} |
- |
- def register(self, name, afactory): |
- self.names[name] = afactory |
- def unregister(self, name): |
- del self.names[name] |
- |
- def requestAvatar(self, avatarID, mind, interface): |
- assert interface == pb.IPerspective |
- afactory = self.names.get(avatarID) |
- if afactory: |
- p = afactory.getPerspective() |
- elif avatarID == "debug": |
- p = DebugPerspective() |
- p.master = self.master |
- p.botmaster = self.botmaster |
- elif avatarID == "statusClient": |
- p = self.statusClientService.getPerspective() |
- else: |
- # it must be one of the buildslaves: no other names will make it |
- # past the checker |
- p = self.botmaster.getPerspective(avatarID) |
- |
- if not p: |
- raise ValueError("no perspective for '%s'" % avatarID) |
- |
- d = defer.maybeDeferred(p.attached, mind) |
- d.addCallback(self._avatarAttached, mind) |
- return d |
- |
- def _avatarAttached(self, p, mind): |
- return (pb.IPerspective, p, lambda p=p,mind=mind: p.detached(mind)) |
- |
-######################################## |
- |
-# service hierarchy: |
-# BuildMaster |
-# BotMaster |
-# ChangeMaster |
-# all IChangeSource objects |
-# StatusClientService |
-# TCPClient(self.ircFactory) |
-# TCPServer(self.slaveFactory) -> dispatcher.requestAvatar |
-# TCPServer(self.site) |
-# UNIXServer(ResourcePublisher(self.site)) |
- |
- |
-class BuildMaster(service.MultiService): |
- debug = 0 |
- manhole = None |
- debugPassword = None |
- projectName = "(unspecified)" |
- projectURL = None |
- buildbotURL = None |
- change_svc = None |
- properties = Properties() |
- |
- def __init__(self, basedir, configFileName="master.cfg"): |
- service.MultiService.__init__(self) |
- self.setName("buildmaster") |
- self.basedir = basedir |
- self.configFileName = configFileName |
- |
- # the dispatcher is the realm in which all inbound connections are |
- # looked up: slave builders, change notifications, status clients, and |
- # the debug port |
- dispatcher = Dispatcher() |
- dispatcher.master = self |
- self.dispatcher = dispatcher |
- self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse() |
- # the checker starts with no user/passwd pairs: they are added later |
- p = portal.Portal(dispatcher) |
- p.registerChecker(self.checker) |
- self.slaveFactory = pb.PBServerFactory(p) |
- self.slaveFactory.unsafeTracebacks = True # let them see exceptions |
- |
- self.slavePortnum = None |
- self.slavePort = None |
- |
- self.botmaster = BotMaster() |
- self.botmaster.setName("botmaster") |
- self.botmaster.setServiceParent(self) |
- dispatcher.botmaster = self.botmaster |
- |
- self.status = Status(self.botmaster, self.basedir) |
- |
- self.statusTargets = [] |
- |
- # this ChangeMaster is a dummy, only used by tests. In the real |
- # buildmaster, where the BuildMaster instance is activated |
- # (startService is called) by twistd, this attribute is overwritten. |
- self.useChanges(TestChangeMaster()) |
- |
- self.readConfig = False |
- |
- def startService(self): |
- service.MultiService.startService(self) |
- self.loadChanges() # must be done before loading the config file |
- if not self.readConfig: |
- # TODO: consider catching exceptions during this call to |
- # loadTheConfigFile and bailing (reactor.stop) if it fails, |
- # since without a config file we can't do anything except reload |
- # the config file, and it would be nice for the user to discover |
- # this quickly. |
- self.loadTheConfigFile() |
- if signal and hasattr(signal, "SIGHUP"): |
- signal.signal(signal.SIGHUP, self._handleSIGHUP) |
- for b in self.botmaster.builders.values(): |
- b.builder_status.addPointEvent(["master", "started"]) |
- b.builder_status.saveYourself() |
- |
- def useChanges(self, changes): |
- if self.change_svc: |
- # TODO: can return a Deferred |
- self.change_svc.disownServiceParent() |
- self.change_svc = changes |
- self.change_svc.basedir = self.basedir |
- self.change_svc.setName("changemaster") |
- self.dispatcher.changemaster = self.change_svc |
- self.change_svc.setServiceParent(self) |
- |
- def loadChanges(self): |
- filename = os.path.join(self.basedir, "changes.pck") |
- try: |
- changes = load(open(filename, "rb")) |
- styles.doUpgrade() |
- except IOError: |
- log.msg("changes.pck missing, using new one") |
- changes = ChangeMaster() |
- except EOFError: |
- log.msg("corrupted changes.pck, using new one") |
- changes = ChangeMaster() |
- self.useChanges(changes) |
- |
- def _handleSIGHUP(self, *args): |
- reactor.callLater(0, self.loadTheConfigFile) |
- |
- def getStatus(self): |
- """ |
- @rtype: L{buildbot.status.builder.Status} |
- """ |
- return self.status |
- |
- def loadTheConfigFile(self, configFile=None): |
- if not configFile: |
- configFile = os.path.join(self.basedir, self.configFileName) |
- |
- log.msg("Creating BuildMaster -- buildbot.version: %s" % buildbot.version) |
- log.msg("loading configuration from %s" % configFile) |
- configFile = os.path.expanduser(configFile) |
- |
- try: |
- f = open(configFile, "r") |
- except IOError, e: |
- log.msg("unable to open config file '%s'" % configFile) |
- log.msg("leaving old configuration in place") |
- log.err(e) |
- return |
- |
- try: |
- self.loadConfig(f) |
- except: |
- log.msg("error during loadConfig") |
- log.err() |
- log.msg("The new config file is unusable, so I'll ignore it.") |
- log.msg("I will keep using the previous config file instead.") |
- f.close() |
- |
- def loadConfig(self, f): |
- """Internal function to load a specific configuration file. Any |
- errors in the file will be signalled by raising an exception. |
- |
- @return: a Deferred that will fire (with None) when the configuration |
- changes have been completed. This may involve a round-trip to each |
- buildslave that was involved.""" |
- |
- localDict = {'basedir': os.path.expanduser(self.basedir)} |
- try: |
- exec f in localDict |
- except: |
- log.msg("error while parsing config file") |
- raise |
- |
- try: |
- config = localDict['BuildmasterConfig'] |
- except KeyError: |
- log.err("missing config dictionary") |
- log.err("config file must define BuildmasterConfig") |
- raise |
- |
- known_keys = ("bots", "slaves", |
- "sources", "change_source", |
- "schedulers", "builders", "mergeRequests", |
- "slavePortnum", "debugPassword", "logCompressionLimit", |
- "manhole", "status", "projectName", "projectURL", |
- "buildbotURL", "properties", "prioritizeBuilders", |
- "eventHorizon", "buildCacheSize", "logHorizon", "buildHorizon", |
- "changeHorizon", "logMaxSize", "logMaxTailSize", |
- "logCompressionMethod", |
- ) |
- for k in config.keys(): |
- if k not in known_keys: |
- log.msg("unknown key '%s' defined in config dictionary" % k) |
- |
- try: |
- # required |
- schedulers = config['schedulers'] |
- builders = config['builders'] |
- slavePortnum = config['slavePortnum'] |
- #slaves = config['slaves'] |
- #change_source = config['change_source'] |
- |
- # optional |
- debugPassword = config.get('debugPassword') |
- manhole = config.get('manhole') |
- status = config.get('status', []) |
- projectName = config.get('projectName') |
- projectURL = config.get('projectURL') |
- buildbotURL = config.get('buildbotURL') |
- properties = config.get('properties', {}) |
- buildCacheSize = config.get('buildCacheSize', None) |
- eventHorizon = config.get('eventHorizon', None) |
- logHorizon = config.get('logHorizon', None) |
- buildHorizon = config.get('buildHorizon', None) |
- logCompressionLimit = config.get('logCompressionLimit', 4*1024) |
- if logCompressionLimit is not None and not \ |
- isinstance(logCompressionLimit, int): |
- raise ValueError("logCompressionLimit needs to be bool or int") |
- logCompressionMethod = config.get('logCompressionMethod', "bz2") |
- if logCompressionMethod not in ('bz2', 'gz'): |
- raise ValueError("logCompressionMethod needs to be 'bz2', or 'gz'") |
- logMaxSize = config.get('logMaxSize') |
- if logMaxSize is not None and not \ |
- isinstance(logMaxSize, int): |
- raise ValueError("logMaxSize needs to be None or int") |
- logMaxTailSize = config.get('logMaxTailSize') |
- if logMaxTailSize is not None and not \ |
- isinstance(logMaxTailSize, int): |
- raise ValueError("logMaxTailSize needs to be None or int") |
- mergeRequests = config.get('mergeRequests') |
- if mergeRequests is not None and not callable(mergeRequests): |
- raise ValueError("mergeRequests must be a callable") |
- prioritizeBuilders = config.get('prioritizeBuilders') |
- if prioritizeBuilders is not None and not callable(prioritizeBuilders): |
- raise ValueError("prioritizeBuilders must be callable") |
- changeHorizon = config.get("changeHorizon") |
- if changeHorizon is not None and not isinstance(changeHorizon, int): |
- raise ValueError("changeHorizon needs to be an int") |
- |
- except KeyError, e: |
- log.msg("config dictionary is missing a required parameter") |
- log.msg("leaving old configuration in place") |
- raise |
- |
- #if "bots" in config: |
- # raise KeyError("c['bots'] is no longer accepted") |
- |
- slaves = config.get('slaves', []) |
- if "bots" in config: |
- m = ("c['bots'] is deprecated as of 0.7.6 and will be " |
- "removed by 0.8.0 . Please use c['slaves'] instead.") |
- log.msg(m) |
- warnings.warn(m, DeprecationWarning) |
- for name, passwd in config['bots']: |
- slaves.append(BuildSlave(name, passwd)) |
- |
- if "bots" not in config and "slaves" not in config: |
- log.msg("config dictionary must have either 'bots' or 'slaves'") |
- log.msg("leaving old configuration in place") |
- raise KeyError("must have either 'bots' or 'slaves'") |
- |
- #if "sources" in config: |
- # raise KeyError("c['sources'] is no longer accepted") |
- |
- if changeHorizon is not None: |
- self.change_svc.changeHorizon = changeHorizon |
- |
- change_source = config.get('change_source', []) |
- if isinstance(change_source, (list, tuple)): |
- change_sources = change_source |
- else: |
- change_sources = [change_source] |
- if "sources" in config: |
- m = ("c['sources'] is deprecated as of 0.7.6 and will be " |
- "removed by 0.8.0 . Please use c['change_source'] instead.") |
- log.msg(m) |
- warnings.warn(m, DeprecationWarning) |
- for s in config['sources']: |
- change_sources.append(s) |
- |
- # do some validation first |
- for s in slaves: |
- assert interfaces.IBuildSlave.providedBy(s) |
- if s.slavename in ("debug", "change", "status"): |
- raise KeyError( |
- "reserved name '%s' used for a bot" % s.slavename) |
- if config.has_key('interlocks'): |
- raise KeyError("c['interlocks'] is no longer accepted") |
- |
- assert isinstance(change_sources, (list, tuple)) |
- for s in change_sources: |
- assert interfaces.IChangeSource(s, None) |
- # this assertion catches c['schedulers'] = Scheduler(), since |
- # Schedulers are service.MultiServices and thus iterable. |
- errmsg = "c['schedulers'] must be a list of Scheduler instances" |
- assert isinstance(schedulers, (list, tuple)), errmsg |
- for s in schedulers: |
- assert interfaces.IScheduler(s, None), errmsg |
- assert isinstance(status, (list, tuple)) |
- for s in status: |
- assert interfaces.IStatusReceiver(s, None) |
- |
- slavenames = [s.slavename for s in slaves] |
- buildernames = [] |
- dirnames = [] |
- |
- # convert builders from objects to config dictionaries |
- builders_dicts = [] |
- for b in builders: |
- if isinstance(b, BuilderConfig): |
- builders_dicts.append(b.getConfigDict()) |
- elif type(b) is dict: |
- builders_dicts.append(b) |
- else: |
- raise ValueError("builder %s is not a BuilderConfig object (or a dict)" % b) |
- builders = builders_dicts |
- |
- for b in builders: |
- if b.has_key('slavename') and b['slavename'] not in slavenames: |
- raise ValueError("builder %s uses undefined slave %s" \ |
- % (b['name'], b['slavename'])) |
- for n in b.get('slavenames', []): |
- if n not in slavenames: |
- raise ValueError("builder %s uses undefined slave %s" \ |
- % (b['name'], n)) |
- if b['name'] in buildernames: |
- raise ValueError("duplicate builder name %s" |
- % b['name']) |
- buildernames.append(b['name']) |
- |
- # sanity check name (BuilderConfig does this too) |
- if b['name'].startswith("_"): |
- errmsg = ("builder names must not start with an " |
- "underscore: " + b['name']) |
- log.err(errmsg) |
- raise ValueError(errmsg) |
- |
- # Fix the dictionnary with default values, in case this wasn't |
- # specified with a BuilderConfig object (which sets the same defaults) |
- b.setdefault('builddir', safeTranslate(b['name'])) |
- b.setdefault('slavebuilddir', b['builddir']) |
- |
- if b['builddir'] in dirnames: |
- raise ValueError("builder %s reuses builddir %s" |
- % (b['name'], b['builddir'])) |
- dirnames.append(b['builddir']) |
- |
- unscheduled_buildernames = buildernames[:] |
- schedulernames = [] |
- for s in schedulers: |
- for b in s.listBuilderNames(): |
- assert b in buildernames, \ |
- "%s uses unknown builder %s" % (s, b) |
- if b in unscheduled_buildernames: |
- unscheduled_buildernames.remove(b) |
- |
- if s.name in schedulernames: |
- # TODO: schedulers share a namespace with other Service |
- # children of the BuildMaster node, like status plugins, the |
- # Manhole, the ChangeMaster, and the BotMaster (although most |
- # of these don't have names) |
- msg = ("Schedulers must have unique names, but " |
- "'%s' was a duplicate" % (s.name,)) |
- raise ValueError(msg) |
- schedulernames.append(s.name) |
- |
- if unscheduled_buildernames: |
- log.msg("Warning: some Builders have no Schedulers to drive them:" |
- " %s" % (unscheduled_buildernames,)) |
- |
- # assert that all locks used by the Builds and their Steps are |
- # uniquely named. |
- lock_dict = {} |
- for b in builders: |
- for l in b.get('locks', []): |
- if isinstance(l, locks.LockAccess): # User specified access to the lock |
- l = l.lockid |
- if lock_dict.has_key(l.name): |
- if lock_dict[l.name] is not l: |
- raise ValueError("Two different locks (%s and %s) " |
- "share the name %s" |
- % (l, lock_dict[l.name], l.name)) |
- else: |
- lock_dict[l.name] = l |
- # TODO: this will break with any BuildFactory that doesn't use a |
- # .steps list, but I think the verification step is more |
- # important. |
- for s in b['factory'].steps: |
- for l in s[1].get('locks', []): |
- if isinstance(l, locks.LockAccess): # User specified access to the lock |
- l = l.lockid |
- if lock_dict.has_key(l.name): |
- if lock_dict[l.name] is not l: |
- raise ValueError("Two different locks (%s and %s)" |
- " share the name %s" |
- % (l, lock_dict[l.name], l.name)) |
- else: |
- lock_dict[l.name] = l |
- |
- if not isinstance(properties, dict): |
- raise ValueError("c['properties'] must be a dictionary") |
- |
- # slavePortnum supposed to be a strports specification |
- if type(slavePortnum) is int: |
- slavePortnum = "tcp:%d" % slavePortnum |
- |
- # now we're committed to implementing the new configuration, so do |
- # it atomically |
- # TODO: actually, this is spread across a couple of Deferreds, so it |
- # really isn't atomic. |
- |
- d = defer.succeed(None) |
- |
- self.projectName = projectName |
- self.projectURL = projectURL |
- self.buildbotURL = buildbotURL |
- |
- self.properties = Properties() |
- self.properties.update(properties, self.configFileName) |
- |
- self.status.logCompressionLimit = logCompressionLimit |
- self.status.logCompressionMethod = logCompressionMethod |
- self.status.logMaxSize = logMaxSize |
- self.status.logMaxTailSize = logMaxTailSize |
- # Update any of our existing builders with the current log parameters. |
- # This is required so that the new value is picked up after a |
- # reconfig. |
- for builder in self.botmaster.builders.values(): |
- builder.builder_status.setLogCompressionLimit(logCompressionLimit) |
- builder.builder_status.setLogCompressionMethod(logCompressionMethod) |
- builder.builder_status.setLogMaxSize(logMaxSize) |
- builder.builder_status.setLogMaxTailSize(logMaxTailSize) |
- |
- if mergeRequests is not None: |
- self.botmaster.mergeRequests = mergeRequests |
- if prioritizeBuilders is not None: |
- self.botmaster.prioritizeBuilders = prioritizeBuilders |
- |
- self.buildCacheSize = buildCacheSize |
- self.eventHorizon = eventHorizon |
- self.logHorizon = logHorizon |
- self.buildHorizon = buildHorizon |
- |
- # self.slaves: Disconnect any that were attached and removed from the |
- # list. Update self.checker with the new list of passwords, including |
- # debug/change/status. |
- d.addCallback(lambda res: self.loadConfig_Slaves(slaves)) |
- |
- # self.debugPassword |
- if debugPassword: |
- self.checker.addUser("debug", debugPassword) |
- self.debugPassword = debugPassword |
- |
- # self.manhole |
- if manhole != self.manhole: |
- # changing |
- if self.manhole: |
- # disownServiceParent may return a Deferred |
- d.addCallback(lambda res: self.manhole.disownServiceParent()) |
- def _remove(res): |
- self.manhole = None |
- return res |
- d.addCallback(_remove) |
- if manhole: |
- def _add(res): |
- self.manhole = manhole |
- manhole.setServiceParent(self) |
- d.addCallback(_add) |
- |
- # add/remove self.botmaster.builders to match builders. The |
- # botmaster will handle startup/shutdown issues. |
- d.addCallback(lambda res: self.loadConfig_Builders(builders)) |
- |
- d.addCallback(lambda res: self.loadConfig_status(status)) |
- |
- # Schedulers are added after Builders in case they start right away |
- d.addCallback(lambda res: self.loadConfig_Schedulers(schedulers)) |
- # and Sources go after Schedulers for the same reason |
- d.addCallback(lambda res: self.loadConfig_Sources(change_sources)) |
- |
- # self.slavePort |
- if self.slavePortnum != slavePortnum: |
- if self.slavePort: |
- def closeSlavePort(res): |
- d1 = self.slavePort.disownServiceParent() |
- self.slavePort = None |
- return d1 |
- d.addCallback(closeSlavePort) |
- if slavePortnum is not None: |
- def openSlavePort(res): |
- self.slavePort = strports.service(slavePortnum, |
- self.slaveFactory) |
- self.slavePort.setServiceParent(self) |
- d.addCallback(openSlavePort) |
- log.msg("BuildMaster listening on port %s" % slavePortnum) |
- self.slavePortnum = slavePortnum |
- |
- log.msg("configuration update started") |
- def _done(res): |
- self.readConfig = True |
- log.msg("configuration update complete") |
- d.addCallback(_done) |
- d.addCallback(lambda res: self.botmaster.maybeStartAllBuilds()) |
- return d |
- |
- def loadConfig_Slaves(self, new_slaves): |
- # set up the Checker with the names and passwords of all valid bots |
- self.checker.users = {} # violates abstraction, oh well |
- for s in new_slaves: |
- self.checker.addUser(s.slavename, s.password) |
- self.checker.addUser("change", "changepw") |
- # let the BotMaster take care of the rest |
- return self.botmaster.loadConfig_Slaves(new_slaves) |
- |
- def loadConfig_Sources(self, sources): |
- if not sources: |
- log.msg("warning: no ChangeSources specified in c['change_source']") |
- # shut down any that were removed, start any that were added |
- deleted_sources = [s for s in self.change_svc if s not in sources] |
- added_sources = [s for s in sources if s not in self.change_svc] |
- log.msg("adding %d new changesources, removing %d" % |
- (len(added_sources), len(deleted_sources))) |
- dl = [self.change_svc.removeSource(s) for s in deleted_sources] |
- def addNewOnes(res): |
- [self.change_svc.addSource(s) for s in added_sources] |
- d = defer.DeferredList(dl, fireOnOneErrback=1, consumeErrors=0) |
- d.addCallback(addNewOnes) |
- return d |
- |
- def allSchedulers(self): |
- return [child for child in self |
- if interfaces.IScheduler.providedBy(child)] |
- |
- |
- def loadConfig_Schedulers(self, newschedulers): |
- oldschedulers = self.allSchedulers() |
- removed = [s for s in oldschedulers if s not in newschedulers] |
- added = [s for s in newschedulers if s not in oldschedulers] |
- dl = [defer.maybeDeferred(s.disownServiceParent) for s in removed] |
- def addNewOnes(res): |
- log.msg("adding %d new schedulers, removed %d" % |
- (len(added), len(dl))) |
- for s in added: |
- s.setServiceParent(self) |
- d = defer.DeferredList(dl, fireOnOneErrback=1) |
- d.addCallback(addNewOnes) |
- if removed or added: |
- # notify Downstream schedulers to potentially pick up |
- # new schedulers now that we have removed and added some |
- def updateDownstreams(res): |
- log.msg("notifying downstream schedulers of changes") |
- for s in newschedulers: |
- if interfaces.IDownstreamScheduler.providedBy(s): |
- s.checkUpstreamScheduler() |
- d.addCallback(updateDownstreams) |
- return d |
- |
- def loadConfig_Builders(self, newBuilderData): |
- somethingChanged = False |
- newList = {} |
- newBuilderNames = [] |
- allBuilders = self.botmaster.builders.copy() |
- for data in newBuilderData: |
- name = data['name'] |
- newList[name] = data |
- newBuilderNames.append(name) |
- |
- # identify all that were removed |
- for oldname in self.botmaster.getBuildernames(): |
- if oldname not in newList: |
- log.msg("removing old builder %s" % oldname) |
- del allBuilders[oldname] |
- somethingChanged = True |
- # announce the change |
- self.status.builderRemoved(oldname) |
- |
- # everything in newList is either unchanged, changed, or new |
- for name, data in newList.items(): |
- old = self.botmaster.builders.get(name) |
- basedir = data['builddir'] |
- #name, slave, builddir, factory = data |
- if not old: # new |
- # category added after 0.6.2 |
- category = data.get('category', None) |
- log.msg("adding new builder %s for category %s" % |
- (name, category)) |
- statusbag = self.status.builderAdded(name, basedir, category) |
- builder = Builder(data, statusbag) |
- allBuilders[name] = builder |
- somethingChanged = True |
- elif old.compareToSetup(data): |
- # changed: try to minimize the disruption and only modify the |
- # pieces that really changed |
- diffs = old.compareToSetup(data) |
- log.msg("updating builder %s: %s" % (name, "\n".join(diffs))) |
- |
- statusbag = old.builder_status |
- statusbag.saveYourself() # seems like a good idea |
- # TODO: if the basedir was changed, we probably need to make |
- # a new statusbag |
- new_builder = Builder(data, statusbag) |
- new_builder.consumeTheSoulOfYourPredecessor(old) |
- # that migrates any retained slavebuilders too |
- |
- # point out that the builder was updated. On the Waterfall, |
- # this will appear just after any currently-running builds. |
- statusbag.addPointEvent(["config", "updated"]) |
- |
- allBuilders[name] = new_builder |
- somethingChanged = True |
- else: |
- # unchanged: leave it alone |
- log.msg("builder %s is unchanged" % name) |
- pass |
- |
- # regardless of whether anything changed, get each builder status |
- # to update its config |
- for builder in allBuilders.values(): |
- builder.builder_status.reconfigFromBuildmaster(self) |
- |
- # and then tell the botmaster if anything's changed |
- if somethingChanged: |
- sortedAllBuilders = [allBuilders[name] for name in newBuilderNames] |
- d = self.botmaster.setBuilders(sortedAllBuilders) |
- return d |
- return None |
- |
- def loadConfig_status(self, status): |
- dl = [] |
- |
- # remove old ones |
- for s in self.statusTargets[:]: |
- if not s in status: |
- log.msg("removing IStatusReceiver", s) |
- d = defer.maybeDeferred(s.disownServiceParent) |
- dl.append(d) |
- self.statusTargets.remove(s) |
- # after those are finished going away, add new ones |
- def addNewOnes(res): |
- for s in status: |
- if not s in self.statusTargets: |
- log.msg("adding IStatusReceiver", s) |
- s.setServiceParent(self) |
- self.statusTargets.append(s) |
- d = defer.DeferredList(dl, fireOnOneErrback=1) |
- d.addCallback(addNewOnes) |
- return d |
- |
- |
- def addChange(self, change): |
- for s in self.allSchedulers(): |
- s.addChange(change) |
- self.status.changeAdded(change) |
- |
- def submitBuildSet(self, bs): |
- # determine the set of Builders to use |
- builders = [] |
- for name in bs.builderNames: |
- b = self.botmaster.builders.get(name) |
- if b: |
- if b not in builders: |
- builders.append(b) |
- continue |
- # TODO: add aliases like 'all' |
- raise KeyError("no such builder named '%s'" % name) |
- |
- # now tell the BuildSet to create BuildRequests for all those |
- # Builders and submit them |
- bs.start(builders) |
- self.status.buildsetSubmitted(bs.status) |
- |
- |
-class Control: |
- implements(interfaces.IControl) |
- |
- def __init__(self, master): |
- self.master = master |
- |
- def addChange(self, change): |
- self.master.change_svc.addChange(change) |
- |
- def submitBuildSet(self, bs): |
- self.master.submitBuildSet(bs) |
- |
- def getBuilder(self, name): |
- b = self.master.botmaster.builders[name] |
- return interfaces.IBuilderControl(b) |
- |
-components.registerAdapter(Control, BuildMaster, interfaces.IControl) |
- |
-# so anybody who can get a handle on the BuildMaster can cause a build with: |
-# IControl(master).getBuilder("full-2.3").requestBuild(buildrequest) |