def _handleSubscribe(self, payloadEnvelope: PayloadEnvelope, tupleSelector: TupleSelector, sendResponse: SendVortexMsgResponseCallable, vortexUuid: str): # Add support for just getting data, no subscription. cache = self._getCache(tupleSelector) if cache and cache.lastServerPayloadDate is not None and cache.cacheEnabled: respPayloadEnvelope = PayloadEnvelope(filt=payloadEnvelope.filt, encodedPayload=cache.encodedPayload, date=cache.lastServerPayloadDate) d = respPayloadEnvelope.toVortexMsgDefer() d.addCallback(sendResponse) d.addErrback(vortexLogFailure, logger, consumeError=True) elif cache: self._sendRequestToServer(payloadEnvelope) else: cache = self._makeCache(tupleSelector) self._sendRequestToServer(payloadEnvelope) cache.vortexUuids.add(vortexUuid) # Allow the cache to be disabled cache.cacheEnabled = ( cache.cacheEnabled and not payloadEnvelope.filt.get("disableCache", False) )
def _process(self, payloadEnvelope: PayloadEnvelope, vortexName: str, sendResponse: SendVortexMsgResponseCallable, **kwargs) -> None: # Ignore responses from the backend, these are handled by PayloadResponse if vortexName == self._proxyToVortexName: return # Shortcut the logic, so that we don't decode the payload unless we need to. if not self._delegateProcessor.delegateCount: yield self._processForProxy(payloadEnvelope, vortexName, sendResponse) return # If we have local processors, then work out if this tupleAction is meant for # the local processor. payload = yield payloadEnvelope.decodePayloadDefer() assert len(payload.tuples) == 1, ( "TupleActionProcessor:%s Expected 1 tuples, received %s" % (self._tupleActionProcessorName, len(payload.tuples))) tupleAction = payload.tuples[0] if self._delegateProcessor.hasDelegate(tupleAction.tupleName()): self._delegateProcessor._processTupleAction( payloadEnvelope.filt, sendResponse, tupleAction) return # Else, Just send it on to the delegate we're proxying for (the backend) yield self._processForProxy(payloadEnvelope, vortexName, sendResponse)
def _processCall(self, payloadEnvelope: PayloadEnvelope, vortexName, sendResponse, *args, **kwargs): """ Process Process the incoming RPC call payloads. """ # If the sending vortex, is local, then ignore it, RPC can not be called locally if VortexFactory.isVortexNameLocal(vortexName): logger.warning( "Received RPC call to %s, from local vortex %s, ignoring it", self.__funcName, vortexName) return # Apply the "allow" logic if self.__acceptOnlyFromVortex and vortexName not in self.__acceptOnlyFromVortex: logger.debug( "Call from non-accepted vortex %s, allowing only from %s", vortexName, str(self.__acceptOnlyFromVortex)) return # Get the args tuple payload = yield payloadEnvelope.decodePayloadDefer() argsTuple = payload.tuples[0] assert isinstance( argsTuple, _VortexRPCArgTuple), ("argsTuple is not an instance of %s" % _VortexRPCArgTuple) logger.debug("Received RPC call for %s", self.__funcName) # Call the method and setup the callbacks result = yield self.callLocally(argsTuple.args, argsTuple.kwargs) yield self._processCallCallback(result, sendResponse, payloadEnvelope.filt)
def _processResponseCallback(self, payloadEnvelope: PayloadEnvelope, stack): """ Process Response Callback Convert the PayloadResponse payload to the result from the remotely called method. """ if not payloadEnvelope.result in (None, True): return Failure(Exception( payloadEnvelope.result).with_traceback(stack), exc_tb=stack) # Get the Result from the payload payload = yield payloadEnvelope.decodePayloadDefer() resultTuple = payload.tuples[0] assert isinstance( resultTuple, _VortexRPCResultTuple), ("resultTuple is not an instance of %s" % _VortexRPCResultTuple) logger.debug("Received RPC result for %s", self.__funcName) # Return the remote result return resultTuple.result
def sendVortexMsg(self, vortexMsgs: Union[VortexMsgList, bytes, None] = None, vortexUuid: Optional[str] = None, priority: int = DEFAULT_PRIORITY): """ Send Vortex Msg Sends the vortex message to any conencted clients with vortexUuid. Or broadcast it to all connected vortex clients if it's None :param vortexMsgs: The vortex message to send :param vortexUuid: The vortexUuid of the client to send to. """ if vortexMsgs is None: vortexMsgs = [PayloadEnvelope().toVortexMsg()] if isMainThread(): return self._sendVortexMsgLater(vortexMsgs, vortexUuid=vortexUuid, priority=priority) return task.deferLater(reactor, 0, self._sendVortexMsgLater, vortexMsgs, vortexUuid=vortexUuid, priority=priority)
def _processUpdateFromBackend(self, payloadEnvelope: PayloadEnvelope): tupleSelector: TupleSelector = payloadEnvelope.filt["tupleSelector"] if not self._hasTupleSelector(tupleSelector): return cache, requiredUpdate = self._updateCache(payloadEnvelope) if not requiredUpdate: return # Get / update the list of observing UUIDs observingUuids = cache.vortexUuids & set(VortexFactory.getRemoteVortexUuids()) if not observingUuids: return # Create the vortexMsg vortexMsg = payloadEnvelope.toVortexMsg() # Send the vortex messages for vortexUuid in observingUuids: d = VortexFactory.sendVortexMsg(vortexMsgs=vortexMsg, destVortexUuid=vortexUuid) d.addErrback(vortexLogFailure, logger, consumeError=True)
def _processForProxy(self, payloadEnvelope: PayloadEnvelope, vortexName: str, sendResponse: SendVortexMsgResponseCallable, **kwargs): # Keep a copy of the incoming filt, in case they are using PayloadResponse responseFilt = copy(payloadEnvelope.filt) # Track the response, log an error if it fails # 5 Seconds is long enough. # VortexJS defaults to 10s, so we have some room for round trip time. pr = PayloadResponse( payloadEnvelope, timeout=PayloadResponse.TIMEOUT - 5, # 5 seconds less resultCheck=False, logTimeoutError=False) # This is not a lambda, so that it can have a breakpoint def reply(payloadEnvelope: PayloadEnvelope): payloadEnvelope.filt = responseFilt d: Deferred = payloadEnvelope.toVortexMsgDefer() d.addCallback(sendResponse) return d pr.addCallback(reply) pr.addCallback( lambda _: logger.debug("Received action response from server")) pr.addErrback(self.__handlePrFailure, payloadEnvelope, sendResponse) vortexMsg = yield payloadEnvelope.toVortexMsgDefer() try: yield VortexFactory.sendVortexMsg( vortexMsgs=vortexMsg, destVortexName=self._proxyToVortexName) except Exception as e: logger.exception(e)
def __initConnection(self): # self.transport.setBinaryMode(True) params = parse_qs(urlparse(self.transport.location).query) if 'vortexUuid' not in params or 'vortexName' not in params: raise Exception( "This isn't a vortex capable websocket. Check the URL") self._remoteVortexUuid = params['vortexUuid'][0] self._remoteVortexName = params['vortexName'][0] self._conn = VortexServerConnection(self._vortex, self._remoteVortexUuid, self._remoteVortexName, self._httpSession, self.transport, self._addr) # Send a heart beat down the new connection, tell it who we are. connectPayloadFilt = {} connectPayloadFilt[PayloadEnvelope.vortexUuidKey] = self._vortex.uuid() connectPayloadFilt[PayloadEnvelope.vortexNameKey] = self._vortex.name() self._conn.write( PayloadEnvelope(filt=connectPayloadFilt).toVortexMsg(), DEFAULT_PRIORITY) self._vortex.connectionOpened(self._httpSession, self._conn)
def __handlePrFailure(self, f: Failure, payloadEnvelope: PayloadEnvelope, sendResponse: SendVortexMsgResponseCallable): payload = yield payloadEnvelope.decodePayloadDefer() action = payload.tuples[0] if f.check(TimeoutError): logger.error("Received no response from\nprocessor %s\naction %s", self._filt, action) else: logger.error("Unexpected error, %s\nprocessor %s\naction %s", f, self._filt, action) vortexLogFailure(f, logger) vortexMsg = yield PayloadEnvelope(filt=payloadEnvelope.filt, result=str( f.value)).toVortexMsgDefer() sendResponse(vortexMsg)
def __init__(self, payloadEnvelope: PayloadEnvelope, destVortexName: Optional[str] = None, destVortexUuid: Optional[str] = None, timeout: Optional[float] = None, resultCheck=True, logTimeoutError=True) -> None: """ Constructor Tag and optionally send a payload. The timeout starts as soon as the constructor is called. :param payloadEnvelope The payloadEnvelope to send to the remote and, and wait for a response for :param destVortexName The name of the vortex to send to. :param destVortexUuid The UUID of the vortex to send a payload to. :param timeout The timeout to wait for a response :param resultCheck Should the response payload.result be checked, if it fails it errback will be called. """ Deferred.__init__(self) if not timeout: timeout = self.TIMEOUT self._resultCheck = resultCheck self._logTimeoutError = logTimeoutError # uuid4 can have collisions self._messageId = str(uuid4()) + str(PayloadResponse.__SEQ) PayloadResponse.__SEQ += 1 payloadEnvelope.filt[self.__messageIdKey] = self._messageId self._filt = copy(payloadEnvelope.filt) self._destVortexName = destVortexName self._status = self.PROCESSING self._date = datetime.now(pytz.utc) self._endpoint = PayloadEndpoint(self._filt, self._process) if destVortexName or destVortexUuid: d: Deferred = payloadEnvelope.toVortexMsgDefer() d.addCallback(VortexFactory.sendVortexMsg, destVortexName=destVortexName, destVortexUuid=destVortexUuid) d.addErrback(self.errback) try: raise Exception() except: self._stack = sys.exc_info()[2] # noinspection PyTypeChecker self.addTimeout(timeout, reactor) self.addErrback(self._timedOut)
def _errback(self, result: Failure, replyFilt: dict, tupleName: str, sendResponse: SendVortexMsgResponseCallable): logger.error("TupleActionProcessor:%s Failed to process TupleActon", self._tupleActionProcessorName) vortexLogFailure(result, logger) failureMessage = result.getErrorMessage() payloadEnvelope = PayloadEnvelope(filt=replyFilt, result=failureMessage) vortexMsg = yield payloadEnvelope.toVortexMsgDefer() try: yield sendResponse(vortexMsg) except Exception as e: logger.error("Failed to send TupleAction response for %s\n%s", tupleName, failureMessage) logger.exception(e)
def sendVortexMsgLocally(cls, vortexMsgs: Union[VortexMsgList, bytes], priority: int = DEFAULT_PRIORITY) -> Deferred: """ Send VortexMsg Sends a payload to the remote vortex. :param vortexMsgs: The vortex message(s) to deliver locally. :return: A C{Deferred} which will callback when the message has been delivered. """ yesMainThread() vortexUuid = "local" vortexName = "local" httpSession = "local" sendResponse = VortexFactory.sendVortexMsgLocally vortexMsgs = [vortexMsgs] if isinstance(vortexMsgs, bytes) else vortexMsgs def send(payloadEnvelope: PayloadEnvelope): try: PayloadIO().process(payloadEnvelope=payloadEnvelope, vortexUuid=vortexUuid, vortexName=vortexName, httpSession=httpSession, sendResponse=sendResponse) return succeed(True) except Exception as e: return Failure(e) deferreds = [] for vortexMsg in vortexMsgs: d = PayloadEnvelope().fromVortexMsgDefer(vortexMsg) d.addCallback(send) deferreds.append(d) return DeferredList(deferreds)
def _handlePoll(self, payloadEnvelope: PayloadEnvelope, tupleSelector: TupleSelector, sendResponse: SendVortexMsgResponseCallable): useCache = payloadEnvelope.filt.get('useCache', True) # Keep a copy of the incoming filt, in case they are using PayloadResponse responseFilt = copy(payloadEnvelope.filt) # Restore the original payload filt (PayloadResponse) and send it back def reply(payload): payload.filt = responseFilt d = payload.toVortexMsgDefer() d.addCallback(sendResponse) d.addErrback(vortexLogFailure, logger, consumeError=True) # logger.debug("Received response from observable") if useCache: cache = self._getCache(tupleSelector) if cache and cache.lastServerPayloadDate is not None and cache.cacheEnabled: payloadEnvelope.encodedPayload = cache.encodedPayload payloadEnvelope.date = cache.lastServerPayloadDate reply(payloadEnvelope) return # Track the response, log an error if it fails # 5 Seconds is long enough pr = PayloadResponse( payloadEnvelope, timeout=PayloadResponse.TIMEOUT - 5, # 5 seconds less logTimeoutError=False ) pr.addErrback(self._handlePrFailure, tupleSelector) pr.addErrback(vortexLogFailure, logger, consumeError=True) pr.addCallback(reply) self._sendRequestToServer(payloadEnvelope)
def connectionMade(self): self._producer = VortexWritePushProducer(self.transport, lambda: self.close()) # Register the producer if there isn't one already. if not self.transport.producer: self.transport.registerProducer(self._producer, True) # Send a heart beat down the new connection, tell it who we are. connectPayloadFilt = { PayloadEnvelope.vortexUuidKey: self._vortexClient.uuid, PayloadEnvelope.vortexNameKey: self._vortexClient.name } self._producer.write(PayloadEnvelope(filt=connectPayloadFilt).toVortexMsg(), DEFAULT_PRIORITY)
def _process(self, payloadEnvelope: PayloadEnvelope, sendResponse: SendVortexMsgResponseCallable, **kwargs): """ Process the Payload / Tuple Action """ payload = yield payloadEnvelope.decodePayloadDefer() assert len(payload.tuples) == 1, ( "TupleActionProcessor:%s Expected 1 tuples, received %s" % ( self._tupleActionProcessorName, len(payload.tuples))) tupleAction = payload.tuples[0] self._processTupleAction(payloadEnvelope.filt, sendResponse, tupleAction)
def respondToException(failure): """ Respond To Exception Putting the exception into a failure messes with the stack, hence the common function """ try: sendResponse( PayloadEnvelope(filt=payloadEnvelope.filt, result=str(failure.getTraceback())) .toVortexMsg() ) except Exception as e: logger.exception(e) vortexLogFailure(failure, logger) logger.error(payloadEnvelope.filt)
def _processTraceConfigPayload(self, payloadEnvelope: PayloadEnvelope, **kwargs): payload = yield payloadEnvelope.decodePayloadDefer() dataDict = payload.tuples[0] if payload.filt.get(plDeleteKey): modelSetKey = dataDict["modelSetKey"] traceConfigKeys = dataDict["traceConfigKeys"] self._removeTraceConfigFromCache(modelSetKey, traceConfigKeys) return modelSetKey = dataDict["modelSetKey"] traceConfigTuples: List[GraphDbTraceConfigTuple] = dataDict["tuples"] self._loadTraceConfigIntoCache(modelSetKey, traceConfigTuples)
def _processAddTestTask(self, tupleAction: AdminSendTestTaskActionTuple): """ Process Add Test Task """ vmsg = yield PayloadEnvelope().toVortexMsgDefer() from peek_plugin_inbox.server.InboxApiABC import NewTask newTask = NewTask(pluginName=inboxPluginName, **tupleAction.formData) newTask.overwriteExisting = True newTask.actions = [ NewTaskAction(onActionPayloadEnvelope=vmsg, **a) for a in tupleAction.formData['actions'] ] yield self._thisPluginsApi.addTask(newTask) return []
def _sendVortexMsgLater(self, vortexMsgs: Union[VortexMsgList, bytes], vortexUuid: Optional[str], priority: int): """ Send the message. Send it later, This also means it doesn't matter what thread this is called from """ yield None if not isinstance(vortexMsgs, list): vortexMsgs = [vortexMsgs] # Deliver locally if vortexUuid == self._uuid: for vortexMsg in vortexMsgs: def sendResponse(vortexMsg_, priority_=DEFAULT_PRIORITY): self._sendVortexMsgLater(vortexMsg_, self._uuid, priority_) def cb(payloadEnvelope: PayloadEnvelope) -> None: PayloadIO().process(payloadEnvelope, vortexUuid=self._uuid, vortexName=self._name, httpSession=None, sendResponse=sendResponse) d = PayloadEnvelope.fromVortexMsgDefer(vortexMsg) d.addCallback(cb) d.addErrback(vortexLogFailure, logger, consumeError=True) return from vortex.VortexServerConnection import VortexServerConnection conns: List[VortexServerConnection] = [] if vortexUuid is None: conns = list(self._connectionByVortexUuid.values()) elif vortexUuid in self._connectionByVortexUuid: conns.append(self._connectionByVortexUuid[vortexUuid]) for conn in conns: for vortexMsg in vortexMsgs: conn.write(vortexMsg, priority) return True
def _processObserve(self, payloadEnvelope: PayloadEnvelope, vortexUuid: str, sendResponse: SendVortexMsgResponseCallable, **kwargs): cacheAll = payloadEnvelope.filt.get("cacheAll") == True payload = yield payloadEnvelope.decodePayloadDefer() lastUpdateByGridKey: DeviceGridT = payload.tuples[0] if not cacheAll: gridKeys = list(lastUpdateByGridKey.keys()) self._observedGridKeysByVortexUuid[vortexUuid] = gridKeys self._rebuildStructs() self._replyToObserve(payload.filt, lastUpdateByGridKey, sendResponse, cacheAll=cacheAll)
def __init__(self, name: str) -> None: self._vortexName = name self._vortexUuid = str(uuid.uuid1()) self._server = None self._port = None self._retrying = False self._serverVortexUuid = None self._serverVortexName = None self._lastBeatReceiveTime = None self._lastHeartBeatCheckTime = datetime.now(pytz.utc) # Start our heart beat checker self._beatLoopingCall = task.LoopingCall(self._checkBeat) self._reconnectVortexMsgs = [PayloadEnvelope().toVortexMsg()] self.__protocol = None
def _processVortexMsgs(self): while self._vortexMsgsQueue: vortexMsg = self._vortexMsgsQueue.popleft() if b"." in vortexMsg: raise Exception( "Something went wrong, there is a '.' in the msg") try: payloadEnvelope = yield PayloadEnvelope().fromVortexMsgDefer( vortexMsg) if payloadEnvelope.isEmpty(): self._processServerInfoPayload(payloadEnvelope) else: self._deliverPayload(payloadEnvelope) except Exception as e: print(vortexMsg) print(e) self._logger.exception(e) raise
def render_POST(self, request): remoteVortexUuid = request.args[b'vortexUuid'][0].decode() remoteVortexName = request.args[b'vortexName'][0].decode() if self.__vortex.isShutdown(): return None httpSession = request.getSession() conn = VortexResourceConnection(self.__vortex, remoteVortexUuid, remoteVortexName, request) # Send a heart beat down the new connection, tell it who we are. connectPayloadFilt = {} connectPayloadFilt[ PayloadEnvelope.vortexUuidKey] = self.__vortex.uuid() connectPayloadFilt[ PayloadEnvelope.vortexNameKey] = self.__vortex.name() conn.write(PayloadEnvelope(filt=connectPayloadFilt).toVortexMsg()) data = request.content.read() if len(data): for vortexStr in data.strip(b'.').split(b'.'): self._processVortexMsg(httpSession, conn, vortexStr.decode("UTF-8")) # Request will be around for a while, do some cleanups request.content = BytesIO() request.args = {} self.__vortex.connectionOpened(httpSession, conn) def connClosed(err): logger.debug("VortexServer connection ended by client") self.__vortex.connectionClosed(conn) request.notifyFinish().addErrback(connClosed) return NOT_DONE_YET
def pollForTuples( self, tupleSelector: TupleSelector, logTimeoutError: bool = True ) -> Deferred: startFilt = copy(self._sendFilt) startFilt.update({"subscribe": False, "tupleSelector": tupleSelector}) def updateCacheCallback( payloadEnvelope: PayloadEnvelope, ) -> PayloadEnvelope: cache, _ = self._updateCache(payloadEnvelope) return payloadEnvelope pr = PayloadResponse( payloadEnvelope=PayloadEnvelope(startFilt), destVortexName=self._destVortexName, logTimeoutError=logTimeoutError, ) pr.addCallback(updateCacheCallback) pr.addCallback( lambda payloadEnvelope: payloadEnvelope.decodePayloadDefer() ) pr.addCallback(lambda payload: payload.tuples) return pr
def __init__(self, name: str) -> None: self._vortexName = name self._vortexUuid = str(uuid.uuid1()) self._server = None self._port = None self._retrying = False self._serverVortexUuid = None self._serverVortexName = None self._cookieJar = CookieJar() self._beatTime = None self._beatTimeout = 15.0 # Server beats at 5 seconds # Start our heart beat checker self._beatLoopingCall = task.LoopingCall(self._checkBeat) self._reconnectVortexMsgs = [PayloadEnvelope().toVortexMsg()] self.__protocol = None
def _processVortexMsg(self, vortexMsg: bytes): payloadEnvelope = yield PayloadEnvelope().fromVortexMsgDefer(vortexMsg) self._vortex.payloadReveived(httpSession=self._httpSession, vortexUuid=self._remoteVortexUuid, vortexName=self._remoteVortexName, payload=payloadEnvelope)
def _tellServerWeWantData(self, tupleSelectors: List[TupleSelector]): for tupleSelector in tupleSelectors: self._sendRequestToServer( PayloadEnvelope({"subscribe": True, "tupleSelector": tupleSelector}) )
def reply(payloadEnvelope: PayloadEnvelope): payloadEnvelope.filt = responseFilt d: Deferred = payloadEnvelope.toVortexMsgDefer() d.addCallback(sendResponse) return d
def _sendUnsubscribeToServer(self, tupleSelector: TupleSelector): payloadEnvelope = PayloadEnvelope() payloadEnvelope.filt["tupleSelector"] = tupleSelector payloadEnvelope.filt["unsubscribe"] = True self._sendRequestToServer(payloadEnvelope)
def _sendRequestToServer(self, payloadEnvelope: PayloadEnvelope): payloadEnvelope.filt["observerName"] = self._observerName d = VortexFactory.sendVortexMsg(vortexMsgs=payloadEnvelope.toVortexMsg(), destVortexName=self._proxyToVortexName) d.addErrback(vortexLogFailure, logger, consumeError=True)