def test_string_validation(self): # Default string validation ConfigManager(folders=self.folders, static_items=[ Config(name="some-string", default_value="Any default is OK") ])
def test_invalid_config_name(self): try: # Invalid config name ConfigManager(folders=self.folders, static_items=[Config(name="WithCapitals")]) raise AssertionError("Shouldn't get here") except RpcException as e: assert e.rc == ResultCode.ERROR_PARAM_INVALID
def test_folder_without_file_default(self): # A simple config manager -- shouldn't be disturbed is folder is non-null, but no config file inside ConfigManager(static_items=[SampleConfig], folders=Folders(workspace=self.workspace_path, system=Path("/missing/folder"))) # Check default value assert SampleConfig.INT_ITEM.int_val == 12
def test_system_default(self, system_config): # A simple config manager ConfigManager(static_items=[SampleConfig], folders=Folders(workspace=self.workspace_path, system=system_config)) # Check default value assert SampleConfig.INT_ITEM.int_val == -78
def test_invalid_default(self): # Try with invalid default value try: ConfigManager(folders=self.folders, static_items=[SampleConfig], cli_config={"my-int-config": "zzz"}) raise AssertionError("Shouldn't get here") except RpcException as e: assert e.rc == ResultCode.ERROR_PARAM_INVALID
def test_conflicting_configs(self): # Try with some conflicting items between statis and user lists try: ConfigManager( folders=self.folders, static_items=[SampleConfig], user_items=[Config(name="my-int-config", default_value="zzz")]) raise AssertionError("Shouldn't get here") except RpcException as e: assert e.rc == ResultCode.ERROR_MODEL_INVALID
def test_pos_int_validation(self): try: # Negative integer while expecting a positive one ConfigManager( folders=self.folders, static_items=[ Config(name="some-pos-int", validator=ConfigValidator.CONFIG_VALID_POS_INT, default_value="-69") ]) raise AssertionError("Shouldn't get here") except RpcException as e: assert e.rc == ResultCode.ERROR_PARAM_INVALID # Same with correct default value ConfigManager(static_items=[ Config(name="some-pos-int", validator=ConfigValidator.CONFIG_VALID_POS_INT, default_value="69") ])
def test_missing_custom_validator(self): try: # Missing custom validator in config definition ConfigManager( folders=self.folders, static_items=[ Config(name="ok", validator=ConfigValidator.CONFIG_VALID_CUSTOM) ]) raise AssertionError("Shouldn't get here") except RpcException as e: assert e.rc == ResultCode.ERROR_PARAM_MISSING
def test_cli_default(self, system_config, user_config, env_config): # A simple config manager ConfigManager( static_items=[SampleConfig], folders=Folders(workspace=self.workspace_path, system=system_config, user=user_config), cli_config={"my-int-config": "357"}, ) # Check default value assert SampleConfig.INT_ITEM.int_val == 357
def test_float_validation(self): try: # Non-float value ConfigManager(folders=self.folders, static_items=[ Config( name="some-float", validator=ConfigValidator.CONFIG_VALID_FLOAT, default_value="not a float") ]) raise AssertionError("Shouldn't get here") except RpcException as e: assert e.rc == ResultCode.ERROR_PARAM_INVALID
def test_invalid_json_model(self, system_config): # Dump non-object Json in config with (system_config / "config.json").open("w") as f: f.write('{"unclosed item') try: ConfigManager(static_items=[SampleConfig], folders=Folders(workspace=self.workspace_path, system=system_config)) raise AssertionError("Shouldn't get here") except RpcException as e: assert e.rc == ResultCode.ERROR_MODEL_INVALID # Dump object with non-string values Json in config with (system_config / "config.json").open("w") as f: json.dump({"some-int": 45}, f) try: ConfigManager(static_items=[SampleConfig], folders=Folders(workspace=self.workspace_path, system=system_config)) raise AssertionError("Shouldn't get here") except RpcException as e: assert e.rc == ResultCode.ERROR_MODEL_INVALID
def test_empty_default(self): # Config item with empty default cm = ConfigManager(folders=self.folders, static_items=[Config(name="ok", can_be_empty=True)]) assert cm.static_items["ok"].str_val == ""
def test_hard_coded_default(self): # A simple config manager ConfigManager(folders=self.folders, static_items=[SampleConfig]) # Check default value assert SampleConfig.INT_ITEM.int_val == 12
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