Exemplo n.º 1
0
    def send(self, request: Event) -> ResultStatus:
        # Just check event name is correct
        if len(request.name) == 0:
            raise RpcException("Empty event name", ResultCode.ERROR_PARAM_MISSING)
        if " " in request.name:
            raise RpcException(f"Invalid event name: {request.name}", ResultCode.ERROR_PARAM_INVALID)

        return self.__internal_send(request)
Exemplo n.º 2
0
 def __check_proxy_names(
         self, request: Union[ProxyRegisterRequest,
                              Filter]) -> List[ServiceInfo]:
     # Verify input proxy names
     if len(request.names) == 0 or any(len(x) == 0 for x in request.names):
         raise RpcException("Missing input name in request",
                            ResultCode.ERROR_PARAM_MISSING)
     services = self.__check_service_names(request)
     if any(not srv.is_proxy for srv in services):
         raise RpcException(
             "At least one of the required services is not a proxy",
             ResultCode.ERROR_PARAM_INVALID)
     return services
Exemplo n.º 3
0
    def proxy_register(self, request: ProxyRegisterRequest) -> ResultStatus:
        # Verify input params
        services = self.__check_proxy_names(request)
        if len(request.version) == 0 or request.port == 0:
            raise RpcException(
                "Missing input parameter in request (one of version or port)",
                ResultCode.ERROR_PARAM_MISSING)

        # Ready to update service proxy info
        with self.lock:
            for srv in services:
                # Update info
                srv.version = request.version
                srv.proxy_port = request.port
                srv.proxy_host = request.host

            # Persist proxy info
            self.__persist_proxies()

        # Send proxy registration event
        if self.__can_send_events:  # pragma: no branch
            self.client.events.send(
                RpcEvent(
                    name="RPC_PROXY_REGISTER",
                    properties=[
                        EventProperty(name="names",
                                      value=",".join(request.names)),
                        EventProperty(name="version", value=request.version),
                        EventProperty(name="port", value=str(request.port)),
                        EventProperty(name="host", value=request.host),
                    ],
                ))

        return ResultStatus()
Exemplo n.º 4
0
 def __check_service_names(
         self, request: Union[ProxyRegisterRequest,
                              Filter]) -> List[ServiceInfo]:
     if any(n not in self.__info for n in request.names):
         raise RpcException(
             "At least one of the required service names is unknown",
             ResultCode.ERROR_ITEM_UNKNOWN)
     return list(map(lambda n: self.__info[n], request.names))
Exemplo n.º 5
0
    def interrupt(self, request: EventInterrupt) -> ResultStatus:
        # Known index?
        q = self.__get_queue(request.client_id)

        # Refuse to interrupt again already interrupted queue
        with self.lock:
            if request.client_id in self.__interrupt_times and self.__interrupt_times[request.client_id] is not None:
                raise RpcException(f"Already interrupted ID: {request.client_id}", ResultCode.ERROR_STATE_UNEXPECTED)

        # Put "None" event (will wait until listen loop is over)
        q.put(None)
        return ResultStatus()
Exemplo n.º 6
0
    def __init__(self, manager: RpcManager, stub: object, info: ServiceInfo,
                 server: object):
        # Filter on manager methods
        logger = manager.logger if not info.is_proxy else logging.getLogger(
            "RpcServer")
        logger.debug(f"Initializing RPC servicer for {info.name}")
        fake_stub = stub(insecure_channel("localhost:1"))
        for n in filter(
                lambda x: not x.startswith("_") and callable(
                    getattr(manager, x)) and hasattr(fake_stub, x),
                dir(manager)):
            method = getattr(manager, n)
            sig = inspect.signature(method)
            return_type = sig.return_annotation

            # Only methods with declared return type + one input parameter (and we're not registering a proxy)
            if return_type != inspect._empty and len(
                    sig.parameters) == 1 and not info.is_proxy:
                if return_type == Result:  # pragma: no cover
                    raise RpcException(
                        f"Can't declare {n} rpc with Result as a return type; please use Return Status instead",
                        ResultCode.ERROR_PARAM_INVALID)
                streaming = is_streaming(fake_stub, n)
                logger.debug(
                    f" >> add method {n} (returns {return_type}){' [streaming]' if streaming else ''}"
                )
                setattr(
                    self,
                    n,
                    RpcStreamingMethod(n, manager, None, return_type, info,
                                       server)
                    if streaming else RpcSimpleMethod(
                        n, manager, None, return_type, info, server),
                )
            # Otherwise, methods are coming from the parent stub
            else:
                if info.is_proxy:
                    # Register proxy method
                    streaming = is_streaming(fake_stub, n)
                    logger.debug(
                        f" >> add proxy method {n}{' [streaming]' if streaming else ''}"
                    )
                    setattr(
                        self, n,
                        RpcStreamingMethod(n, None, stub, None, info, server)
                        if streaming else RpcSimpleMethod(
                            n, None, stub, None, info, server))
                else:
                    # Register stub method (not defined in the manager - will raise an exception on runtime)
                    logger.debug(f" >> add stub method {n}")
                    setattr(self, n, method)
Exemplo n.º 7
0
 def __get_queue(self, index: int) -> Queue:
     with self.lock:
         q = self.__queues[index] if index in self.__queues else None
         if q is None:
             raise RpcException(f"Unknown event listening ID: {index}", ResultCode.ERROR_ITEM_UNKNOWN)
         return q
Exemplo n.º 8
0
    def delegate_call(self, request, context):
        # Verify API version
        metadata = RpcMetadata.from_context(context)
        client_version = None
        if len(metadata.api_version):
            client_version = int(metadata.api_version)
            if client_version > self.info.current_api_version:
                raise RpcException(
                    f"Server current API version ({self.info.current_api_version}) is too old for client API version ({client_version})",
                    rc=ResultCode.ERROR_API_SERVER_TOO_OLD,
                )
            elif client_version < self.info.supported_api_version:
                raise RpcException(
                    f"Client API version ({client_version}) is too old for server supported API version ({self.info.current_api_version})",
                    rc=ResultCode.ERROR_API_CLIENT_TOO_OLD,
                )

        # Proxy?
        if self.info.is_proxy:
            # Delegate to remote proxy, if port is set
            first_try = time.time()
            while self.info.proxy_port == 0:
                # Wait a bit more for proxy registration
                if (time.time() -
                        first_try) < RpcStaticConfig.CLIENT_TIMEOUT.float_val:
                    # Server is not available, and timeout didn't expired yet: sleep and retry
                    time.sleep(0.5)
                    self.logger.debug(
                        f"Proxy not registered yet for method {self.name}: wait a bit..."
                    )
                else:
                    # Timeout expired... notify the caller
                    raise RpcException(
                        "Proxy didn't registered in time for method call",
                        ResultCode.ERROR_PROXY_UNREGISTERED)

            # Reuse metadata from context
            client_meta = copy.copy(metadata)
            client_meta.client = f"{client_meta.client}(proxied)"

            # Temporary client to proxied server (that won't raise exceptions: let's simply forward the output message to final client)
            client = RpcClient(
                self.info.proxy_host if len(self.info.proxy_host) else
                RpcStaticConfig.MAIN_HOST.str_val,
                self.info.proxy_port,
                {
                    "stub": (self.stub, client_version if client_version
                             is not None else self.info.current_api_version)
                },
                name=client_meta,
                timeout=RpcStaticConfig.CLIENT_TIMEOUT.float_val,
                logger=self.logger,
                exception=False,
            )

            # Call remote method
            result_provider = getattr(client.stub, self.name)(request)
        else:
            # Not a proxy method, delegate to manager
            result_provider = self.manager_method(request)

        return result_provider
Exemplo n.º 9
0
    def __init__(
        self,
        port: int,
        descriptors: List[RpcServiceDescriptor],
        folders: Folders = None,
        cli_config: Dict[str, str] = None,
        static_items: List[Config] = None,
        user_items: List[Config] = None,
        with_events: bool = False,
        with_debug_signal: bool = True,
    ):
        RpcManager.__init__(self, PROXY_FILE)
        self.__port = port
        self.calls = []
        self.__shutdown_event = Event()

        # Prepare config manager
        # (by the way, add our own static items)
        config_m = ConfigManager(
            folders, cli_config, (static_items + [RpcStaticConfig])
            if static_items is not None else [RpcStaticConfig], user_items)

        # Create server instance, disabling port reuse
        self.__server = server(futures.ThreadPoolExecutor(
            max_workers=RpcStaticConfig.MAX_WORKERS.int_val),
                               options=[("grpc.so_reuseport", 0)])

        # Systematically add services:
        # - to handle server basic operations
        # - to handle remote user configuration update
        # - to handle remote loggers configuration update
        # - to handle events (if required)
        base_descriptors = [
            RpcServiceDescriptor(grpc_helper, "srv", ServerApiVersion, self,
                                 add_RpcServerServiceServicer_to_server,
                                 RpcServerServiceStub),
            RpcServiceDescriptor(grpc_helper, "config", ConfigApiVersion,
                                 config_m, add_ConfigServiceServicer_to_server,
                                 ConfigServiceStub),
            RpcServiceDescriptor(grpc_helper, "log", LoggerApiVersion,
                                 LogsManager(),
                                 add_LoggerServiceServicer_to_server,
                                 LoggerServiceStub),
        ]
        if with_events:
            base_descriptors.append(
                RpcServiceDescriptor(grpc_helper, "events", EventApiVersion,
                                     EventsManager(),
                                     add_EventServiceServicer_to_server,
                                     EventServiceStub))
        self.descriptors = {d.name: d for d in base_descriptors}

        # Get servicers
        self.descriptors.update({d.name: d for d in descriptors})

        # Load persisted proxies model
        proxies_model = self._load_config(folders.workspace)

        # Prepare auto clients stubs map
        stubs_map = {}

        # Register everything
        self.__info = {}
        for descriptor in self.descriptors.values():
            # Prepare folders and logger (only for non-proxy)
            if not descriptor.is_proxy:
                descriptor.manager._init_folders_n_logger(folders, port)

            # Persisted model?
            persisted_model = proxies_model[
                descriptor.name] if descriptor.name in proxies_model else None

            # Build info for service
            versions = list(descriptor.api_version.values())
            versions.remove(0)
            current_version = max(versions)
            info = ServiceInfo(
                name=descriptor.name,
                version=persisted_model[ProxyModel.VERSION]
                if persisted_model is not None else
                f"{descriptor.module.__title__}:{descriptor.module.__version__}",
                current_api_version=current_version,
                supported_api_version=min(versions),
                is_proxy=descriptor.is_proxy,
                proxy_port=persisted_model[ProxyModel.PORT]
                if persisted_model is not None else None,
                proxy_host=persisted_model[ProxyModel.HOST]
                if persisted_model is not None else None,
            )
            self.logger.debug(
                f"Registering service in RPC server: {trace_buffer(info)}")

            # Remember info
            self.__info[info.name] = info

            # Register servicer in RPC server
            descriptor.register_method(
                RpcServicer(descriptor.manager, descriptor.client_stub, info,
                            self), self.__server)

            # Contribute to auto-client stubs map
            stubs_map[descriptor.name] = (descriptor.client_stub,
                                          current_version)

        # Setup port and start
        self.logger.debug(f"Starting RPC server on port {self.__port}")
        try:
            self.__server.add_insecure_port(f"[::]:{self.__port}")
        except Exception as e:  # NOQA: B902
            msg = f"Failed to start RPC server on port {self.__port}: {e}"
            self.logger.error(msg)
            raise RpcException(msg, rc=ResultCode.ERROR_PORT_BUSY)
        self.__server.start()
        self.logger.debug(f"RPC server started on port {self.__port}")

        # Hook debug signal if required
        if with_debug_signal and os.name == "posix":
            signal.signal(signal.SIGUSR2, self.__dump_debug)

        # Load all managers (including client pointing to all served services)
        to_raise = None
        for descriptor in self.__real_descriptors:
            client = RpcClient(
                "localhost",
                self.__port,
                stubs_map,
                name=f"{descriptor.name}-client",
                timeout=RpcStaticConfig.CLIENT_TIMEOUT.float_val,
                logger=descriptor.manager.logger,
            )
            try:
                descriptor.manager._preload(client, folders)
            except Exception as e:
                self.logger.error(
                    f"Error occurred during {descriptor.name} manager loading: {e}\n"
                    + "".join(traceback.format_tb(e.__traceback__)))
                to_raise = e
                break

        # If something bad happened during managers loading, shutdown and raise exception
        if to_raise is not None:
            self.shutdown()
            raise to_raise