1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 __doc__ = """PBUtil
16 Base classes handy for use with PB clients.
17 """
18
19 import logging
20 zenlog = logging.getLogger("zen.pbclientfactory")
21
22 from twisted.spread import pb
23
24 from twisted.spread.pb import PBClientFactory
25 from twisted.internet import protocol, reactor, defer, task
26 from twisted.internet.error import ConnectionClosed
30 """Reconnecting client factory for PB brokers.
31
32 Like PBClientFactory, but if the connection fails or is lost, the factory
33 will attempt to reconnect.
34
35 Instead of using f.getRootObject (which gives a Deferred that can only
36 be fired once), override the gotRootObject method.
37
38 Instead of using the newcred f.login (which is also one-shot), call
39 f.startLogin() with the credentials and client, and override the
40 gotPerspective method.
41
42 Instead of using the oldcred f.getPerspective (also one-shot), call
43 f.startGettingPerspective() with the same arguments, and override
44 gotPerspective.
45
46 gotRootObject and gotPerspective will be called each time the object is
47 received (once per successful connection attempt). You will probably want
48 to use obj.notifyOnDisconnect to find out when the connection is lost.
49
50 If an authorization error occurs, failedToGetPerspective() will be
51 invoked.
52
53 To use me, subclass, then hand an instance to a connector (like
54 TCPClient).
55 """
56 __pychecker__='no-override'
57
58
59
60 maxDelay = 300
61
62 - def __init__(self, connectTimeout=30, pingPerspective=False, pingInterval=30, pingtimeout=120):
63 PBClientFactory.__init__(self)
64 self._doingLogin = False
65 self._doingGetPerspective = False
66 self._scheduledConnectTimeout = None
67 self._connectTimeout = connectTimeout
68
69 self._shouldPingPerspective = pingPerspective
70
71 self._pingInterval = pingInterval
72
73 self._pingTimeoutTime = pingtimeout
74
75 self._pingTimeout = None
76
77 self._pingCheck = None
78
79 self._perspective = None
80
85
87 zenlog.debug("Failed to create connection to %s:%s - %s",
88 connector.host, connector.port, reason)
89 self._perspective = None
90 self._cancelConnectTimeout()
91 PBClientFactory.clientConnectionFailed(self, connector, reason)
92
93
94
95 if self.continueTrying:
96 self.connector = connector
97 self.retry()
98
108
120
125
127
128 d = self.__dict__.copy()
129 d['connector'] = None
130 d['_callID'] = None
131 return d
132
133
134
136 raise RuntimeError( "getPerspective is one-shot: use startGettingPerspective instead" )
137
140 self._doingGetPerspective = True
141 if perspectiveName == None:
142 perspectiveName = username
143 self._oldcredArgs = (username, password, serviceName,
144 perspectiveName, client)
145
147
148 (username, password,
149 serviceName, perspectiveName, client) = self._oldcredArgs
150 d = self._cbAuthIdentity(root, username, password)
151 d.addCallback(self._cbGetPerspective,
152 serviceName, perspectiveName, client)
153 d.addCallbacks(self._gotPerspective, self.failedToGetPerspective)
154
155
156
157
158 - def login(self, credentials, client=None):
159 from Products.ZenUtils.Utils import unused
160 unused(credentials, client)
161 raise RuntimeError( "Login is one-shot: use startLogin instead" )
162
164 self._credentials = credentials
165 self._client = client
166 self._doingLogin = True
167
175
183
184
186 if self._broker:
187 self.disconnect()
188 elif self.connector:
189 try:
190 self.connector.disconnect()
191 except Exception:
192 zenlog.exception('Could not disconnect')
193 else:
194 zenlog.debug('No connector or broker to disconnect')
195
196
200
204
206 self._scheduledConnectTimeout, timeout = None, self._scheduledConnectTimeout
207 if timeout and timeout.active():
208 zenlog.debug("Cancelling connect timeout")
209 timeout.cancel()
210
211
213 if not self._pingTimeout:
214 self._pingTimeout = reactor.callLater(self._pingTimeoutTime,
215 self._doPingTimeout)
216
222
224 if self._perspective:
225 zenlog.warn("Perspective ping timed out after %s seconds", self._pingTimeoutTime)
226 self._disconnect()
227
228 @defer.inlineCallbacks
230 if not self._pingCheck:
231 pingCheck = task.LoopingCall(self._pingPerspective)
232 self._pingCheck = pingCheck
233 try:
234 yield pingCheck.start(self._pingInterval)
235 except Exception:
236 zenlog.exception("perspective ping loop died")
237 finally:
238
239 zenlog.info("perspective ping loop ended")
240
241 @defer.inlineCallbacks
257
258
259
261 """
262 Called when a connection is about to be attempted. Can be the initial
263 connect or a retry/reconnect
264 """
265 pass
266
268 """The remote avatar or perspective (obtained each time this factory
269 connects) is now available."""
270 pass
271
273 """The remote root object (obtained each time this factory connects)
274 is now available. This method will be called each time the connection
275 is established and the object reference is retrieved."""
276 pass
277
279 """The login process failed, most likely because of an authorization
280 failure (bad password), but it is also possible that we lost the new
281 connection before we managed to send our credentials.
282 """
283 self._cancelConnectTimeout()
284 zenlog.debug("ReconnectingPBClientFactory.failedToGetPerspective")
285 if why.check(pb.PBConnectionLost):
286 zenlog.debug("we lost the brand-new connection")
287
288 return
289
290 zenlog.warning("Cancelling attempts to connect")
291 self.stopTrying()
292 if why.type == 'twisted.cred.error.UnauthorizedLogin':
293 zenlog.critical("zenhub username/password combination is incorrect!")
294
295 else:
296 zenlog.critical("Unknown connection problem to zenhub %s", why.type)
297