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