1 import os.path
2 import socket
3 import sys
4
5 from twisted.spread import pb
6 from twisted.python import log
7 from twisted.internet import reactor, defer, error
8 from twisted.application import service, internet
9 from twisted.cred import credentials
10
11 import buildslave
12 from buildslave.util import now
13 from buildslave.pbutil import ReconnectingPBClientFactory
14 from buildslave.commands import registry, base
15
18
20
21 """This is the local representation of a single Builder: it handles a
22 single kind of build (like an all-warnings build). It has a name and a
23 home directory. The rest of its behavior is determined by the master.
24 """
25
26 stopCommandOnShutdown = True
27
28
29
30
31 remote = None
32
33
34
35 command = None
36
37
38
39 remoteStep = None
40
41
42 _reactor = reactor
43
47
49 return "<SlaveBuilder '%s' at %d>" % (self.name, id(self))
50
54
55
56
57
58
65
70
78
82
84 log.msg("SlaveBuilder.remote_print(%s): message from master: %s" %
85 (self.name, message))
86
88 log.msg("lost remote")
89 self.remote = None
90
96
97
98
100 """This is invoked before the first step of any new build is run. It
101 doesn't do much, but masters call it so it's still here."""
102 pass
103
105 """
106 This gets invoked by L{buildbot.process.step.RemoteCommand.start}, as
107 part of various master-side BuildSteps, to start various commands
108 that actually do the build. I return nothing. Eventually I will call
109 .commandComplete() to notify the master-side RemoteCommand that I'm
110 done.
111 """
112
113 self.activity()
114
115 if self.command:
116 log.msg("leftover command, dropping it")
117 self.stopCommand()
118
119 try:
120 factory = registry.getFactory(command)
121 except KeyError:
122 raise UnknownCommand, "unrecognized SlaveCommand '%s'" % command
123 self.command = factory(self, stepId, args)
124
125 log.msg(" startCommand:%s [id %s]" % (command,stepId))
126 self.remoteStep = stepref
127 self.remoteStep.notifyOnDisconnect(self.lostRemoteStep)
128 d = self.command.doStart()
129 d.addCallback(lambda res: None)
130 d.addBoth(self.commandComplete)
131 return None
132
134 """Halt the current step."""
135 log.msg("asked to interrupt current command: %s" % why)
136 self.activity()
137 if not self.command:
138
139
140 log.msg(" .. but none was running")
141 return
142 self.command.doInterrupt()
143
144
146 """Make any currently-running command die, with no further status
147 output. This is used when the buildslave is shutting down or the
148 connection to the master has been lost. Interrupt the command,
149 silence it, and then forget about it."""
150 if not self.command:
151 return
152 log.msg("stopCommand: halting current command %s" % self.command)
153 self.command.doInterrupt()
154 self.command = None
155
156
158 """This sends the status update to the master-side
159 L{buildbot.process.step.RemoteCommand} object, giving it a sequence
160 number in the process. It adds the update to a queue, and asks the
161 master to acknowledge the update so it can be removed from that
162 queue."""
163
164 if not self.running:
165
166
167
168 return
169
170
171
172 if self.remoteStep:
173 update = [data, 0]
174 updates = [update]
175 d = self.remoteStep.callRemote("update", updates)
176 d.addCallback(self.ackUpdate)
177 d.addErrback(self._ackFailed, "SlaveBuilder.sendUpdate")
178
181
184
186 log.msg("SlaveBuilder._ackFailed:", where)
187 log.err(why)
188
189
190
212
213
215 log.msg("slave shutting down on command from master")
216 self._reactor.stop()
217
218
219 -class Bot(pb.Referenceable, service.MultiService):
220 """I represent the slave-side bot."""
221 usePTY = None
222 name = "bot"
223
224 - def __init__(self, basedir, usePTY, unicode_encoding=None):
225 service.MultiService.__init__(self)
226 self.basedir = basedir
227 self.usePTY = usePTY
228 self.unicode_encoding = unicode_encoding or sys.getfilesystemencoding() or 'ascii'
229 self.builders = {}
230
234
241
243 retval = {}
244 wanted_dirs = ["info"]
245 for (name, builddir) in wanted:
246 wanted_dirs.append(builddir)
247 b = self.builders.get(name, None)
248 if b:
249 if b.builddir != builddir:
250 log.msg("changing builddir for builder %s from %s to %s" \
251 % (name, b.builddir, builddir))
252 b.setBuilddir(builddir)
253 else:
254 b = SlaveBuilder(name)
255 b.usePTY = self.usePTY
256 b.unicode_encoding = self.unicode_encoding
257 b.setServiceParent(self)
258 b.setBuilddir(builddir)
259 self.builders[name] = b
260 retval[name] = b
261 for name in self.builders.keys():
262 if not name in map(lambda a: a[0], wanted):
263 log.msg("removing old builder %s" % name)
264 self.builders[name].disownServiceParent()
265 del(self.builders[name])
266
267 for d in os.listdir(self.basedir):
268 if os.path.isdir(os.path.join(self.basedir, d)):
269 if d not in wanted_dirs:
270 log.msg("I have a leftover directory '%s' that is not "
271 "being used by the buildmaster: you can delete "
272 "it now" % d)
273 return retval
274
276 log.msg("message from master:", message)
277
279 """This command retrieves data from the files in SLAVEDIR/info/* and
280 sends the contents to the buildmaster. These are used to describe
281 the slave and its configuration, and should be created and
282 maintained by the slave administrator. They will be retrieved each
283 time the master-slave connection is established.
284 """
285
286 files = {}
287 basedir = os.path.join(self.basedir, "info")
288 if not os.path.isdir(basedir):
289 return files
290 for f in os.listdir(basedir):
291 filename = os.path.join(basedir, f)
292 if os.path.isfile(filename):
293 files[f] = open(filename, "r").read()
294 return files
295
299
300
301
428
429
431 - def __init__(self, buildmaster_host, port, name, passwd, basedir,
432 keepalive, usePTY, keepaliveTimeout=30, umask=None,
433 maxdelay=300, unicode_encoding=None):
434 log.msg("Creating BuildSlave -- version: %s" % buildslave.version)
435 self.recordHostname()
436 service.MultiService.__init__(self)
437 bot = Bot(basedir, usePTY, unicode_encoding=unicode_encoding)
438 bot.setServiceParent(self)
439 self.bot = bot
440 if keepalive == 0:
441 keepalive = None
442 self.umask = umask
443 bf = self.bf = BotFactory(buildmaster_host, port, keepalive, keepaliveTimeout, maxdelay)
444 bf.startLogin(credentials.UsernamePassword(name, passwd), client=bot)
445 self.connection = c = internet.TCPClient(buildmaster_host, port, bf)
446 c.setServiceParent(self)
447
449 "Record my hostname in twistd.hostname, for user convenience"
450 log.msg("recording hostname in twistd.hostname")
451 try:
452 open("twistd.hostname", "w").write("%s\n" % socket.getfqdn())
453 except:
454 log.msg("failed - ignoring")
455
457 if self.umask is not None:
458 os.umask(self.umask)
459 service.MultiService.startService(self)
460
462 self.bf.continueTrying = 0
463 self.bf.stopTrying()
464 service.MultiService.stopService(self)
465