Package flumotion :: Package twisted :: Module pb
[hide private]

Source Code for Module flumotion.twisted.pb

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_pb -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """ 
 23  Flumotion Perspective Broker using keycards 
 24   
 25  Inspired by L{twisted.spread.pb} 
 26  """ 
 27   
 28  import time 
 29   
 30  from twisted.cred import checkers, credentials 
 31  from twisted.cred.portal import IRealm, Portal 
 32  from twisted.internet import protocol, defer, reactor 
 33  from twisted.internet import error as terror 
 34  from twisted.python import log, reflect, failure 
 35  from twisted.spread import pb, flavors 
 36  from twisted.spread.pb import PBClientFactory 
 37  from zope.interface import implements 
 38   
 39  from flumotion.configure import configure 
 40  from flumotion.common import keycards, interfaces, common, errors 
 41  from flumotion.common import log as flog 
 42  from flumotion.twisted import reflect as freflect 
 43  from flumotion.twisted import credentials as fcredentials 
 44  # TODO: 
 45  #   merge FMCF back into twisted 
 46   
 47  ### Keycard-based FPB objects 
 48   
 49  # we made three changes to the standard PBClientFactory: 
 50  # 1) the root object has a getKeycardClasses() call that the server 
 51  #    uses to tell clients about the interfaces it supports 
 52  # 2) you can request a specific interface for the avatar to 
 53  #    implement, instead of only IPerspective 
 54  # 3) you send in a keycard, on which you can set a preference for an avatarId 
 55  # this way you can request a different avatarId than the user you authenticate 
 56  # with, or you can login without a username 
 57   
58 -class FPBClientFactory(pb.PBClientFactory, flog.Loggable):
59 """ 60 I am an extended Perspective Broker client factory using generic 61 keycards for login. 62 63 64 @ivar keycard: the keycard used last for logging in; set after 65 self.login has completed 66 @type keycard: L{keycards.Keycard} 67 @ivar medium: the client-side referenceable for the PB server 68 to call on, and for the client to call to the 69 PB server 70 @type medium: L{flumotion.common.medium.BaseMedium} 71 @ivar perspectiveInterface: the interface we want to request a perspective 72 for 73 @type perspectiveInterface: subclass of 74 L{flumotion.common.interfaces.IMedium} 75 """ 76 logCategory = "FPBClientFactory" 77 keycard = None 78 medium = None 79 perspectiveInterface = None # override in subclass 80 _fpbconnector = None 81 82 ## from protocol.ClientFactory
83 - def startedConnecting(self, connector):
84 self._fpbconnector = connector 85 return pb.PBClientFactory.startedConnecting(self, connector)
86 87 ## from twisted.spread.pb.ClientFactory
88 - def disconnect(self):
89 if self._fpbconnector: 90 try: 91 self._fpbconnector.stopConnecting() 92 except terror.NotConnectingError: 93 pass 94 return pb.PBClientFactory.disconnect(self)
95
96 - def getKeycardClasses(self):
97 """ 98 Ask the remote PB server for all the keycard interfaces it supports. 99 100 @rtype: L{twisted.internet.defer.Deferred} returning list of str 101 """ 102 def getRootObjectCb(root): 103 return root.callRemote('getKeycardClasses')
104 105 d = self.getRootObject() 106 d.addCallback(getRootObjectCb) 107 return d
108
109 - def login(self, authenticator):
110 """ 111 Login, respond to challenges, and eventually get perspective 112 from remote PB server. 113 114 Currently only credentials implementing IUsernamePassword are 115 supported. 116 117 @return: Deferred of RemoteReference to the perspective. 118 """ 119 assert authenticator, "I really do need an authenticator" 120 assert not isinstance(authenticator, keycards.Keycard) 121 interfaces = [] 122 if self.perspectiveInterface: 123 self.debug('perspectiveInterface is %r' % self.perspectiveInterface) 124 interfaces.append(self.perspectiveInterface) 125 else: 126 self.warning('No perspectiveInterface set on %r' % self) 127 if not pb.IPerspective in interfaces: 128 interfaces.append(pb.IPerspective) 129 interfaces = [reflect.qual(interface) 130 for interface in interfaces] 131 132 def getKeycardClassesCb(keycardClasses): 133 self.log('supported keycard classes: %r' % keycardClasses) 134 d = authenticator.issue(keycardClasses) 135 return d
136 137 def issueCb(keycard): 138 self.keycard = keycard 139 self.debug('using keycard: %r' % self.keycard) 140 return self.keycard 141 142 d = self.getKeycardClasses() 143 d.addCallback(getKeycardClassesCb) 144 d.addCallback(issueCb) 145 d.addCallback(lambda r: self.getRootObject()) 146 d.addCallback(self._cbSendKeycard, authenticator, self.medium, 147 interfaces) 148 return d 149 150 # we are a different kind of PB client, so warn
151 - def _cbSendUsername(self, root, username, password, avatarId, client, interfaces):
152 self.warning("you really want to use cbSendKeycard")
153 154
155 - def _cbSendKeycard(self, root, authenticator, client, interfaces, count=0):
156 self.log("_cbSendKeycard(root=%r, authenticator=%r, client=%r, " 157 "interfaces=%r, count=%d", root, authenticator, client, 158 interfaces, count) 159 count = count + 1 160 d = root.callRemote("login", self.keycard, client, *interfaces) 161 return d.addCallback(self._cbLoginCallback, root, authenticator, client, 162 interfaces, count)
163 164 # we can get either a keycard, None (?) or a remote reference
165 - def _cbLoginCallback(self, result, root, authenticator, client, interfaces, 166 count):
167 if count > 5: 168 # too many recursions, server is h0rked 169 self.warning('Too many recursions, internal error.') 170 self.log("FPBClientFactory(): result %r" % result) 171 172 if isinstance(result, pb.RemoteReference): 173 # everything done, return reference 174 self.debug('login successful, returning %r', result) 175 return result 176 177 # must be a keycard 178 keycard = result 179 if not keycard.state == keycards.AUTHENTICATED: 180 self.log("FPBClientFactory(): requester needs to resend %r", 181 keycard) 182 d = authenticator.respond(keycard) 183 def _loginAgainCb(keycard): 184 d = root.callRemote("login", keycard, client, *interfaces) 185 return d.addCallback(self._cbLoginCallback, root, authenticator, 186 client, interfaces, count)
187 d.addCallback(_loginAgainCb) 188 return d 189 190 self.debug("FPBClientFactory(): authenticated %r" % keycard) 191 return keycard 192
193 -class ReconnectingPBClientFactory(pb.PBClientFactory, flog.Loggable, 194 protocol.ReconnectingClientFactory):
195 """ 196 Reconnecting client factory for normal PB brokers. 197 198 Users of this factory call startLogin to start logging in, and should 199 override getLoginDeferred to get the deferred returned from the PB server 200 for each login attempt. 201 """ 202
203 - def __init__(self):
204 pb.PBClientFactory.__init__(self) 205 self._doingLogin = False
206
207 - def clientConnectionFailed(self, connector, reason):
208 log.msg("connection failed to %s, reason %r" % ( 209 connector.getDestination(), reason)) 210 pb.PBClientFactory.clientConnectionFailed(self, connector, reason) 211 RCF = protocol.ReconnectingClientFactory 212 RCF.clientConnectionFailed(self, connector, reason)
213
214 - def clientConnectionLost(self, connector, reason):
215 log.msg("connection lost to %s, reason %r" % ( 216 connector.getDestination(), reason)) 217 pb.PBClientFactory.clientConnectionLost(self, connector, reason, 218 reconnecting=True) 219 RCF = protocol.ReconnectingClientFactory 220 RCF.clientConnectionLost(self, connector, reason)
221
222 - def clientConnectionMade(self, broker):
223 log.msg("connection made") 224 self.resetDelay() 225 pb.PBClientFactory.clientConnectionMade(self, broker) 226 if self._doingLogin: 227 d = self.login(self._credentials, self._client) 228 self.gotDeferredLogin(d)
229
230 - def startLogin(self, credentials, client=None):
231 self._credentials = credentials 232 self._client = client 233 234 self._doingLogin = True
235 236 # methods to override
237 - def gotDeferredLogin(self, deferred):
238 """ 239 The deferred from login is now available. 240 """ 241 raise NotImplementedError
242
243 -class ReconnectingFPBClientFactory(FPBClientFactory, 244 protocol.ReconnectingClientFactory):
245 """ 246 Reconnecting client factory for FPB brokers (using keycards for login). 247 248 Users of this factory call startLogin to start logging in. 249 Override getLoginDeferred to get a handle to the deferred returned 250 from the PB server. 251 """ 252
253 - def __init__(self):
254 FPBClientFactory.__init__(self) 255 self._doingLogin = False 256 self._doingGetPerspective = False
257
258 - def clientConnectionFailed(self, connector, reason):
259 log.msg("connection failed to %s, reason %r" % ( 260 connector.getDestination(), reason)) 261 FPBClientFactory.clientConnectionFailed(self, connector, reason) 262 RCF = protocol.ReconnectingClientFactory 263 RCF.clientConnectionFailed(self, connector, reason) 264 if self.continueTrying: 265 self.debug("will try reconnect in %f seconds", self.delay) 266 else: 267 self.debug("not trying to reconnect")
268
269 - def clientConnectionLost(self, connector, reason):
270 log.msg("connection lost to %s, reason %r" % ( 271 connector.getDestination(), reason)) 272 FPBClientFactory.clientConnectionLost(self, connector, reason, 273 reconnecting=True) 274 RCF = protocol.ReconnectingClientFactory 275 RCF.clientConnectionLost(self, connector, reason)
276
277 - def clientConnectionMade(self, broker):
278 log.msg("connection made") 279 self.resetDelay() 280 FPBClientFactory.clientConnectionMade(self, broker) 281 if self._doingLogin: 282 d = self.login(self._authenticator) 283 self.gotDeferredLogin(d)
284 285 # TODO: This is a poorly named method; it just provides the appropriate 286 # authentication information, and doesn't actually _start_ login at all.
287 - def startLogin(self, authenticator):
288 assert not isinstance(authenticator, keycards.Keycard) 289 self._authenticator = authenticator 290 self._doingLogin = True
291 292 # methods to override
293 - def gotDeferredLogin(self, deferred):
294 """ 295 The deferred from login is now available. 296 """ 297 raise NotImplementedError
298 299 ### FIXME: this code is an adaptation of twisted/spread/pb.py 300 # it allows you to login to a FPB server requesting interfaces other than 301 # IPerspective. 302 # in other terms, you can request different "kinds" of avatars from the same 303 # PB server. 304 # this code needs to be sent upstream to Twisted
305 -class _FPortalRoot:
306 """ 307 Root object, used to login to bouncer. 308 """ 309 310 implements(flavors.IPBRoot) 311
312 - def __init__(self, bouncerPortal):
313 """ 314 @type bouncerPortal: L{flumotion.twisted.portal.BouncerPortal} 315 """ 316 self.bouncerPortal = bouncerPortal
317
318 - def rootObject(self, broker):
319 return _BouncerWrapper(self.bouncerPortal, broker)
320
321 -class _BouncerWrapper(pb.Referenceable, flog.Loggable):
322 323 logCategory = "_BouncerWrapper" 324
325 - def __init__(self, bouncerPortal, broker):
326 self.bouncerPortal = bouncerPortal 327 self.broker = broker
328
329 - def remote_getKeycardClasses(self):
330 """ 331 @returns: the fully-qualified class names of supported keycard 332 interfaces 333 @rtype: L{twisted.internet.defer.Deferred} firing list of str 334 """ 335 return self.bouncerPortal.getKeycardClasses()
336
337 - def remote_login(self, keycard, mind, *interfaces):
338 """ 339 Start of keycard login. 340 341 @param interfaces: list of fully qualified names of interface objects 342 343 @returns: one of 344 - a L{flumotion.common.keycards.Keycard} when more steps 345 need to be performed 346 - a L{twisted.spread.pb.AsReferenceable} when authentication 347 has succeeded, which will turn into a 348 L{twisted.spread.pb.RemoteReference} on the client side 349 - a L{flumotion.common.errors.NotAuthenticatedError} when 350 authentication is denied 351 """ 352 def loginResponse(result): 353 self.log("loginResponse: result=%r", result) 354 # if the result is a keycard, we're not yet ready 355 if isinstance(result, keycards.Keycard): 356 return result 357 else: 358 # authenticated, so the result is the tuple 359 interface, perspective, logout = result 360 self.broker.notifyOnDisconnect(logout) 361 return pb.AsReferenceable(perspective, "perspective")
362 363 # corresponds with FPBClientFactory._cbSendKeycard 364 self.log("remote_login(keycard=%s, *interfaces=%r" % (keycard, interfaces)) 365 interfaces = [freflect.namedAny(interface) for interface in interfaces] 366 d = self.bouncerPortal.login(keycard, mind, *interfaces) 367 d.addCallback(loginResponse) 368 return d
369
370 -class Authenticator(flog.Loggable, pb.Referenceable):
371 """ 372 I am an object used by FPB clients to create keycards for me 373 and respond to challenges. 374 375 I encapsulate keycard-related data, plus secrets which are used locally 376 and not put on the keycard. 377 378 I can be serialized over PB connections to a RemoteReference and then 379 adapted with RemoteAuthenticator to present the same interface. 380 381 @cvar username: a username to log in with 382 @type username: str 383 @cvar password: a password to log in with 384 @type password: str 385 @cvar address: an address to log in from 386 @type address: str 387 @cvar avatarId: the avatarId we want to request from the PB server 388 @type avatarId: str 389 """ 390 logCategory = "authenticator" 391 392 avatarId = None 393 394 username = None 395 password = None 396 address = None 397 ttl = 30 398 # FIXME: we can add ssh keys and similar here later on 399
400 - def __init__(self, **kwargs):
401 for key in kwargs: 402 setattr(self, key, kwargs[key])
403
404 - def issue(self, keycardClasses):
405 """ 406 Issue a keycard that implements one of the given interfaces. 407 408 @param keycardClasses: list of fully qualified keycard classes 409 @type keycardClasses: list of str 410 411 @rtype: L{twisted.internet.defer.Deferred} firing L{keycards.Keycard} 412 """ 413 # this method returns a deferred so we present the same interface 414 # as the RemoteAuthenticator adapter 415 416 # construct a list of keycard interfaces we can support right now 417 supported = [] 418 # address is allowed to be None 419 if self.username is not None and self.password is not None: 420 # We only want to support challenge-based keycards, for 421 # security. Maybe later we want this to be configurable 422 # supported.append(keycards.KeycardUACPP) 423 supported.append(keycards.KeycardUACPCC) 424 supported.append(keycards.KeycardUASPCC) 425 426 # expand to fully qualified names 427 supported = [reflect.qual(k) for k in supported] 428 429 for i in keycardClasses: 430 if i in supported: 431 self.log('Keycard interface %s supported, looking up', i) 432 name = i.split(".")[-1] 433 methodName = "issue_%s" % name 434 method = getattr(self, methodName) 435 keycard = method() 436 self.debug('Issuing keycard %r of class %s', keycard, 437 name) 438 keycard.avatarId = self.avatarId 439 if self.ttl is not None: 440 keycard.ttl = self.ttl 441 return defer.succeed(keycard) 442 443 self.debug('Could not issue a keycard') 444 return defer.succeed(None)
445 446 # non-challenge types
447 - def issue_KeycardUACPP(self):
448 return keycards.KeycardUACPP(self.username, self.password, 449 self.address)
450 451 # challenge types
452 - def issue_KeycardUACPCC(self):
453 return keycards.KeycardUACPCC(self.username, self.address)
454
455 - def issue_KeycardUASPCC(self):
456 return keycards.KeycardUASPCC(self.username, self.address)
457
458 - def respond(self, keycard):
459 """ 460 Respond to a challenge on the given keycard, based on the secrets 461 we have. 462 463 @param keycard: the keycard with the challenge to respond to 464 @type keycard: L{keycards.Keycard} 465 466 @rtype: L{twisted.internet.defer.Deferred} firing a {keycards.Keycard} 467 @returns: a deferred firing the keycard with a response set 468 """ 469 self.debug('responding to challenge on keycard %r' % keycard) 470 methodName = "respond_%s" % keycard.__class__.__name__ 471 method = getattr(self, methodName) 472 return defer.succeed(method(keycard))
473
474 - def respond_KeycardUACPCC(self, keycard):
475 self.log('setting password') 476 keycard.setPassword(self.password) 477 return keycard
478
479 - def respond_KeycardUASPCC(self, keycard):
480 self.log('setting password') 481 keycard.setPassword(self.password) 482 return keycard
483 484 ### pb.Referenceable methods
485 - def remote_issue(self, interfaces):
486 return self.issue(interfaces)
487
488 - def remote_respond(self, keycard):
489 return self.respond(keycard)
490
491 -class RemoteAuthenticator:
492 """ 493 I am an adapter for a pb.RemoteReference to present the same interface 494 as L{Authenticator} 495 """ 496 497 avatarId = None # not serialized 498 username = None # for convenience, will always be None 499 password = None # for convenience, will always be None 500
501 - def __init__(self, remoteReference):
502 self._remote = remoteReference
503
504 - def copy(self, avatarId=None):
505 ret = RemoteAuthenticator(self._remote) 506 ret.avatarId = avatarId or self.avatarId 507 return ret
508
509 - def issue(self, interfaces):
510 def issueCb(keycard): 511 keycard.avatarId = self.avatarId 512 return keycard
513 514 d = self._remote.callRemote('issue', interfaces) 515 d.addCallback(issueCb) 516 return d
517
518 - def respond(self, keycard):
519 return self._remote.callRemote('respond', keycard)
520 521
522 -class Referenceable(pb.Referenceable, flog.Loggable):
523 """ 524 @cvar remoteLogName: name to use to log the other side of the connection 525 @type remoteLogName: str 526 """ 527 logCategory = 'referenceable' 528 remoteLogName = 'remote' 529 530 531 # a referenceable that logs receiving remote messages
532 - def remoteMessageReceived(self, broker, message, args, kwargs):
533 args = broker.unserialize(args) 534 kwargs = broker.unserialize(kwargs) 535 method = getattr(self, "remote_%s" % message, None) 536 if method is None: 537 raise pb.NoSuchMethod("No such method: remote_%s" % (message,)) 538 539 level = flog.DEBUG 540 if message == 'ping': level = flog.LOG 541 542 debugClass = self.logCategory.upper() 543 # all this malarkey is to avoid actually interpolating variables 544 # if it is not needed 545 startArgs = [self.remoteLogName, debugClass, message] 546 format, debugArgs = flog.getFormatArgs( 547 '%s --> %s: remote_%s(', startArgs, 548 ')', (), args, kwargs) 549 # log going into the method 550 logKwArgs = self.doLog(level, method, format, *debugArgs) 551 552 # invoke the remote_ method 553 d = defer.maybeDeferred(method, *args, **kwargs) 554 555 # log coming out of the method 556 def callback(result): 557 format, debugArgs = flog.getFormatArgs( 558 '%s <-- %s: remote_%s(', startArgs, 559 '): %r', (flog.ellipsize(result), ), args, kwargs) 560 self.doLog(level, -1, format, *debugArgs, **logKwArgs) 561 return result
562 def errback(failure): 563 format, debugArgs = flog.getFormatArgs( 564 '%s <-- %s: remote_%s(', startArgs, 565 '): failure %r', (failure, ), args, kwargs) 566 self.doLog(level, -1, format, *debugArgs, **logKwArgs) 567 return failure
568 569 d.addCallbacks(callback, errback) 570 return broker.serialize(d, self.perspective) 571
572 -class Avatar(pb.Avatar, flog.Loggable):
573 """ 574 @cvar remoteLogName: name to use to log the other side of the connection 575 @type remoteLogName: str 576 """ 577 logCategory = 'avatar' 578 remoteLogName = 'remote' 579
580 - def __init__(self, avatarId):
581 self.avatarId = avatarId 582 self.logName = avatarId 583 self.mind = None 584 self.debug("created new Avatar with id %s", avatarId)
585 586 # a referenceable that logs receiving remote messages
587 - def perspectiveMessageReceived(self, broker, message, args, kwargs):
588 args = broker.unserialize(args) 589 kwargs = broker.unserialize(kwargs) 590 method = getattr(self, "perspective_%s" % message, None) 591 if method is None: 592 raise pb.NoSuchMethod("No such method: perspective_%s" % (message,)) 593 594 level = flog.DEBUG 595 if message == 'ping': level = flog.LOG 596 debugClass = self.logCategory.upper() 597 startArgs = [self.remoteLogName, debugClass, message] 598 format, debugArgs = flog.getFormatArgs( 599 '%s --> %s: perspective_%s(', startArgs, 600 ')', (), args, kwargs) 601 # log going into the method 602 logKwArgs = self.doLog(level, method, format, *debugArgs) 603 604 # invoke the perspective_ method 605 d = defer.maybeDeferred(method, *args, **kwargs) 606 607 # log coming out of the method 608 def callback(result): 609 format, debugArgs = flog.getFormatArgs( 610 '%s <-- %s: perspective_%s(', startArgs, 611 '): %r', (flog.ellipsize(result), ), args, kwargs) 612 self.doLog(level, -1, format, *debugArgs, **logKwArgs) 613 return result
614 def errback(failure): 615 format, debugArgs = flog.getFormatArgs( 616 '%s <-- %s: perspective_%s(', startArgs, 617 '): failure %r', (failure, ), args, kwargs) 618 self.doLog(level, -1, format, *debugArgs, **logKwArgs) 619 return failure
620 621 d.addCallbacks(callback, errback) 622 623 return broker.serialize(d, self, method, args, kwargs) 624
625 - def setMind(self, mind):
626 """ 627 Tell the avatar that the given mind has been attached. 628 This gives the avatar a way to call remotely to the client that 629 requested this avatar. 630 631 It is best to call setMind() from within the avatar's __init__ 632 method. Some old code still does this via a callLater, however. 633 634 @type mind: L{twisted.spread.pb.RemoteReference} 635 """ 636 self.mind = mind 637 def nullMind(x): 638 self.debug('%r: disconnected from %r' % (self, self.mind)) 639 self.mind = None
640 self.mind.notifyOnDisconnect(nullMind) 641 642 transport = self.mind.broker.transport 643 tarzan = transport.getHost() 644 jane = transport.getPeer() 645 if tarzan and jane: 646 self.debug("PB client connection seen by me is from me %s to %s" % ( 647 common.addressGetHost(tarzan), 648 common.addressGetHost(jane))) 649 self.log('Client attached is mind %s', mind) 650
651 - def mindCallRemoteLogging(self, level, stackDepth, name, *args, 652 **kwargs):
653 """ 654 Call the given remote method, and log calling and returning nicely. 655 656 @param level: the level we should log at (log.DEBUG, log.INFO, etc) 657 @type level: int 658 @param stackDepth: the number of stack frames to go back to get 659 file and line information, negative or zero. 660 @type stackDepth: non-positive int 661 @param name: name of the remote method 662 @type name: str 663 """ 664 if level is not None: 665 debugClass = str(self.__class__).split(".")[-1].upper() 666 startArgs = [self.remoteLogName, debugClass, name] 667 format, debugArgs = flog.getFormatArgs( 668 '%s --> %s: callRemote(%s, ', startArgs, 669 ')', (), args, kwargs) 670 logKwArgs = self.doLog(level, stackDepth - 1, format, 671 *debugArgs) 672 673 if not self.mind: 674 self.warning('Tried to mindCallRemote(%s), but we are ' 675 'disconnected', name) 676 return defer.fail(errors.NotConnectedError()) 677 678 def callback(result): 679 format, debugArgs = flog.getFormatArgs( 680 '%s <-- %s: callRemote(%s, ', startArgs, 681 '): %r', (flog.ellipsize(result), ), args, kwargs) 682 self.doLog(level, -1, format, *debugArgs, **logKwArgs) 683 return result
684 685 def errback(failure): 686 format, debugArgs = flog.getFormatArgs( 687 '%s <-- %s: callRemote(%s', startArgs, 688 '): %r', (failure, ), args, kwargs) 689 self.doLog(level, -1, format, *debugArgs, **logKwArgs) 690 return failure 691 692 d = self.mind.callRemote(name, *args, **kwargs) 693 if level is not None: 694 d.addCallbacks(callback, errback) 695 return d 696
697 - def mindCallRemote(self, name, *args, **kwargs):
698 """ 699 Call the given remote method, and log calling and returning nicely. 700 701 @param name: name of the remote method 702 @type name: str 703 """ 704 return self.mindCallRemoteLogging(flog.DEBUG, -1, name, *args, 705 **kwargs)
706
707 - def disconnect(self):
708 """ 709 Disconnect the remote PB client. If we are already disconnected, 710 do nothing. 711 """ 712 if self.mind: 713 return self.mind.broker.transport.loseConnection()
714
715 -class PingableAvatar(Avatar):
716 _pingCheckInterval = configure.heartbeatInterval * 2.5 717
718 - def perspective_ping(self):
719 self._lastPing = time.time() 720 return defer.succeed(True)
721
722 - def startPingChecking(self, disconnect):
723 self._lastPing = time.time() 724 self._pingCheckDisconnect = disconnect 725 self._pingCheck()
726
727 - def _pingCheck(self):
728 self._pingCheckDC = None 729 if time.time() - self._lastPing > self._pingCheckInterval: 730 self.info('no ping in %f seconds, closing connection', 731 self._pingCheckInterval) 732 self._pingCheckDisconnect() 733 else: 734 self._pingCheckDC = reactor.callLater(self._pingCheckInterval, 735 self._pingCheck)
736
737 - def stopPingChecking(self):
738 if self._pingCheckDC: 739 self._pingCheckDC.cancel() 740 self._pingCheckDC = None
741
742 - def setMind(self, mind):
743 # chain up 744 Avatar.setMind(self, mind) 745 746 def stopPingCheckingCb(x): 747 self.debug('stop pinging') 748 self.stopPingChecking()
749 self.mind.notifyOnDisconnect(stopPingCheckingCb) 750 751 # Now we have a remote reference, so start checking pings. 752 def _disconnect(): 753 if self.mind: 754 self.mind.broker.transport.loseConnection()
755 self.startPingChecking(_disconnect) 756