diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 8a52c5c6..297fb52d 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -15,6 +15,8 @@ from core.api.grpc import core_pb2, core_pb2_grpc from core.api.grpc.configservices_pb2 import ( GetConfigServicesRequest, GetConfigServicesResponse, + GetConfigServiceTemplatesRequest, + GetConfigServiceTemplatesResponse, GetNodeConfigServiceRequest, GetNodeConfigServiceResponse, GetNodeConfigServicesRequest, @@ -1092,6 +1094,12 @@ class CoreGrpcClient: request = GetConfigServicesRequest() return self.stub.GetConfigServices(request) + def get_config_service_templates( + self, name: str + ) -> GetConfigServiceTemplatesResponse: + request = GetConfigServiceTemplatesRequest(name=name) + return self.stub.GetConfigServiceTemplates(request) + def get_node_config_service( self, session_id: int, node_id: int, name: str ) -> GetNodeConfigServiceResponse: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index ed97eb34..dd5fcff6 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -5,6 +5,7 @@ import re import tempfile import time from concurrent import futures +from typing import Type import grpc from grpc import ServicerContext @@ -14,6 +15,8 @@ from core.api.grpc.configservices_pb2 import ( ConfigService, GetConfigServicesRequest, GetConfigServicesResponse, + GetConfigServiceTemplatesRequest, + GetConfigServiceTemplatesResponse, GetNodeConfigServiceRequest, GetNodeConfigServiceResponse, GetNodeConfigServicesRequest, @@ -112,9 +115,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): except CoreError: context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found") - def validate_service(self, name: str, context: ServicerContext) -> None: - if name not in self.coreemu.service_manager.services: + def validate_service( + self, name: str, context: ServicerContext + ) -> Type[ConfigService]: + service = self.coreemu.service_manager.services.get(name) + if not service: context.abort(grpc.StatusCode.NOT_FOUND, f"unknown service {name}") + return service def StartSession( self, request: core_pb2.StartSessionRequest, context: ServicerContext @@ -1456,6 +1463,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): executables=service.executables, dependencies=service.dependencies, directories=service.directories, + files=service.files, startup=service.startup, validate=service.validate, shutdown=service.shutdown, @@ -1481,6 +1489,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): config = {x.id: x.default for x in service.default_configs} return GetNodeConfigServiceResponse(config=config) + def GetConfigServiceTemplates( + self, request: GetConfigServiceTemplatesRequest, context: ServicerContext + ) -> GetConfigServiceTemplatesResponse: + service_class = self.validate_service(request.name, context) + service = service_class(None) + templates = service.get_templates() + return GetConfigServiceTemplatesResponse(templates=templates) + def GetNodeConfigServices( self, request: GetNodeConfigServicesRequest, context: ServicerContext ) -> GetNodeConfigServicesResponse: diff --git a/daemon/core/configservice/base.py b/daemon/core/configservice/base.py index b35307a0..ae273f77 100644 --- a/daemon/core/configservice/base.py +++ b/daemon/core/configservice/base.py @@ -55,6 +55,11 @@ class ConfigService(abc.ABC): def directories(self) -> List[str]: raise NotImplementedError + @property + @abc.abstractmethod + def files(self) -> List[str]: + raise NotImplementedError + @property @abc.abstractmethod def default_configs(self) -> List[Configuration]: @@ -120,8 +125,34 @@ class ConfigService(abc.ABC): f"failure to create service directory: {directory}" ) + def data(self) -> Dict[str, Any]: + return {} + + def get_text(self, name: str) -> str: + raise CoreError( + f"node({self.node.name} service({self.name}) unknown template({name})" + ) + + def get_templates(self) -> Dict[str, str]: + templates = {} + for name in self.files: + if self.templates.has_template(name): + template = self.templates.get_template(name).source + else: + template = self.get_text(name) + template = inspect.cleandoc(template) + templates[name] = template + return templates + def create_files(self) -> None: - raise NotImplementedError + data = self.data() + for name in self.files: + if self.templates.has_template(name): + self.render_template(name, data) + else: + text = self.get_text(name) + text = inspect.cleandoc(text) + self.render_text(name, text, data) def run_startup(self) -> None: for cmd in self.startup: @@ -176,7 +207,6 @@ class ConfigService(abc.ABC): def render_text(self, name: str, text: str, data: Dict[str, Any] = None) -> None: try: - text = inspect.cleandoc(text) template = Template(text) self._render(name, template, data) except Exception: diff --git a/daemon/core/configservices/sercurityservices/services.py b/daemon/core/configservices/sercurityservices/services.py index 6fd1fba0..a0a1aec4 100644 --- a/daemon/core/configservices/sercurityservices/services.py +++ b/daemon/core/configservices/sercurityservices/services.py @@ -7,6 +7,7 @@ class VpnClient(ConfigService): name = "VPNClient" group = GROUP_NAME directories = [] + files = ["vpnclient.sh"] executables = ["openvpn", "ip", "killall"] dependencies = [] startup = ["sh vpnclient.sh"] @@ -14,6 +15,3 @@ class VpnClient(ConfigService): shutdown = ["killall openvpn"] validation_mode = ConfigServiceMode.BLOCKING default_configs = [] - - def create_files(self): - self.render_template("vpnclient.sh") diff --git a/daemon/core/configservices/serviceutils/services.py b/daemon/core/configservices/serviceutils/services.py index d15c08c2..2b564ed4 100644 --- a/daemon/core/configservices/serviceutils/services.py +++ b/daemon/core/configservices/serviceutils/services.py @@ -1,3 +1,5 @@ +from typing import Any, Dict + import netaddr from core import utils @@ -12,6 +14,7 @@ class DefaultRoute(ConfigService): name = "DefaultRoute" group = GROUP_NAME directories = [] + files = ["defaultroute.sh"] executables = ["ip"] dependencies = [] startup = ["sh defaultroute.sh"] @@ -24,7 +27,7 @@ class DefaultRoute(ConfigService): Configuration(_id="value3", _type=ConfigDataTypes.STRING, label="Value 3"), ] - def create_files(self): + def data(self) -> Dict[str, Any]: addresses = [] for netif in self.node.netifs(): if getattr(netif, "control", False): @@ -33,14 +36,14 @@ class DefaultRoute(ConfigService): net = netaddr.IPNetwork(addr) if net[1] != net[-2]: addresses.append(net[1]) - data = dict(addresses=addresses) - self.render_template("defaultroute.sh", data) + return dict(addresses=addresses) class IpForwardService(ConfigService): name = "IPForward" group = GROUP_NAME directories = [] + files = ["ipforward.sh"] executables = ["sysctl"] dependencies = [] startup = ["sh ipforward.sh"] @@ -49,10 +52,9 @@ class IpForwardService(ConfigService): validation_mode = ConfigServiceMode.BLOCKING default_configs = [] - def create_files(self) -> None: + def data(self) -> Dict[str, Any]: devnames = [] for ifc in self.node.netifs(): devname = utils.sysctl_devname(ifc.name) devnames.append(devname) - data = dict(devnames=devnames) - self.render_template("ipforward.sh", data) + return dict(devnames=devnames) diff --git a/daemon/core/configservices/simpleservice.py b/daemon/core/configservices/simpleservice.py index 8fb40938..efee5275 100644 --- a/daemon/core/configservices/simpleservice.py +++ b/daemon/core/configservices/simpleservice.py @@ -5,6 +5,7 @@ class SimpleService(ConfigService): name = "Simple" group = "SimpleGroup" directories = [] + files = ["test1.sh"] executables = [] dependencies = [] startup = [] @@ -13,10 +14,10 @@ class SimpleService(ConfigService): validation_mode = ConfigServiceMode.BLOCKING default_configs = [] - def create_files(self): - text = """ - # sample script - # node id(${node.id}) name(${node.name}) - echo hello - """ - self.render_text("test1.sh", text) + def get_text(self, name: str) -> str: + if name == "test1.sh": + return """ + # sample script + # node id(${node.id}) name(${node.name}) + echo hello + """ diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 1e44715a..78eaecf3 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -63,6 +63,7 @@ class CoreClient: self.app = app self.master = app.master self.services = {} + self.config_services_groups = {} self.config_services = {} self.default_services = {} self.emane_models = [] @@ -417,7 +418,10 @@ class CoreClient: # get config service informations response = self.client.get_config_services() for service in response.services: - group_services = self.config_services.setdefault(service.group, set()) + self.config_services[service.name] = service + group_services = self.config_services_groups.setdefault( + service.group, set() + ) group_services.add(service.name) # if there are no sessions, create a new session, else join a session diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index b017212b..4c86dd3b 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -46,7 +46,7 @@ class ConfigServiceConfigDialog(Dialog): self.default_shutdown = [] self.validation_mode = None self.validation_time = None - self.validation_period = None + self.validation_period = tk.StringVar() self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16) self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16) @@ -68,35 +68,33 @@ class ConfigServiceConfigDialog(Dialog): def load(self): try: self.app.core.create_nodes_and_links() - default_config = self.app.core.get_node_service( - self.node_id, self.service_name - ) - self.default_startup = default_config.startup[:] - self.default_validate = default_config.validate[:] - self.default_shutdown = default_config.shutdown[:] - custom_configs = self.service_configs - if ( - self.node_id in custom_configs - and self.service_name in custom_configs[self.node_id] - ): - service_config = custom_configs[self.node_id][self.service_name] - else: - service_config = default_config + # default_config = self.app.core.get_node_service( + # self.node_id, self.service_name + # ) + # self.default_startup = default_config.startup[:] + # self.default_validate = default_config.validate[:] + # self.default_shutdown = default_config.shutdown[:] + # custom_configs = self.service_configs + # if ( + # self.node_id in custom_configs + # and self.service_name in custom_configs[self.node_id] + # ): + # service_config = custom_configs[self.node_id][self.service_name] + # else: + # service_config = default_config + service_config = self.core.config_services[self.service_name] self.dependencies = service_config.dependencies[:] self.executables = service_config.executables[:] - self.filenames = service_config.configs[:] + self.filenames = service_config.files[:] self.startup_commands = service_config.startup[:] self.validation_commands = service_config.validate[:] self.shutdown_commands = service_config.shutdown[:] self.validation_mode = service_config.validation_mode self.validation_time = service_config.validation_timer - self.original_service_files = { - x: self.app.core.get_node_service_file( - self.node_id, self.service_name, x - ) - for x in self.filenames - } + self.validation_period.set(service_config.validation_period) + response = self.core.client.get_config_service_templates(self.service_name) + self.original_service_files = response.templates self.temp_service_files = dict(self.original_service_files) file_configs = self.file_configs if ( @@ -303,7 +301,7 @@ class ConfigServiceConfigDialog(Dialog): label = ttk.Label(frame, text="Validation Period") label.grid(row=2, column=0, sticky="w", padx=PADX) self.validation_period_entry = ttk.Entry( - frame, state=tk.DISABLED, textvariable=tk.StringVar() + frame, state=tk.DISABLED, textvariable=self.validation_period ) self.validation_period_entry.grid(row=2, column=1, sticky="ew", pady=PADY) diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index 46897d60..e4aefe25 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -51,7 +51,7 @@ class NodeConfigServiceDialog(Dialog): label_frame.columnconfigure(0, weight=1) self.groups = ListboxScroll(label_frame) self.groups.grid(sticky="nsew") - for group in sorted(self.app.core.config_services): + for group in sorted(self.app.core.config_services_groups): self.groups.listbox.insert(tk.END, group) self.groups.listbox.bind("<>", self.handle_group_change) self.groups.listbox.selection_set(0) @@ -98,7 +98,7 @@ class NodeConfigServiceDialog(Dialog): index = selection[0] group = self.groups.listbox.get(index) self.services.clear() - for name in sorted(self.app.core.config_services[group]): + for name in sorted(self.app.core.config_services_groups[group]): checked = name in self.current_services self.services.add(name, checked) diff --git a/daemon/proto/core/api/grpc/configservices.proto b/daemon/proto/core/api/grpc/configservices.proto index a6d3e95a..4b744944 100644 --- a/daemon/proto/core/api/grpc/configservices.proto +++ b/daemon/proto/core/api/grpc/configservices.proto @@ -16,12 +16,13 @@ message ConfigService { repeated string executables = 3; repeated string dependencies = 4; repeated string directories = 5; - repeated string startup = 6; - repeated string validate = 7; - repeated string shutdown = 8; - ConfigServiceValidationMode.Enum validation_mode = 9; - int32 validation_timer = 10; - float validation_period = 11; + repeated string files = 6; + repeated string startup = 7; + repeated string validate = 8; + repeated string shutdown = 9; + ConfigServiceValidationMode.Enum validation_mode = 10; + int32 validation_timer = 11; + float validation_period = 12; } message GetConfigServicesRequest { @@ -32,6 +33,14 @@ message GetConfigServicesResponse { repeated ConfigService services = 1; } +message GetConfigServiceTemplatesRequest { + string name = 1; +} + +message GetConfigServiceTemplatesResponse { + map templates = 1; +} + message GetNodeConfigServiceRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 93435077..8f09317a 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -107,6 +107,8 @@ service CoreApi { // config services rpc GetConfigServices (configservices.GetConfigServicesRequest) returns (configservices.GetConfigServicesResponse) { } + rpc GetConfigServiceTemplates (configservices.GetConfigServiceTemplatesRequest) returns (configservices.GetConfigServiceTemplatesResponse) { + } rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) { } rpc GetNodeConfigServices (configservices.GetNodeConfigServicesRequest) returns (configservices.GetNodeConfigServicesResponse) {