Package buildbot :: Package process :: Module slavebuilder
[frames] | no frames]

Source Code for Module buildbot.process.slavebuilder

  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   
 16  from twisted.spread import pb 
 17  from twisted.internet import defer 
 18  from twisted.python import log 
 19   
 20  (ATTACHING, # slave attached, still checking hostinfo/etc 
 21   IDLE, # idle, available for use 
 22   PINGING, # build about to start, making sure it is still alive 
 23   BUILDING, # build is running 
 24   LATENT, # latent slave is not substantiated; similar to idle 
 25   SUBSTANTIATING, 
 26   ) = range(6) 
 27   
28 -class AbstractSlaveBuilder(pb.Referenceable):
29 """I am the master-side representative for one of the 30 L{buildbot.slave.bot.SlaveBuilder} objects that lives in a remote 31 buildbot. When a remote builder connects, I query it for command versions 32 and then make it available to any Builds that are ready to run. """ 33
34 - def __init__(self):
35 self.ping_watchers = [] 36 self.state = None # set in subclass 37 self.remote = None 38 self.slave = None 39 self.builder_name = None 40 self.locks = None
41
42 - def __repr__(self):
43 r = ["<", self.__class__.__name__] 44 if self.builder_name: 45 r.extend([" builder=", repr(self.builder_name)]) 46 if self.slave: 47 r.extend([" slave=", repr(self.slave.slavename)]) 48 r.append(">") 49 return ''.join(r)
50
51 - def setBuilder(self, b):
52 self.builder = b 53 self.builder_name = b.name
54
55 - def getSlaveCommandVersion(self, command, oldversion=None):
56 if self.remoteCommands is None: 57 # the slave is 0.5.0 or earlier 58 return oldversion 59 return self.remoteCommands.get(command)
60
61 - def isAvailable(self):
62 # if this SlaveBuilder is busy, then it's definitely not available 63 if self.isBusy(): 64 return False 65 66 # otherwise, check in with the BuildSlave 67 if self.slave: 68 return self.slave.canStartBuild() 69 70 # no slave? not very available. 71 return False
72
73 - def isBusy(self):
74 return self.state not in (IDLE, LATENT)
75
76 - def buildStarted(self):
77 self.state = BUILDING
78
79 - def buildFinished(self):
80 self.state = IDLE 81 if self.slave: 82 self.slave.buildFinished(self)
83
84 - def attached(self, slave, remote, commands):
85 """ 86 @type slave: L{buildbot.buildslave.BuildSlave} 87 @param slave: the BuildSlave that represents the buildslave as a 88 whole 89 @type remote: L{twisted.spread.pb.RemoteReference} 90 @param remote: a reference to the L{buildbot.slave.bot.SlaveBuilder} 91 @type commands: dict: string -> string, or None 92 @param commands: provides the slave's version of each RemoteCommand 93 """ 94 self.state = ATTACHING 95 self.remote = remote 96 self.remoteCommands = commands # maps command name to version 97 if self.slave is None: 98 self.slave = slave 99 self.slave.addSlaveBuilder(self) 100 else: 101 assert self.slave == slave 102 log.msg("Buildslave %s attached to %s" % (slave.slavename, 103 self.builder_name)) 104 d = defer.succeed(None) 105 106 d.addCallback(lambda _: 107 self.remote.callRemote("setMaster", self)) 108 109 d.addCallback(lambda _: 110 self.remote.callRemote("print", "attached")) 111 112 def setIdle(res): 113 self.state = IDLE 114 return self
115 d.addCallback(setIdle) 116 117 return d
118
119 - def prepare(self, builder_status, build):
120 if not self.slave.acquireLocks(): 121 return defer.succeed(False) 122 return defer.succeed(True)
123
124 - def ping(self, status=None):
125 """Ping the slave to make sure it is still there. Returns a Deferred 126 that fires with True if it is. 127 128 @param status: if you point this at a BuilderStatus, a 'pinging' 129 event will be pushed. 130 """ 131 oldstate = self.state 132 self.state = PINGING 133 newping = not self.ping_watchers 134 d = defer.Deferred() 135 self.ping_watchers.append(d) 136 if newping: 137 if status: 138 event = status.addEvent(["pinging"]) 139 d2 = defer.Deferred() 140 d2.addCallback(self._pong_status, event) 141 self.ping_watchers.insert(0, d2) 142 # I think it will make the tests run smoother if the status 143 # is updated before the ping completes 144 Ping().ping(self.remote).addCallback(self._pong) 145 146 def reset_state(res): 147 if self.state == PINGING: 148 self.state = oldstate 149 return res
150 d.addCallback(reset_state) 151 return d 152
153 - def _pong(self, res):
154 watchers, self.ping_watchers = self.ping_watchers, [] 155 for d in watchers: 156 d.callback(res)
157
158 - def _pong_status(self, res, event):
159 if res: 160 event.text = ["ping", "success"] 161 else: 162 event.text = ["ping", "failed"] 163 event.finish()
164
165 - def detached(self):
166 log.msg("Buildslave %s detached from %s" % (self.slave.slavename, 167 self.builder_name)) 168 if self.slave: 169 self.slave.removeSlaveBuilder(self) 170 self.slave = None 171 self.remote = None 172 self.remoteCommands = None
173 174
175 -class Ping:
176 running = False 177
178 - def ping(self, remote):
179 assert not self.running 180 if not remote: 181 # clearly the ping must fail 182 return defer.succeed(False) 183 self.running = True 184 log.msg("sending ping") 185 self.d = defer.Deferred() 186 # TODO: add a distinct 'ping' command on the slave.. using 'print' 187 # for this purpose is kind of silly. 188 remote.callRemote("print", "ping").addCallbacks(self._pong, 189 self._ping_failed, 190 errbackArgs=(remote,)) 191 return self.d
192
193 - def _pong(self, res):
194 log.msg("ping finished: success") 195 self.d.callback(True)
196
197 - def _ping_failed(self, res, remote):
198 log.msg("ping finished: failure") 199 # the slave has some sort of internal error, disconnect them. If we 200 # don't, we'll requeue a build and ping them again right away, 201 # creating a nasty loop. 202 remote.broker.transport.loseConnection() 203 # TODO: except, if they actually did manage to get this far, they'll 204 # probably reconnect right away, and we'll do this game again. Maybe 205 # it would be better to leave them in the PINGING state. 206 self.d.callback(False)
207 208
209 -class SlaveBuilder(AbstractSlaveBuilder):
210
211 - def __init__(self):
214
215 - def detached(self):
216 AbstractSlaveBuilder.detached(self) 217 if self.slave: 218 self.slave.removeSlaveBuilder(self) 219 self.slave = None 220 self.state = ATTACHING
221
222 -class LatentSlaveBuilder(AbstractSlaveBuilder):
223 - def __init__(self, slave, builder):
224 AbstractSlaveBuilder.__init__(self) 225 self.slave = slave 226 self.state = LATENT 227 self.setBuilder(builder) 228 self.slave.addSlaveBuilder(self) 229 log.msg("Latent buildslave %s attached to %s" % (slave.slavename, 230 self.builder_name))
231
232 - def prepare(self, builder_status, build):
233 # If we can't lock, then don't bother trying to substantiate 234 if not self.slave or not self.slave.acquireLocks(): 235 return defer.succeed(False) 236 237 log.msg("substantiating slave %s" % (self,)) 238 d = self.substantiate(build) 239 def substantiation_failed(f): 240 builder_status.addPointEvent(['removing', 'latent', 241 self.slave.slavename]) 242 self.slave.disconnect() 243 # TODO: should failover to a new Build 244 return f
245 def substantiation_cancelled(res): 246 # if res is False, latent slave cancelled subtantiation 247 if not res: 248 self.state = LATENT 249 return res
250 d.addCallback(substantiation_cancelled) 251 d.addErrback(substantiation_failed) 252 return d 253
254 - def substantiate(self, build):
255 self.state = SUBSTANTIATING 256 d = self.slave.substantiate(self, build) 257 if not self.slave.substantiated: 258 event = self.builder.builder_status.addEvent( 259 ["substantiating"]) 260 def substantiated(res): 261 msg = ["substantiate", "success"] 262 if isinstance(res, basestring): 263 msg.append(res) 264 elif isinstance(res, (tuple, list)): 265 msg.extend(res) 266 event.text = msg 267 event.finish() 268 return res
269 def substantiation_failed(res): 270 event.text = ["substantiate", "failed"] 271 # TODO add log of traceback to event 272 event.finish() 273 return res 274 d.addCallbacks(substantiated, substantiation_failed) 275 return d 276
277 - def detached(self):
278 AbstractSlaveBuilder.detached(self) 279 self.state = LATENT
280
281 - def buildStarted(self):
282 AbstractSlaveBuilder.buildStarted(self) 283 self.slave.buildStarted(self)
284
285 - def _attachFailure(self, why, where):
286 self.state = LATENT 287 return AbstractSlaveBuilder._attachFailure(self, why, where)
288
289 - def ping(self, status=None):
290 if not self.slave.substantiated: 291 if status: 292 status.addEvent(["ping", "latent"]).finish() 293 return defer.succeed(True) 294 return AbstractSlaveBuilder.ping(self, status)
295