added files to config services, added default logic for creating files from templates, added new method to provide extra data to templates, updated gui to display templates raw

This commit is contained in:
Blake Harnden 2020-01-20 15:02:04 -08:00
parent 9447ddb94f
commit 7b5df11dc7
11 changed files with 120 additions and 52 deletions

View file

@ -15,6 +15,8 @@ from core.api.grpc import core_pb2, core_pb2_grpc
from core.api.grpc.configservices_pb2 import ( from core.api.grpc.configservices_pb2 import (
GetConfigServicesRequest, GetConfigServicesRequest,
GetConfigServicesResponse, GetConfigServicesResponse,
GetConfigServiceTemplatesRequest,
GetConfigServiceTemplatesResponse,
GetNodeConfigServiceRequest, GetNodeConfigServiceRequest,
GetNodeConfigServiceResponse, GetNodeConfigServiceResponse,
GetNodeConfigServicesRequest, GetNodeConfigServicesRequest,
@ -1092,6 +1094,12 @@ class CoreGrpcClient:
request = GetConfigServicesRequest() request = GetConfigServicesRequest()
return self.stub.GetConfigServices(request) 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( def get_node_config_service(
self, session_id: int, node_id: int, name: str self, session_id: int, node_id: int, name: str
) -> GetNodeConfigServiceResponse: ) -> GetNodeConfigServiceResponse:

View file

@ -5,6 +5,7 @@ import re
import tempfile import tempfile
import time import time
from concurrent import futures from concurrent import futures
from typing import Type
import grpc import grpc
from grpc import ServicerContext from grpc import ServicerContext
@ -14,6 +15,8 @@ from core.api.grpc.configservices_pb2 import (
ConfigService, ConfigService,
GetConfigServicesRequest, GetConfigServicesRequest,
GetConfigServicesResponse, GetConfigServicesResponse,
GetConfigServiceTemplatesRequest,
GetConfigServiceTemplatesResponse,
GetNodeConfigServiceRequest, GetNodeConfigServiceRequest,
GetNodeConfigServiceResponse, GetNodeConfigServiceResponse,
GetNodeConfigServicesRequest, GetNodeConfigServicesRequest,
@ -112,9 +115,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
except CoreError: except CoreError:
context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found") context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found")
def validate_service(self, name: str, context: ServicerContext) -> None: def validate_service(
if name not in self.coreemu.service_manager.services: 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}") context.abort(grpc.StatusCode.NOT_FOUND, f"unknown service {name}")
return service
def StartSession( def StartSession(
self, request: core_pb2.StartSessionRequest, context: ServicerContext self, request: core_pb2.StartSessionRequest, context: ServicerContext
@ -1456,6 +1463,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
executables=service.executables, executables=service.executables,
dependencies=service.dependencies, dependencies=service.dependencies,
directories=service.directories, directories=service.directories,
files=service.files,
startup=service.startup, startup=service.startup,
validate=service.validate, validate=service.validate,
shutdown=service.shutdown, shutdown=service.shutdown,
@ -1481,6 +1489,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config = {x.id: x.default for x in service.default_configs} config = {x.id: x.default for x in service.default_configs}
return GetNodeConfigServiceResponse(config=config) 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( def GetNodeConfigServices(
self, request: GetNodeConfigServicesRequest, context: ServicerContext self, request: GetNodeConfigServicesRequest, context: ServicerContext
) -> GetNodeConfigServicesResponse: ) -> GetNodeConfigServicesResponse:

View file

@ -55,6 +55,11 @@ class ConfigService(abc.ABC):
def directories(self) -> List[str]: def directories(self) -> List[str]:
raise NotImplementedError raise NotImplementedError
@property
@abc.abstractmethod
def files(self) -> List[str]:
raise NotImplementedError
@property @property
@abc.abstractmethod @abc.abstractmethod
def default_configs(self) -> List[Configuration]: def default_configs(self) -> List[Configuration]:
@ -120,8 +125,34 @@ class ConfigService(abc.ABC):
f"failure to create service directory: {directory}" 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: 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: def run_startup(self) -> None:
for cmd in self.startup: 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: def render_text(self, name: str, text: str, data: Dict[str, Any] = None) -> None:
try: try:
text = inspect.cleandoc(text)
template = Template(text) template = Template(text)
self._render(name, template, data) self._render(name, template, data)
except Exception: except Exception:

View file

@ -7,6 +7,7 @@ class VpnClient(ConfigService):
name = "VPNClient" name = "VPNClient"
group = GROUP_NAME group = GROUP_NAME
directories = [] directories = []
files = ["vpnclient.sh"]
executables = ["openvpn", "ip", "killall"] executables = ["openvpn", "ip", "killall"]
dependencies = [] dependencies = []
startup = ["sh vpnclient.sh"] startup = ["sh vpnclient.sh"]
@ -14,6 +15,3 @@ class VpnClient(ConfigService):
shutdown = ["killall openvpn"] shutdown = ["killall openvpn"]
validation_mode = ConfigServiceMode.BLOCKING validation_mode = ConfigServiceMode.BLOCKING
default_configs = [] default_configs = []
def create_files(self):
self.render_template("vpnclient.sh")

View file

@ -1,3 +1,5 @@
from typing import Any, Dict
import netaddr import netaddr
from core import utils from core import utils
@ -12,6 +14,7 @@ class DefaultRoute(ConfigService):
name = "DefaultRoute" name = "DefaultRoute"
group = GROUP_NAME group = GROUP_NAME
directories = [] directories = []
files = ["defaultroute.sh"]
executables = ["ip"] executables = ["ip"]
dependencies = [] dependencies = []
startup = ["sh defaultroute.sh"] startup = ["sh defaultroute.sh"]
@ -24,7 +27,7 @@ class DefaultRoute(ConfigService):
Configuration(_id="value3", _type=ConfigDataTypes.STRING, label="Value 3"), Configuration(_id="value3", _type=ConfigDataTypes.STRING, label="Value 3"),
] ]
def create_files(self): def data(self) -> Dict[str, Any]:
addresses = [] addresses = []
for netif in self.node.netifs(): for netif in self.node.netifs():
if getattr(netif, "control", False): if getattr(netif, "control", False):
@ -33,14 +36,14 @@ class DefaultRoute(ConfigService):
net = netaddr.IPNetwork(addr) net = netaddr.IPNetwork(addr)
if net[1] != net[-2]: if net[1] != net[-2]:
addresses.append(net[1]) addresses.append(net[1])
data = dict(addresses=addresses) return dict(addresses=addresses)
self.render_template("defaultroute.sh", data)
class IpForwardService(ConfigService): class IpForwardService(ConfigService):
name = "IPForward" name = "IPForward"
group = GROUP_NAME group = GROUP_NAME
directories = [] directories = []
files = ["ipforward.sh"]
executables = ["sysctl"] executables = ["sysctl"]
dependencies = [] dependencies = []
startup = ["sh ipforward.sh"] startup = ["sh ipforward.sh"]
@ -49,10 +52,9 @@ class IpForwardService(ConfigService):
validation_mode = ConfigServiceMode.BLOCKING validation_mode = ConfigServiceMode.BLOCKING
default_configs = [] default_configs = []
def create_files(self) -> None: def data(self) -> Dict[str, Any]:
devnames = [] devnames = []
for ifc in self.node.netifs(): for ifc in self.node.netifs():
devname = utils.sysctl_devname(ifc.name) devname = utils.sysctl_devname(ifc.name)
devnames.append(devname) devnames.append(devname)
data = dict(devnames=devnames) return dict(devnames=devnames)
self.render_template("ipforward.sh", data)

View file

@ -5,6 +5,7 @@ class SimpleService(ConfigService):
name = "Simple" name = "Simple"
group = "SimpleGroup" group = "SimpleGroup"
directories = [] directories = []
files = ["test1.sh"]
executables = [] executables = []
dependencies = [] dependencies = []
startup = [] startup = []
@ -13,10 +14,10 @@ class SimpleService(ConfigService):
validation_mode = ConfigServiceMode.BLOCKING validation_mode = ConfigServiceMode.BLOCKING
default_configs = [] default_configs = []
def create_files(self): def get_text(self, name: str) -> str:
text = """ if name == "test1.sh":
# sample script return """
# node id(${node.id}) name(${node.name}) # sample script
echo hello # node id(${node.id}) name(${node.name})
""" echo hello
self.render_text("test1.sh", text) """

View file

@ -63,6 +63,7 @@ class CoreClient:
self.app = app self.app = app
self.master = app.master self.master = app.master
self.services = {} self.services = {}
self.config_services_groups = {}
self.config_services = {} self.config_services = {}
self.default_services = {} self.default_services = {}
self.emane_models = [] self.emane_models = []
@ -417,7 +418,10 @@ class CoreClient:
# get config service informations # get config service informations
response = self.client.get_config_services() response = self.client.get_config_services()
for service in response.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) group_services.add(service.name)
# if there are no sessions, create a new session, else join a session # if there are no sessions, create a new session, else join a session

View file

@ -46,7 +46,7 @@ class ConfigServiceConfigDialog(Dialog):
self.default_shutdown = [] self.default_shutdown = []
self.validation_mode = None self.validation_mode = None
self.validation_time = None self.validation_time = None
self.validation_period = None self.validation_period = tk.StringVar()
self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16) self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16)
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16) self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16)
@ -68,35 +68,33 @@ class ConfigServiceConfigDialog(Dialog):
def load(self): def load(self):
try: try:
self.app.core.create_nodes_and_links() self.app.core.create_nodes_and_links()
default_config = self.app.core.get_node_service( # default_config = self.app.core.get_node_service(
self.node_id, self.service_name # self.node_id, self.service_name
) # )
self.default_startup = default_config.startup[:] # self.default_startup = default_config.startup[:]
self.default_validate = default_config.validate[:] # self.default_validate = default_config.validate[:]
self.default_shutdown = default_config.shutdown[:] # self.default_shutdown = default_config.shutdown[:]
custom_configs = self.service_configs # custom_configs = self.service_configs
if ( # if (
self.node_id in custom_configs # self.node_id in custom_configs
and self.service_name in custom_configs[self.node_id] # and self.service_name in custom_configs[self.node_id]
): # ):
service_config = custom_configs[self.node_id][self.service_name] # service_config = custom_configs[self.node_id][self.service_name]
else: # else:
service_config = default_config # service_config = default_config
service_config = self.core.config_services[self.service_name]
self.dependencies = service_config.dependencies[:] self.dependencies = service_config.dependencies[:]
self.executables = service_config.executables[:] self.executables = service_config.executables[:]
self.filenames = service_config.configs[:] self.filenames = service_config.files[:]
self.startup_commands = service_config.startup[:] self.startup_commands = service_config.startup[:]
self.validation_commands = service_config.validate[:] self.validation_commands = service_config.validate[:]
self.shutdown_commands = service_config.shutdown[:] self.shutdown_commands = service_config.shutdown[:]
self.validation_mode = service_config.validation_mode self.validation_mode = service_config.validation_mode
self.validation_time = service_config.validation_timer self.validation_time = service_config.validation_timer
self.original_service_files = { self.validation_period.set(service_config.validation_period)
x: self.app.core.get_node_service_file( response = self.core.client.get_config_service_templates(self.service_name)
self.node_id, self.service_name, x self.original_service_files = response.templates
)
for x in self.filenames
}
self.temp_service_files = dict(self.original_service_files) self.temp_service_files = dict(self.original_service_files)
file_configs = self.file_configs file_configs = self.file_configs
if ( if (
@ -303,7 +301,7 @@ class ConfigServiceConfigDialog(Dialog):
label = ttk.Label(frame, text="Validation Period") label = ttk.Label(frame, text="Validation Period")
label.grid(row=2, column=0, sticky="w", padx=PADX) label.grid(row=2, column=0, sticky="w", padx=PADX)
self.validation_period_entry = ttk.Entry( 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) self.validation_period_entry.grid(row=2, column=1, sticky="ew", pady=PADY)

View file

@ -51,7 +51,7 @@ class NodeConfigServiceDialog(Dialog):
label_frame.columnconfigure(0, weight=1) label_frame.columnconfigure(0, weight=1)
self.groups = ListboxScroll(label_frame) self.groups = ListboxScroll(label_frame)
self.groups.grid(sticky="nsew") 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.insert(tk.END, group)
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change) self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
self.groups.listbox.selection_set(0) self.groups.listbox.selection_set(0)
@ -98,7 +98,7 @@ class NodeConfigServiceDialog(Dialog):
index = selection[0] index = selection[0]
group = self.groups.listbox.get(index) group = self.groups.listbox.get(index)
self.services.clear() 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 checked = name in self.current_services
self.services.add(name, checked) self.services.add(name, checked)

View file

@ -16,12 +16,13 @@ message ConfigService {
repeated string executables = 3; repeated string executables = 3;
repeated string dependencies = 4; repeated string dependencies = 4;
repeated string directories = 5; repeated string directories = 5;
repeated string startup = 6; repeated string files = 6;
repeated string validate = 7; repeated string startup = 7;
repeated string shutdown = 8; repeated string validate = 8;
ConfigServiceValidationMode.Enum validation_mode = 9; repeated string shutdown = 9;
int32 validation_timer = 10; ConfigServiceValidationMode.Enum validation_mode = 10;
float validation_period = 11; int32 validation_timer = 11;
float validation_period = 12;
} }
message GetConfigServicesRequest { message GetConfigServicesRequest {
@ -32,6 +33,14 @@ message GetConfigServicesResponse {
repeated ConfigService services = 1; repeated ConfigService services = 1;
} }
message GetConfigServiceTemplatesRequest {
string name = 1;
}
message GetConfigServiceTemplatesResponse {
map<string, string> templates = 1;
}
message GetNodeConfigServiceRequest { message GetNodeConfigServiceRequest {
int32 session_id = 1; int32 session_id = 1;
int32 node_id = 2; int32 node_id = 2;

View file

@ -107,6 +107,8 @@ service CoreApi {
// config services // config services
rpc GetConfigServices (configservices.GetConfigServicesRequest) returns (configservices.GetConfigServicesResponse) { rpc GetConfigServices (configservices.GetConfigServicesRequest) returns (configservices.GetConfigServicesResponse) {
} }
rpc GetConfigServiceTemplates (configservices.GetConfigServiceTemplatesRequest) returns (configservices.GetConfigServiceTemplatesResponse) {
}
rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) { rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) {
} }
rpc GetNodeConfigServices (configservices.GetNodeConfigServicesRequest) returns (configservices.GetNodeConfigServicesResponse) { rpc GetNodeConfigServices (configservices.GetNodeConfigServicesRequest) returns (configservices.GetNodeConfigServicesResponse) {