[Buildbot-devel] [PATCH 1/4] use BuildSlave objects in configuration
Dustin J. Mitchell
dustin at zmanda.com
Fri Jun 22 18:03:28 UTC 2007
2007-06-22 Dustin J. Mitchell <dustin at zmanda.com>
This patch changes the bot configuration to use objects instead of
tuples. This will allow us to move some bot-specific functionality
into BuildSlave, and also allow users to subclass BuildSlave for their
own purposes.
* buildbot/buildslave.py: add new BuildSlave class, suitable for use
in master.cfg
* buildbot/master.py: use the BotPerspective subclass, BuildSlave,
as a configuration source; support consumption of successors' souls
as a reconfiguration technique
* docs/buildbot.texinfo buildbot/scripts/sample.cfg: documentation
Index: wip/buildbot/buildslave.py
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ wip/buildbot/buildslave.py 2007-06-22 12:33:51.731773807 -0500
@@ -0,0 +1,29 @@
+import string
+
+from twisted.python import log
+
+from buildbot.master import BotPerspective
+
+class BuildSlave(BotPerspective):
+ """I represent a build slave -- a connection from a remote machine capable of
+ running builds. I am instantiated by the configuration file, and can be
+ subclassed to add extra functionality."""
+
+ def __init__(self, name, password):
+ BotPerspective.__init__(self, name)
+ self.password = password
+
+ def consumeTheSoulOfYourSuccessor(self, new):
+ """
+ Given a new BuildSlave, configure this one identically. Because
+ BuildSlave objects are remotely referenced, we can't replace them
+ without disconnecting the slave, yet there's no reason to do that.
+ """
+ BotPerspective.consumeTheSoulOfYourSuccessor(new)
+ self.password = new.password
+
+ def __repr__(self):
+ builders = self.botmaster.getBuildersForSlave(self.slavename)
+ return "<BuildSlave '%s', current builders: %s>" % \
+ (self.slavename,
+ string.join(map(lambda b: b.name, builders), ','))
Index: wip/buildbot/master.py
===================================================================
--- wip.orig/buildbot/master.py 2007-06-22 12:30:25.602283243 -0500
+++ wip/buildbot/master.py 2007-06-22 12:39:25.493196716 -0500
@@ -42,13 +42,21 @@
reference to this instance. The BotMaster object is stashed as the
.service attribute."""
- def __init__(self, name, botmaster):
+ def __init__(self, name):
self.slavename = name
- self.botmaster = botmaster
+ self.botmaster = None # as-yet unowned
self.slave_status = SlaveStatus(name)
self.slave = None # a RemoteReference to the Bot, when connected
self.slave_commands = None
+ def consumeTheSoulOfYourSuccessor(self, new):
+ # the reconfiguration logic should guarantee this:
+ assert self.slavename == new.slavename
+
+ def setBotmaster(self, botmaster):
+ assert not self.botmaster, "BotPerspective already has a botmaster"
+ self.botmaster = botmaster
+
def updateSlave(self):
"""Called to add or remove builders after the slave has connected.
@@ -301,9 +309,9 @@
return defer.succeed(None)
- def addSlave(self, slavename):
- slave = BotPerspective(slavename, self)
- self.slaves[slavename] = slave
+ def addSlave(self, slave):
+ slave.setBotmaster(self)
+ self.slaves[slave.slavename] = slave
def removeSlave(self, slavename):
d = self.slaves[slavename].disconnect()
@@ -530,7 +538,7 @@
self.statusTargets = []
- self.bots = []
+ self.bots = {}
# 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.
@@ -678,9 +686,14 @@
log.msg("leaving old configuration in place")
raise
+ # keep compatibility with old 'bots' format
+ if type(bots[0]) == type(()):
+ from buildbot.buildslave import BuildSlave # work around circular import
+ bots = [ BuildSlave(name, pw) for name, pw in bots ]
+
# do some validation first
- for name, passwd in bots:
- if name in ("debug", "change", "status"):
+ for bot in bots:
+ if bot.slavename in ("debug", "change", "status"):
raise KeyError, "reserved name '%s' used for a bot" % name
if config.has_key('interlocks'):
raise KeyError("c['interlocks'] is no longer accepted")
@@ -698,7 +711,7 @@
for s in status:
assert interfaces.IStatusReceiver(s, None)
- slavenames = [name for name,pw in bots]
+ slavenames = [ bot.slavename for bot in bots ]
buildernames = []
dirnames = []
for b in builders:
@@ -848,19 +861,32 @@
def loadConfig_Slaves(self, bots):
# set up the Checker with the names and passwords of all valid bots
+
+ # update bot authentication
self.checker.users = {} # violates abstraction, oh well
- for user, passwd in bots:
- self.checker.addUser(user, passwd)
+ for bot in bots:
+ self.checker.addUser(bot.slavename, bot.password)
self.checker.addUser("change", "changepw")
# identify new/old bots
- old = self.bots; oldnames = [name for name,pw in old]
- new = bots; newnames = [name for name,pw in new]
+ old = self.bots; oldnames = [ bot.slavename for bot in old ]
+ new = bots; newnames = [ bot.slavename for bot in new ]
+
# removeSlave will hang up on the old bot
- dl = [self.botmaster.removeSlave(name)
- for name in oldnames if name not in newnames]
- [self.botmaster.addSlave(name)
- for name in newnames if name not in oldnames]
+ dl = [self.botmaster.removeSlave(bot)
+ for bot in old if bot.slavename not in newnames]
+
+ # add all of the new bots
+ [self.botmaster.addSlave(bot)
+ for bot in new if bot.slavename not in oldnames]
+
+ # and reconfigure any existing bots, and slot the *old*
+ # object into 'bots'.
+ for oldbot in old:
+ for newbot in new:
+ if oldbot.slavename == newbot.slavename:
+ oldbot.consumeTheSoulOfYourSuccessor(newbot)
+ bots[bots.index(newbot)] = oldbot
# all done
self.bots = bots
Index: wip/buildbot/scripts/sample.cfg
===================================================================
--- wip.orig/buildbot/scripts/sample.cfg 2007-06-22 12:30:25.602283243 -0500
+++ wip/buildbot/scripts/sample.cfg 2007-06-22 12:30:27.762215128 -0500
@@ -17,9 +17,14 @@
####### BUILDSLAVES
# the 'bots' list defines the set of allowable buildslaves. Each element is a
-# tuple of bot-name and bot-password. These correspond to values given to the
-# buildslave's mktap invocation.
-c['bots'] = [("bot1name", "bot1passwd")]
+# BuildSlave object containing the bot-name and bot-password. These correspond to
+# values given to the buildslave's mktap invocation. A subclass can be used here
+# to allow further bot-specific configuration.
+from buildbot.buildslave import BuildSlave
+c['bots'] = []
+c['bots'].append(
+ BuildSlave("bot1name", "bot1passwd")
+)
# 'slavePortnum' defines the TCP port to listen on. This must match the value
Index: wip/docs/buildbot.texinfo
===================================================================
--- wip.orig/docs/buildbot.texinfo 2007-06-22 12:30:25.602283243 -0500
+++ wip/docs/buildbot.texinfo 2007-06-22 12:30:27.762215128 -0500
@@ -2214,14 +2214,16 @@
@bcindex c['bots']
The @code{c['bots']} key is a list of known buildslaves. Each
-buildslave is defined by a tuple of (slavename, slavepassword). These
+buildslave is defined by a @code{BuildSlave} instance. The
+ at code{BuildSlave} constructor requires a botname and password. These
are the same two values that need to be provided to the buildslave
administrator when they create the buildslave.
@example
-c['bots'] = [('bot-solaris', 'solarispasswd'),
- ('bot-bsd', 'bsdpasswd'),
- ]
+from buildbot.buildslave import BuildSlave
+c['bots'] = []
+c['bots'].append(BuildSlave("bot-solaris", "solarispasswd"))
+c['bots'].append(BuildSlave("bot-bsd", "bsdpasswd"))
@end example
The slavenames must be unique, of course. The password exists to
--
Dustin J. Mitchell
Storage Software Engineer, Zmanda, Inc.
http://www.zmanda.com/
More information about the devel
mailing list