Package buildbot :: Module buildslave
[frames] | no frames]

Source Code for Module buildbot.buildslave

  1  # Portions copyright Canonical Ltd. 2009 
  2   
  3  import time 
  4  from email.Message import Message 
  5  from email.Utils import formatdate 
  6  from zope.interface import implements 
  7  from twisted.python import log 
  8  from twisted.internet import defer, reactor 
  9  from twisted.application import service 
 10  from twisted.spread import pb 
 11   
 12  from buildbot.status.builder import SlaveStatus 
 13  from buildbot.status.mail import MailNotifier 
 14  from buildbot.interfaces import IBuildSlave, ILatentBuildSlave 
 15  from buildbot.process.properties import Properties 
 16  from buildbot.locks import LockAccess 
 17   
 18  import sys 
 19   
20 -class AbstractBuildSlave(pb.Avatar, service.MultiService):
21 """This is the master-side representative for a remote buildbot slave. 22 There is exactly one for each slave described in the config file (the 23 c['slaves'] list). When buildbots connect in (.attach), they get a 24 reference to this instance. The BotMaster object is stashed as the 25 .botmaster attribute. The BotMaster is also our '.parent' Service. 26 27 I represent a build slave -- a remote machine capable of 28 running builds. I am instantiated by the configuration file, and can be 29 subclassed to add extra functionality.""" 30 31 implements(IBuildSlave) 32
33 - def __init__(self, name, password, max_builds=None, 34 notify_on_missing=[], missing_timeout=3600, 35 properties={}, locks=None):
36 """ 37 @param name: botname this machine will supply when it connects 38 @param password: password this machine will supply when 39 it connects 40 @param max_builds: maximum number of simultaneous builds that will 41 be run concurrently on this buildslave (the 42 default is None for no limit) 43 @param properties: properties that will be applied to builds run on 44 this slave 45 @type properties: dictionary 46 @param locks: A list of locks that must be acquired before this slave 47 can be used 48 @type locks: dictionary 49 """ 50 service.MultiService.__init__(self) 51 self.slavename = name 52 self.password = password 53 self.botmaster = None # no buildmaster yet 54 self.slave_status = SlaveStatus(name) 55 self.slave = None # a RemoteReference to the Bot, when connected 56 self.slave_commands = None 57 self.slavebuilders = {} 58 self.max_builds = max_builds 59 self.access = [] 60 if locks: 61 self.access = locks 62 63 self.properties = Properties() 64 self.properties.update(properties, "BuildSlave") 65 self.properties.setProperty("slavename", name, "BuildSlave") 66 67 self.lastMessageReceived = 0 68 if isinstance(notify_on_missing, str): 69 notify_on_missing = [notify_on_missing] 70 self.notify_on_missing = notify_on_missing 71 for i in notify_on_missing: 72 assert isinstance(i, str) 73 self.missing_timeout = missing_timeout 74 self.missing_timer = None
75
76 - def update(self, new):
77 """ 78 Given a new BuildSlave, configure this one identically. Because 79 BuildSlave objects are remotely referenced, we can't replace them 80 without disconnecting the slave, yet there's no reason to do that. 81 """ 82 # the reconfiguration logic should guarantee this: 83 assert self.slavename == new.slavename 84 assert self.password == new.password 85 assert self.__class__ == new.__class__ 86 self.max_builds = new.max_builds 87 self.access = new.access 88 self.notify_on_missing = new.notify_on_missing 89 self.missing_timeout = new.missing_timeout 90 91 self.properties.updateFromProperties(new.properties) 92 93 if self.botmaster: 94 self.updateLocks()
95
96 - def __repr__(self):
97 if self.botmaster: 98 builders = self.botmaster.getBuildersForSlave(self.slavename) 99 return "<%s '%s', current builders: %s>" % \ 100 (self.__class__.__name__, self.slavename, 101 ','.join(map(lambda b: b.name, builders))) 102 else: 103 return "<%s '%s', (no builders yet)>" % \ 104 (self.__class__.__name__, self.slavename)
105
106 - def updateLocks(self):
107 # convert locks into their real form 108 locks = [] 109 for access in self.access: 110 if not isinstance(access, LockAccess): 111 access = access.defaultAccess() 112 lock = self.botmaster.getLockByID(access.lockid) 113 locks.append((lock, access)) 114 self.locks = [(l.getLock(self), la) for l, la in locks]
115
116 - def locksAvailable(self):
117 """ 118 I am called to see if all the locks I depend on are available, 119 in which I return True, otherwise I return False 120 """ 121 if not self.locks: 122 return True 123 for lock, access in self.locks: 124 if not lock.isAvailable(access): 125 return False 126 return True
127
128 - def acquireLocks(self):
129 """ 130 I am called when a build is preparing to run. I try to claim all 131 the locks that are needed for a build to happen. If I can't, then 132 my caller should give up the build and try to get another slave 133 to look at it. 134 """ 135 log.msg("acquireLocks(slave %s, locks %s)" % (self, self.locks)) 136 if not self.locksAvailable(): 137 log.msg("slave %s can't lock, giving up" % (self, )) 138 return False 139 # all locks are available, claim them all 140 for lock, access in self.locks: 141 lock.claim(self, access) 142 return True
143
144 - def releaseLocks(self):
145 """ 146 I am called to release any locks after a build has finished 147 """ 148 log.msg("releaseLocks(%s): %s" % (self, self.locks)) 149 for lock, access in self.locks: 150 lock.release(self, access)
151
152 - def setBotmaster(self, botmaster):
153 assert not self.botmaster, "BuildSlave already has a botmaster" 154 self.botmaster = botmaster 155 self.updateLocks() 156 self.startMissingTimer()
157
158 - def stopMissingTimer(self):
159 if self.missing_timer: 160 self.missing_timer.cancel() 161 self.missing_timer = None
162
163 - def startMissingTimer(self):
164 if self.notify_on_missing and self.missing_timeout and self.parent: 165 self.stopMissingTimer() # in case it's already running 166 self.missing_timer = reactor.callLater(self.missing_timeout, 167 self._missing_timer_fired)
168
169 - def recordConnectTime(self):
170 if self.slave_status: 171 self.slave_status.recordConnectTime()
172
173 - def isConnected(self):
174 return self.slave
175
176 - def _missing_timer_fired(self):
177 self.missing_timer = None 178 # notify people, but only if we're still in the config 179 if not self.parent: 180 return 181 182 buildmaster = self.botmaster.parent 183 status = buildmaster.getStatus() 184 text = "The Buildbot working for '%s'\n" % status.getProjectName() 185 text += ("has noticed that the buildslave named %s went away\n" % 186 self.slavename) 187 text += "\n" 188 text += ("It last disconnected at %s (buildmaster-local time)\n" % 189 time.ctime(time.time() - self.missing_timeout)) # approx 190 text += "\n" 191 text += "The admin on record (as reported by BUILDSLAVE:info/admin)\n" 192 text += "was '%s'.\n" % self.slave_status.getAdmin() 193 text += "\n" 194 text += "Sincerely,\n" 195 text += " The Buildbot\n" 196 text += " %s\n" % status.getProjectURL() 197 subject = "Buildbot: buildslave %s was lost" % self.slavename 198 return self._mail_missing_message(subject, text)
199 200
201 - def updateSlave(self):
202 """Called to add or remove builders after the slave has connected. 203 204 @return: a Deferred that indicates when an attached slave has 205 accepted the new builders and/or released the old ones.""" 206 if self.slave: 207 return self.sendBuilderList() 208 else: 209 return defer.succeed(None)
210
211 - def updateSlaveStatus(self, buildStarted=None, buildFinished=None):
212 if buildStarted: 213 self.slave_status.buildStarted(buildStarted) 214 if buildFinished: 215 self.slave_status.buildFinished(buildFinished)
216
217 - def attached(self, bot):
218 """This is called when the slave connects. 219 220 @return: a Deferred that fires when the attachment is complete 221 """ 222 223 # the botmaster should ensure this. 224 assert not self.isConnected() 225 226 # now we go through a sequence of calls, gathering information, then 227 # tell the Botmaster that it can finally give this slave to all the 228 # Builders that care about it. 229 230 # we accumulate slave information in this 'state' dictionary, then 231 # set it atomically if we make it far enough through the process 232 state = {} 233 234 # Reset graceful shutdown status 235 self.slave_status.setGraceful(False) 236 # We want to know when the graceful shutdown flag changes 237 self.slave_status.addGracefulWatcher(self._gracefulChanged) 238 239 d = defer.succeed(None) 240 def _log_attachment_on_slave(res): 241 d1 = bot.callRemote("print", "attached") 242 d1.addErrback(lambda why: None) 243 return d1
244 d.addCallback(_log_attachment_on_slave) 245 246 def _get_info(res): 247 d1 = bot.callRemote("getSlaveInfo") 248 def _got_info(info): 249 log.msg("Got slaveinfo from '%s'" % self.slavename) 250 # TODO: info{} might have other keys 251 state["admin"] = info.get("admin") 252 state["host"] = info.get("host") 253 state["access_uri"] = info.get("access_uri", None)
254 def _info_unavailable(why): 255 # maybe an old slave, doesn't implement remote_getSlaveInfo 256 log.msg("BuildSlave.info_unavailable") 257 log.err(why) 258 d1.addCallbacks(_got_info, _info_unavailable) 259 return d1 260 d.addCallback(_get_info) 261 262 def _get_version(res): 263 d1 = bot.callRemote("getVersion") 264 def _got_version(version): 265 state["version"] = version 266 def _version_unavailable(why): 267 # probably an old slave 268 log.msg("BuildSlave.version_unavailable") 269 log.err(why) 270 d1.addCallbacks(_got_version, _version_unavailable) 271 d.addCallback(_get_version) 272 273 def _get_commands(res): 274 d1 = bot.callRemote("getCommands") 275 def _got_commands(commands): 276 state["slave_commands"] = commands 277 def _commands_unavailable(why): 278 # probably an old slave 279 log.msg("BuildSlave._commands_unavailable") 280 if why.check(AttributeError): 281 return 282 log.err(why) 283 d1.addCallbacks(_got_commands, _commands_unavailable) 284 return d1 285 d.addCallback(_get_commands) 286 287 def _accept_slave(res): 288 self.slave_status.setAdmin(state.get("admin")) 289 self.slave_status.setHost(state.get("host")) 290 self.slave_status.setAccessURI(state.get("access_uri")) 291 self.slave_status.setVersion(state.get("version")) 292 self.slave_status.setConnected(True) 293 self.slave_commands = state.get("slave_commands") 294 self.slave = bot 295 log.msg("bot attached") 296 self.messageReceivedFromSlave() 297 self.stopMissingTimer() 298 self.botmaster.parent.status.slaveConnected(self.slavename) 299 300 return self.updateSlave() 301 d.addCallback(_accept_slave) 302 d.addCallback(lambda res: self.botmaster.triggerNewBuildCheck()) 303 304 # Finally, the slave gets a reference to this BuildSlave. They 305 # receive this later, after we've started using them. 306 d.addCallback(lambda res: self) 307 return d 308
309 - def messageReceivedFromSlave(self):
310 now = time.time() 311 self.lastMessageReceived = now 312 self.slave_status.setLastMessageReceived(now)
313
314 - def detached(self, mind):
315 self.slave = None 316 self.slave_status.removeGracefulWatcher(self._gracefulChanged) 317 self.slave_status.setConnected(False) 318 log.msg("BuildSlave.detached(%s)" % self.slavename) 319 self.botmaster.parent.status.slaveDisconnected(self.slavename)
320
321 - def disconnect(self):
322 """Forcibly disconnect the slave. 323 324 This severs the TCP connection and returns a Deferred that will fire 325 (with None) when the connection is probably gone. 326 327 If the slave is still alive, they will probably try to reconnect 328 again in a moment. 329 330 This is called in two circumstances. The first is when a slave is 331 removed from the config file. In this case, when they try to 332 reconnect, they will be rejected as an unknown slave. The second is 333 when we wind up with two connections for the same slave, in which 334 case we disconnect the older connection. 335 """ 336 337 if not self.slave: 338 return defer.succeed(None) 339 log.msg("disconnecting old slave %s now" % self.slavename) 340 # When this Deferred fires, we'll be ready to accept the new slave 341 return self._disconnect(self.slave)
342
343 - def _disconnect(self, slave):
344 # all kinds of teardown will happen as a result of 345 # loseConnection(), but it happens after a reactor iteration or 346 # two. Hook the actual disconnect so we can know when it is safe 347 # to connect the new slave. We have to wait one additional 348 # iteration (with callLater(0)) to make sure the *other* 349 # notifyOnDisconnect handlers have had a chance to run. 350 d = defer.Deferred() 351 352 # notifyOnDisconnect runs the callback with one argument, the 353 # RemoteReference being disconnected. 354 def _disconnected(rref): 355 reactor.callLater(0, d.callback, None)
356 slave.notifyOnDisconnect(_disconnected) 357 tport = slave.broker.transport 358 # this is the polite way to request that a socket be closed 359 tport.loseConnection() 360 try: 361 # but really we don't want to wait for the transmit queue to 362 # drain. The remote end is unlikely to ACK the data, so we'd 363 # probably have to wait for a (20-minute) TCP timeout. 364 #tport._closeSocket() 365 # however, doing _closeSocket (whether before or after 366 # loseConnection) somehow prevents the notifyOnDisconnect 367 # handlers from being run. Bummer. 368 tport.offset = 0 369 tport.dataBuffer = "" 370 except: 371 # however, these hacks are pretty internal, so don't blow up if 372 # they fail or are unavailable 373 log.msg("failed to accelerate the shutdown process") 374 log.msg("waiting for slave to finish disconnecting") 375 376 return d 377
378 - def sendBuilderList(self):
379 our_builders = self.botmaster.getBuildersForSlave(self.slavename) 380 blist = [(b.name, b.slavebuilddir) for b in our_builders] 381 d = self.slave.callRemote("setBuilderList", blist) 382 return d
383
384 - def perspective_keepalive(self):
385 pass
386
387 - def addSlaveBuilder(self, sb):
388 self.slavebuilders[sb.builder_name] = sb
389
390 - def removeSlaveBuilder(self, sb):
391 try: 392 del self.slavebuilders[sb.builder_name] 393 except KeyError: 394 pass
395
396 - def canStartBuild(self):
397 """ 398 I am called when a build is requested to see if this buildslave 399 can start a build. This function can be used to limit overall 400 concurrency on the buildslave. 401 """ 402 # If we're waiting to shutdown gracefully, then we shouldn't 403 # accept any new jobs. 404 if self.slave_status.getGraceful(): 405 return False 406 407 if self.max_builds: 408 active_builders = [sb for sb in self.slavebuilders.values() 409 if sb.isBusy()] 410 if len(active_builders) >= self.max_builds: 411 return False 412 413 if not self.locksAvailable(): 414 return False 415 416 return True
417
418 - def _mail_missing_message(self, subject, text):
419 # first, see if we have a MailNotifier we can use. This gives us a 420 # fromaddr and a relayhost. 421 buildmaster = self.botmaster.parent 422 for st in buildmaster.statusTargets: 423 if isinstance(st, MailNotifier): 424 break 425 else: 426 # if not, they get a default MailNotifier, which always uses SMTP 427 # to localhost and uses a dummy fromaddr of "buildbot". 428 log.msg("buildslave-missing msg using default MailNotifier") 429 st = MailNotifier("buildbot") 430 # now construct the mail 431 432 m = Message() 433 m.set_payload(text) 434 m['Date'] = formatdate(localtime=True) 435 m['Subject'] = subject 436 m['From'] = st.fromaddr 437 recipients = self.notify_on_missing 438 m['To'] = ", ".join(recipients) 439 d = st.sendMessage(m, recipients) 440 # return the Deferred for testing purposes 441 return d
442
443 - def _gracefulChanged(self, graceful):
444 """This is called when our graceful shutdown setting changes""" 445 if graceful: 446 active_builders = [sb for sb in self.slavebuilders.values() 447 if sb.isBusy()] 448 if len(active_builders) == 0: 449 # Shut down! 450 self.shutdown()
451
452 - def shutdown(self):
453 """Shutdown the slave""" 454 # Look for a builder with a remote reference to the client side 455 # slave. If we can find one, then call "shutdown" on the remote 456 # builder, which will cause the slave buildbot process to exit. 457 d = None 458 for b in self.slavebuilders.values(): 459 if b.remote: 460 d = b.remote.callRemote("shutdown") 461 break 462 463 if d: 464 log.msg("Shutting down slave: %s" % self.slavename) 465 # The remote shutdown call will not complete successfully since the 466 # buildbot process exits almost immediately after getting the 467 # shutdown request. 468 # Here we look at the reason why the remote call failed, and if 469 # it's because the connection was lost, that means the slave 470 # shutdown as expected. 471 def _errback(why): 472 if why.check(pb.PBConnectionLost): 473 log.msg("Lost connection to %s" % self.slavename) 474 else: 475 log.err("Unexpected error when trying to shutdown %s" % self.slavename)
476 d.addErrback(_errback) 477 return d 478 log.err("Couldn't find remote builder to shut down slave") 479 return defer.succeed(None) 480
481 -class BuildSlave(AbstractBuildSlave):
482
483 - def sendBuilderList(self):
484 d = AbstractBuildSlave.sendBuilderList(self) 485 def _sent(slist): 486 dl = [] 487 for name, remote in slist.items(): 488 # use get() since we might have changed our mind since then 489 b = self.botmaster.builders.get(name) 490 if b: 491 d1 = b.attached(self, remote, self.slave_commands) 492 dl.append(d1) 493 return defer.DeferredList(dl)
494 def _set_failed(why): 495 log.msg("BuildSlave.sendBuilderList (%s) failed" % self) 496 log.err(why)
497 # TODO: hang up on them?, without setBuilderList we can't use 498 # them 499 d.addCallbacks(_sent, _set_failed) 500 return d 501
502 - def detached(self, mind):
503 AbstractBuildSlave.detached(self, mind) 504 self.botmaster.slaveLost(self) 505 self.startMissingTimer()
506
507 - def buildFinished(self, sb):
508 """This is called when a build on this slave is finished.""" 509 # If we're gracefully shutting down, and we have no more active 510 # builders, then it's safe to disconnect 511 if self.slave_status.getGraceful(): 512 active_builders = [sb for sb in self.slavebuilders.values() 513 if sb.isBusy()] 514 if len(active_builders) == 0: 515 # Shut down! 516 return self.shutdown() 517 return defer.succeed(None)
518
519 -class AbstractLatentBuildSlave(AbstractBuildSlave):
520 """A build slave that will start up a slave instance when needed. 521 522 To use, subclass and implement start_instance and stop_instance. 523 524 See ec2buildslave.py for a concrete example. Also see the stub example in 525 test/test_slaves.py. 526 """ 527 528 implements(ILatentBuildSlave) 529 530 substantiated = False 531 substantiation_deferred = None 532 build_wait_timer = None 533 _shutdown_callback_handle = None 534
535 - def __init__(self, name, password, max_builds=None, 536 notify_on_missing=[], missing_timeout=60*20, 537 build_wait_timeout=60*10, 538 properties={}, locks=None):
539 AbstractBuildSlave.__init__( 540 self, name, password, max_builds, notify_on_missing, 541 missing_timeout, properties, locks) 542 self.building = set() 543 self.build_wait_timeout = build_wait_timeout
544
545 - def start_instance(self):
546 # responsible for starting instance that will try to connect with 547 # this master. Should return deferred. Problems should use an 548 # errback. 549 raise NotImplementedError
550
551 - def stop_instance(self, fast=False):
552 # responsible for shutting down instance. 553 raise NotImplementedError
554
555 - def substantiate(self, sb):
556 if self.substantiated: 557 self._clearBuildWaitTimer() 558 self._setBuildWaitTimer() 559 return defer.succeed(True) 560 if self.substantiation_deferred is None: 561 if self.parent and not self.missing_timer: 562 # start timer. if timer times out, fail deferred 563 self.missing_timer = reactor.callLater( 564 self.missing_timeout, 565 self._substantiation_failed, defer.TimeoutError()) 566 self.substantiation_deferred = defer.Deferred() 567 if self.slave is None: 568 self._substantiate() # start up instance 569 # else: we're waiting for an old one to detach. the _substantiate 570 # will be done in ``detached`` below. 571 return self.substantiation_deferred
572
573 - def _substantiate(self):
574 # register event trigger 575 d = self.start_instance() 576 self._shutdown_callback_handle = reactor.addSystemEventTrigger( 577 'before', 'shutdown', self._soft_disconnect, fast=True) 578 def start_instance_result(result): 579 # If we don't report success, then preparation failed. 580 if not result: 581 log.msg("Slave '%s' doesn not want to substantiate at this time" % (self.slavename,)) 582 self.substantiation_deferred.callback(False) 583 self.substantiation_deferred = None 584 return result
585 def clean_up(failure): 586 if self.missing_timer is not None: 587 self.missing_timer.cancel() 588 self._substantiation_failed(failure) 589 if self._shutdown_callback_handle is not None: 590 handle = self._shutdown_callback_handle 591 del self._shutdown_callback_handle 592 reactor.removeSystemEventTrigger(handle) 593 return failure
594 d.addCallbacks(start_instance_result, clean_up) 595 return d 596
597 - def attached(self, bot):
598 if self.substantiation_deferred is None: 599 msg = 'Slave %s received connection while not trying to ' \ 600 'substantiate. Disconnecting.' % (self.slavename,) 601 log.msg(msg) 602 self._disconnect(bot) 603 return defer.fail(RuntimeError(msg)) 604 return AbstractBuildSlave.attached(self, bot)
605
606 - def detached(self, mind):
607 AbstractBuildSlave.detached(self, mind) 608 if self.substantiation_deferred is not None: 609 self._substantiate()
610
611 - def _substantiation_failed(self, failure):
612 self.missing_timer = None 613 if self.substantiation_deferred: 614 d = self.substantiation_deferred 615 self.substantiation_deferred = None 616 d.errback(failure) 617 self.insubstantiate() 618 # notify people, but only if we're still in the config 619 if not self.parent or not self.notify_on_missing: 620 return 621 622 buildmaster = self.botmaster.parent 623 status = buildmaster.getStatus() 624 text = "The Buildbot working for '%s'\n" % status.getProjectName() 625 text += ("has noticed that the latent buildslave named %s \n" % 626 self.slavename) 627 text += "never substantiated after a request\n" 628 text += "\n" 629 text += ("The request was made at %s (buildmaster-local time)\n" % 630 time.ctime(time.time() - self.missing_timeout)) # approx 631 text += "\n" 632 text += "Sincerely,\n" 633 text += " The Buildbot\n" 634 text += " %s\n" % status.getProjectURL() 635 subject = "Buildbot: buildslave %s never substantiated" % self.slavename 636 return self._mail_missing_message(subject, text)
637
638 - def buildStarted(self, sb):
639 assert self.substantiated 640 self._clearBuildWaitTimer() 641 self.building.add(sb.builder_name)
642
643 - def buildFinished(self, sb):
644 self.building.remove(sb.builder_name) 645 if not self.building: 646 self._setBuildWaitTimer()
647
648 - def _clearBuildWaitTimer(self):
649 if self.build_wait_timer is not None: 650 if self.build_wait_timer.active(): 651 self.build_wait_timer.cancel() 652 self.build_wait_timer = None
653
654 - def _setBuildWaitTimer(self):
655 self._clearBuildWaitTimer() 656 self.build_wait_timer = reactor.callLater( 657 self.build_wait_timeout, self._soft_disconnect)
658
659 - def insubstantiate(self, fast=False):
660 self._clearBuildWaitTimer() 661 d = self.stop_instance(fast) 662 if self._shutdown_callback_handle is not None: 663 handle = self._shutdown_callback_handle 664 del self._shutdown_callback_handle 665 reactor.removeSystemEventTrigger(handle) 666 self.substantiated = False 667 self.building.clear() # just to be sure 668 return d
669
670 - def _soft_disconnect(self, fast=False):
671 d = AbstractBuildSlave.disconnect(self) 672 if self.slave is not None: 673 # this could be called when the slave needs to shut down, such as 674 # in BotMaster.removeSlave, *or* when a new slave requests a 675 # connection when we already have a slave. It's not clear what to 676 # do in the second case: this shouldn't happen, and if it 677 # does...if it's a latent slave, shutting down will probably kill 678 # something we want...but we can't know what the status is. So, 679 # here, we just do what should be appropriate for the first case, 680 # and put our heads in the sand for the second, at least for now. 681 # The best solution to the odd situation is removing it as a 682 # possibilty: make the master in charge of connecting to the 683 # slave, rather than vice versa. TODO. 684 d = defer.DeferredList([d, self.insubstantiate(fast)]) 685 else: 686 if self.substantiation_deferred is not None: 687 # unlike the previous block, we don't expect this situation when 688 # ``attached`` calls ``disconnect``, only when we get a simple 689 # request to "go away". 690 self.substantiation_deferred.errback() 691 self.substantiation_deferred = None 692 if self.missing_timer: 693 self.missing_timer.cancel() 694 self.missing_timer = None 695 self.stop_instance() 696 return d
697
698 - def disconnect(self):
699 d = self._soft_disconnect() 700 # this removes the slave from all builders. It won't come back 701 # without a restart (or maybe a sighup) 702 self.botmaster.slaveLost(self)
703
704 - def stopService(self):
705 res = defer.maybeDeferred(AbstractBuildSlave.stopService, self) 706 if self.slave is not None: 707 d = self._soft_disconnect() 708 res = defer.DeferredList([res, d]) 709 return res
710
711 - def updateSlave(self):
712 """Called to add or remove builders after the slave has connected. 713 714 Also called after botmaster's builders are initially set. 715 716 @return: a Deferred that indicates when an attached slave has 717 accepted the new builders and/or released the old ones.""" 718 for b in self.botmaster.getBuildersForSlave(self.slavename): 719 if b.name not in self.slavebuilders: 720 b.addLatentSlave(self) 721 return AbstractBuildSlave.updateSlave(self)
722
723 - def sendBuilderList(self):
724 d = AbstractBuildSlave.sendBuilderList(self) 725 def _sent(slist): 726 dl = [] 727 for name, remote in slist.items(): 728 # use get() since we might have changed our mind since then. 729 # we're checking on the builder in addition to the 730 # slavebuilders out of a bit of paranoia. 731 b = self.botmaster.builders.get(name) 732 sb = self.slavebuilders.get(name) 733 if b and sb: 734 d1 = sb.attached(self, remote, self.slave_commands) 735 dl.append(d1) 736 return defer.DeferredList(dl)
737 def _set_failed(why): 738 log.msg("BuildSlave.sendBuilderList (%s) failed" % self) 739 log.err(why) 740 # TODO: hang up on them?, without setBuilderList we can't use 741 # them 742 if self.substantiation_deferred: 743 self.substantiation_deferred.errback() 744 self.substantiation_deferred = None 745 if self.missing_timer: 746 self.missing_timer.cancel() 747 self.missing_timer = None 748 # TODO: maybe log? send an email? 749 return why 750 d.addCallbacks(_sent, _set_failed) 751 def _substantiated(res): 752 log.msg("Slave %s substantiated \o/" % self.slavename) 753 self.substantiated = True 754 if not self.substantiation_deferred: 755 log.msg("No substantiation deferred for %s" % self.slavename) 756 if self.substantiation_deferred: 757 log.msg("Firing %s substantiation deferred with success" % self.slavename) 758 d = self.substantiation_deferred 759 del self.substantiation_deferred 760 d.callback(True) 761 # note that the missing_timer is already handled within 762 # ``attached`` 763 if not self.building: 764 self._setBuildWaitTimer() 765 d.addCallback(_substantiated) 766 return d 767