149 lines
5.5 KiB
Python
149 lines
5.5 KiB
Python
# This file is part of Buildbot. Buildbot is free software: you can
|
|
# redistribute it and/or modify it under the terms of the GNU General Public
|
|
# License as published by the Free Software Foundation, version 2.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Software Foundation, Inc., 51
|
|
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
# Copyright Buildbot Team Members
|
|
|
|
|
|
"""Base classes handy for use with PB clients.
|
|
"""
|
|
|
|
from twisted.spread import pb
|
|
|
|
from twisted.cred import error
|
|
from twisted.internet import protocol
|
|
from twisted.internet import reactor
|
|
from twisted.python import log
|
|
from twisted.spread.pb import PBClientFactory
|
|
|
|
|
|
class ReconnectingPBClientFactory(PBClientFactory,
|
|
protocol.ReconnectingClientFactory):
|
|
|
|
"""Reconnecting client factory for PB brokers.
|
|
|
|
Like PBClientFactory, but if the connection fails or is lost, the factory
|
|
will attempt to reconnect.
|
|
|
|
Instead of using f.getRootObject (which gives a Deferred that can only
|
|
be fired once), override the gotRootObject method.
|
|
|
|
Instead of using the newcred f.login (which is also one-shot), call
|
|
f.startLogin() with the credentials and client, and override the
|
|
gotPerspective method.
|
|
|
|
gotRootObject and gotPerspective will be called each time the object is
|
|
received (once per successful connection attempt). You will probably want
|
|
to use obj.notifyOnDisconnect to find out when the connection is lost.
|
|
|
|
If an authorization error occurs, failedToGetPerspective() will be
|
|
invoked.
|
|
|
|
To use me, subclass, then hand an instance to a connector (like
|
|
TCPClient).
|
|
"""
|
|
|
|
# hung connections wait for a relatively long time, since a busy master may
|
|
# take a while to get back to us.
|
|
hungConnectionTimer = None
|
|
HUNG_CONNECTION_TIMEOUT = 120
|
|
|
|
def clientConnectionFailed(self, connector, reason):
|
|
PBClientFactory.clientConnectionFailed(self, connector, reason)
|
|
if self.continueTrying:
|
|
self.connector = connector
|
|
self.retry()
|
|
|
|
def clientConnectionLost(self, connector, reason):
|
|
PBClientFactory.clientConnectionLost(self, connector, reason,
|
|
reconnecting=True)
|
|
RCF = protocol.ReconnectingClientFactory
|
|
RCF.clientConnectionLost(self, connector, reason)
|
|
|
|
def startedConnecting(self, connector):
|
|
self.startHungConnectionTimer(connector)
|
|
|
|
def clientConnectionMade(self, broker):
|
|
self.resetDelay()
|
|
PBClientFactory.clientConnectionMade(self, broker)
|
|
self.doLogin(self._root, broker)
|
|
self.gotRootObject(self._root)
|
|
|
|
# newcred methods
|
|
|
|
def login(self, *args):
|
|
raise RuntimeError("login is one-shot: use startLogin instead")
|
|
|
|
def startLogin(self, credentials, client=None):
|
|
self._credentials = credentials
|
|
self._client = client
|
|
|
|
def doLogin(self, root, broker):
|
|
# newcred login()
|
|
d = self._cbSendUsername(root, self._credentials.username,
|
|
self._credentials.password, self._client)
|
|
d.addCallbacks(self.gotPerspective, self.failedToGetPerspective,
|
|
errbackArgs=(broker,))
|
|
|
|
# timer for hung connections
|
|
|
|
def startHungConnectionTimer(self, connector):
|
|
self.stopHungConnectionTimer()
|
|
|
|
def hungConnection():
|
|
log.msg("connection attempt timed out (is the port number correct?)")
|
|
self.hungConnectionTimer = None
|
|
connector.disconnect()
|
|
# (this will trigger the retry)
|
|
self.hungConnectionTimer = reactor.callLater(self.HUNG_CONNECTION_TIMEOUT, hungConnection)
|
|
|
|
def stopHungConnectionTimer(self):
|
|
if self.hungConnectionTimer:
|
|
self.hungConnectionTimer.cancel()
|
|
self.hungConnectionTimer = None
|
|
|
|
# methods to override
|
|
|
|
def gotPerspective(self, perspective):
|
|
"""The remote avatar or perspective (obtained each time this factory
|
|
connects) is now available."""
|
|
self.stopHungConnectionTimer()
|
|
|
|
def gotRootObject(self, root):
|
|
"""The remote root object (obtained each time this factory connects)
|
|
is now available. This method will be called each time the connection
|
|
is established and the object reference is retrieved."""
|
|
self.stopHungConnectionTimer()
|
|
|
|
def failedToGetPerspective(self, why, broker):
|
|
"""The login process failed, most likely because of an authorization
|
|
failure (bad password), but it is also possible that we lost the new
|
|
connection before we managed to send our credentials.
|
|
"""
|
|
log.msg("ReconnectingPBClientFactory.failedToGetPerspective")
|
|
self.stopHungConnectionTimer()
|
|
# put something useful in the logs
|
|
if why.check(pb.PBConnectionLost):
|
|
log.msg("we lost the brand-new connection")
|
|
# fall through
|
|
elif why.check(error.UnauthorizedLogin):
|
|
log.msg("unauthorized login; check slave name and password")
|
|
# fall through
|
|
else:
|
|
log.err(why, 'While trying to connect:')
|
|
self.stopTrying()
|
|
reactor.stop()
|
|
return
|
|
|
|
# lose the current connection, which will trigger a retry
|
|
broker.transport.loseConnection()
|