1# This file is part of Buildbot. Buildbot is free software: you can 2# redistribute it and/or modify it under the terms of the GNU General Public 3# License as published by the Free Software Foundation, version 2. 4# 5# This program is distributed in the hope that it will be useful, but WITHOUT 6# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 7# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 8# details. 9# 10# You should have received a copy of the GNU General Public License along with 11# this program; if not, write to the Free Software Foundation, Inc., 51 12# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 13# 14# Copyright Buildbot Team Members 15 16importos.path 17importsocket 18importsys 19importsignal 20 21fromtwisted.spreadimportpb 22fromtwisted.pythonimportlog 23fromtwisted.internetimporterror,reactor,task 24fromtwisted.applicationimportservice,internet 25fromtwisted.credimportcredentials 26 27importbuildslave 28frombuildslave.utilimportnow 29frombuildslave.pbutilimportReconnectingPBClientFactory 30frombuildslave.commandsimportregistry,base 31

36 37"""This is the local representation of a single Builder: it handles a 38 single kind of build (like an all-warnings build). It has a name and a 39 home directory. The rest of its behavior is determined by the master. 40 """ 41 42stopCommandOnShutdown=True 43 44# remote is a ref to the Builder object on the master side, and is set 45# when they attach. We use it to detect when the connection to the master 46# is severed. 47remote=None 48 49# .command points to a SlaveCommand instance, and is set while the step 50# is running. We use it to implement the stopBuild method. 51command=None 52 53# .remoteStep is a ref to the master-side BuildStep object, and is set 54# when the step is started 55remoteStep=None 56

67# note that self.parent will go away when the buildmaster's config 68# file changes and this Builder is removed (possibly because it has 69# been changed, so the Builder will be re-added again in a moment). 70# This may occur during a build, while a step is running. 71

147"""Halt the current step."""148log.msg("asked to interrupt current command: %s"%why)149self.activity()150ifnotself.command:151# TODO: just log it, a race could result in their interrupting a152# command that wasn't actually running153log.msg(" .. but none was running")154return155self.command.doInterrupt()

159"""Make any currently-running command die, with no further status160 output. This is used when the buildslave is shutting down or the161 connection to the master has been lost. Interrupt the command,162 silence it, and then forget about it."""163ifnotself.command:164return165log.msg("stopCommand: halting current command %s"%self.command)166self.command.doInterrupt()# shut up! and die!167self.command=None# forget you!

171"""This sends the status update to the master-side172 L{buildbot.process.step.RemoteCommand} object, giving it a sequence173 number in the process. It adds the update to a queue, and asks the174 master to acknowledge the update so it can be removed from that175 queue."""176177ifnotself.running:178# .running comes from service.Service, and says whether the179# service is running or not. If we aren't running, don't send any180# status messages.181return182# the update[1]=0 comes from the leftover 'updateNum', which the183# master still expects to receive. Provide it to avoid significant184# interoperability issues between new slaves and old masters.185ifself.remoteStep:186update=[data,0]187updates=[update]188d=self.remoteStep.callRemote("update",updates)189d.addCallback(self.ackUpdate)190d.addErrback(self._ackFailed,"SlaveBuilder.sendUpdate")

257retval={}258wanted_dirs=["info"]259for(name,builddir)inwanted:260wanted_dirs.append(builddir)261b=self.builders.get(name,None)262ifb:263ifb.builddir!=builddir:264log.msg("changing builddir for builder %s from %s to %s" \ 265%(name,b.builddir,builddir))266b.setBuilddir(builddir)267else:268b=SlaveBuilder(name)269b.usePTY=self.usePTY270b.unicode_encoding=self.unicode_encoding271b.setServiceParent(self)272b.setBuilddir(builddir)273self.builders[name]=b274retval[name]=b275fornameinself.builders.keys():276ifnotnameinmap(lambdaa:a[0],wanted):277log.msg("removing old builder %s"%name)278self.builders[name].disownServiceParent()279del(self.builders[name])280281fordinos.listdir(self.basedir):282ifos.path.isdir(os.path.join(self.basedir,d)):283ifdnotinwanted_dirs:284log.msg("I have a leftover directory '%s' that is not "285"being used by the buildmaster: you can delete "286"it now"%d)287returnretval

293"""This command retrieves data from the files in SLAVEDIR/info/* and294 sends the contents to the buildmaster. These are used to describe295 the slave and its configuration, and should be created and296 maintained by the slave administrator. They will be retrieved each297 time the master-slave connection is established.298 """299300files={}301basedir=os.path.join(self.basedir,"info")302ifos.path.isdir(basedir):303forfinos.listdir(basedir):304filename=os.path.join(basedir,f)305ifos.path.isfile(filename):306files[f]=open(filename,"r").read()307files['environ']=os.environ.copy()308files['system']=os.name309files['basedir']=self.basedir310returnfiles

317log.msg("slave shutting down on command from master")318# there's no good way to learn that the PB response has been delivered,319# so we'll just wait a bit, in hopes the master hears back. Masters are320# resilinet to slaves dropping their connections, so there is no harm321# if this timeout is too short.322reactor.callLater(0.2,reactor.stop)

325# 'keepaliveInterval' serves two purposes. The first is to keep the326# connection alive: it guarantees that there will be at least some327# traffic once every 'keepaliveInterval' seconds, which may help keep an328# interposed NAT gateway from dropping the address mapping because it329# thinks the connection has been abandoned. The second is to put an upper330# limit on how long the buildmaster might have gone away before we notice331# it. For this second purpose, we insist upon seeing *some* evidence of332# the buildmaster at least once every 'keepaliveInterval' seconds.333keepaliveInterval=None# None = do not use keepalives334335# 'keepaliveTimeout' seconds before the interval expires, we will send a336# keepalive request, both to add some traffic to the connection, and to337# prompt a response from the master in case all our builders are idle. We338# don't insist upon receiving a timely response from this message: a slow339# link might put the request at the wrong end of a large build message.340keepaliveTimeout=30# how long we will go without a response341342# 'maxDelay' determines the maximum amount of time the slave will wait343# between connection retries344maxDelay=300345346keepaliveTimer=None347activityTimer=None348lastActivity=0349unsafeTracebacks=1350perspective=None351

427# send the keepalive request. If it fails outright, the connection428# was already dropped, so just log and ignore.429self.keepaliveTimer=None430log.msg("sending app-level keepalive")431d=self.perspective.callRemote("keepalive")432d.addCallback(self.activity)433d.addErrback(self.keepaliveLost)

513ifos.path.exists(self.shutdown_file)and \ 514os.path.getmtime(self.shutdown_file)>self.shutdown_mtime:515log.msg("Initiating shutdown because %s was touched"%self.shutdown_file)516self.gracefulShutdown()517518# In case the shutdown fails, update our mtime so we don't keep519# trying to shutdown over and over again.520# We do want to be able to try again later if the master is521# restarted, so we'll keep monitoring the mtime.522self.shutdown_mtime=os.path.getmtime(self.shutdown_file)

525"""Start shutting down"""526ifnotself.bf.perspective:527log.msg("No active connection, shutting down NOW")528reactor.stop()529530log.msg("Telling the master we want to shutdown after any running builds are finished")531d=self.bf.perspective.callRemote("shutdown")532def_shutdownfailed(err):533iferr.check(AttributeError):534log.msg("Master does not support slave initiated shutdown. Upgrade master to 0.8.3 or later to use this feature.")535else:536log.msg('callRemote("shutdown") failed')537log.err(err)