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