updates to support saving config services to xml, loading config services from xml, retrieving config services from coretk when joining a session

This commit is contained in:
Blake Harnden 2020-01-21 17:32:12 -08:00
parent 0ea2f73a80
commit d7d0a55fd2
9 changed files with 162 additions and 43 deletions

View file

@ -17,6 +17,8 @@ from core.api.grpc.configservices_pb2 import (
GetConfigServiceDefaultsResponse, GetConfigServiceDefaultsResponse,
GetConfigServicesRequest, GetConfigServicesRequest,
GetConfigServicesResponse, GetConfigServicesResponse,
GetNodeConfigServiceConfigsRequest,
GetNodeConfigServiceConfigsResponse,
GetNodeConfigServiceRequest, GetNodeConfigServiceRequest,
GetNodeConfigServiceResponse, GetNodeConfigServiceResponse,
GetNodeConfigServicesRequest, GetNodeConfigServicesRequest,
@ -1103,6 +1105,12 @@ class CoreGrpcClient:
request = GetConfigServiceDefaultsRequest(name=name) request = GetConfigServiceDefaultsRequest(name=name)
return self.stub.GetConfigServiceDefaults(request) return self.stub.GetConfigServiceDefaults(request)
def get_node_config_service_configs(
self, session_id: int
) -> GetNodeConfigServiceConfigsResponse:
request = GetNodeConfigServiceConfigsRequest(session_id=session_id)
return self.stub.GetNodeConfigServiceConfigs(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

@ -23,6 +23,8 @@ from core.api.grpc.configservices_pb2 import (
GetConfigServiceDefaultsResponse, GetConfigServiceDefaultsResponse,
GetConfigServicesRequest, GetConfigServicesRequest,
GetConfigServicesResponse, GetConfigServicesResponse,
GetNodeConfigServiceConfigsRequest,
GetNodeConfigServiceConfigsResponse,
GetNodeConfigServiceRequest, GetNodeConfigServiceRequest,
GetNodeConfigServiceResponse, GetNodeConfigServiceResponse,
GetNodeConfigServicesRequest, GetNodeConfigServicesRequest,
@ -45,7 +47,7 @@ from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags
from core.emulator.session import Session from core.emulator.session import Session
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import NodeBase from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode from core.nodes.docker import DockerNode
from core.nodes.lxd import LxcNode from core.nodes.lxd import LxcNode
from core.services.coreservices import ServiceManager from core.services.coreservices import ServiceManager
@ -192,7 +194,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if config.config: if config.config:
service.set_config(config.config) service.set_config(config.config)
for name, template in config.templates.items(): for name, template in config.templates.items():
service.custom_template(name, template) service.set_template(name, template)
# service file configs # service file configs
for config in request.service_file_configs: for config in request.service_file_configs:
@ -463,6 +465,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if services is None: if services is None:
services = [] services = []
services = [x.name for x in services] services = [x.name for x in services]
config_services = getattr(node, "config_services", {})
config_services = [x for x in config_services]
emane_model = None emane_model = None
if isinstance(node, EmaneNet): if isinstance(node, EmaneNet):
emane_model = node.model.name emane_model = node.model.name
@ -478,6 +482,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
services=services, services=services,
icon=node.icon, icon=node.icon,
image=image, image=image,
config_services=config_services,
) )
if isinstance(node, (DockerNode, LxcNode)): if isinstance(node, (DockerNode, LxcNode)):
node_proto.image = node.image node_proto.image = node.image
@ -1528,6 +1533,27 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
templates=templates, config=config, modes=modes templates=templates, config=config, modes=modes
) )
def GetNodeConfigServiceConfigs(
self, request: GetNodeConfigServiceConfigsRequest, context: ServicerContext
) -> GetNodeConfigServiceConfigsResponse:
session = self.get_session(request.session_id, context)
configs = []
for node in session.nodes.values():
if not isinstance(node, CoreNodeBase):
continue
for name, service in node.config_services.items():
if not service.custom_templates and not service.custom_config:
continue
config_proto = configservices_pb2.ConfigServiceConfig(
node_id=node.id,
name=name,
templates=service.custom_templates,
config=service.custom_config,
)
configs.append(config_proto)
return GetNodeConfigServiceConfigsResponse(configs=configs)
def GetNodeConfigServices( def GetNodeConfigServices(
self, request: GetNodeConfigServicesRequest, context: ServicerContext self, request: GetNodeConfigServicesRequest, context: ServicerContext
) -> GetNodeConfigServicesResponse: ) -> GetNodeConfigServicesResponse:

View file

@ -38,6 +38,7 @@ class ConfigService(abc.ABC):
self.templates = TemplateLookup(directories=templates_path) self.templates = TemplateLookup(directories=templates_path)
self.config = {} self.config = {}
self.custom_templates = {} self.custom_templates = {}
self.custom_config = {}
configs = self.default_configs[:] configs = self.default_configs[:]
self._define_config(configs) self._define_config(configs)
@ -134,7 +135,7 @@ class ConfigService(abc.ABC):
def data(self) -> Dict[str, Any]: def data(self) -> Dict[str, Any]:
return {} return {}
def custom_template(self, name: str, template: str) -> None: def set_template(self, name: str, template: str) -> None:
self.custom_templates[name] = template self.custom_templates[name] = template
def get_text(self, name: str) -> str: def get_text(self, name: str) -> str:
@ -248,11 +249,13 @@ class ConfigService(abc.ABC):
self.config[config.id] = config self.config[config.id] = config
def render_config(self) -> Dict[str, str]: def render_config(self) -> Dict[str, str]:
return {k: v.default for k, v in self.config.items()} if self.custom_config:
return self.custom_config
else:
return {k: v.default for k, v in self.config.items()}
def set_config(self, data: Dict[str, str]) -> None: def set_config(self, data: Dict[str, str]) -> None:
for key, value in data.items(): for key, value in data.items():
config = self.config.get(key) if key not in self.config:
if config is None:
raise CoreError(f"unknown config: {key}") raise CoreError(f"unknown config: {key}")
config.default = value self.custom_config[key] = value

View file

@ -297,6 +297,18 @@ class CoreClient:
data = config.files[file_name] data = config.files[file_name]
files[file_name] = data files[file_name] = data
# get config service configurations
response = self.client.get_node_config_service_configs(self.session_id)
for config in response.configs:
node_configs = self.config_service_configs.setdefault(
config.node_id, {}
)
service_config = node_configs.setdefault(config.name, {})
if config.templates:
service_config["templates"] = config.templates
if config.config:
service_config["config"] = config.config
# draw session # draw session
self.app.canvas.reset_and_redraw(session) self.app.canvas.reset_and_redraw(session)
@ -880,14 +892,11 @@ class CoreClient:
for node_id, node_config in self.config_service_configs.items(): for node_id, node_config in self.config_service_configs.items():
for name, service_config in node_config.items(): for name, service_config in node_config.items():
config = service_config.get("config", {}) config = service_config.get("config", {})
config_values = {}
for key, option in config.items():
config_values[key] = option.value
config_proto = configservices_pb2.ConfigServiceConfig( config_proto = configservices_pb2.ConfigServiceConfig(
node_id=node_id, node_id=node_id,
name=name, name=name,
templates=service_config["templates"], templates=service_config["templates"],
config=config_values, config=config,
) )
config_service_protos.append(config_proto) config_service_protos.append(config_proto)
return config_service_protos return config_service_protos

View file

@ -93,15 +93,17 @@ class ConfigServiceConfigDialog(Dialog):
node_configs = self.service_configs.get(self.node_id, {}) node_configs = self.service_configs.get(self.node_id, {})
service_config = node_configs.get(self.service_name, {}) service_config = node_configs.get(self.service_name, {})
self.default_config = response.config self.config = response.config
self.default_config = {x.name: x.value for x in self.config.values()}
custom_config = service_config.get("config") custom_config = service_config.get("config")
if custom_config: if custom_config:
self.config = custom_config for key, value in custom_config.items():
else: self.config[key].value = value
self.config = dict(self.default_config) logging.info("default config: %s", self.default_config)
custom_templates = service_config.get("templates", {}) custom_templates = service_config.get("templates", {})
for file, data in custom_templates.items(): for file, data in custom_templates.items():
self.modified_files.add(file)
self.temp_service_files[file] = data self.temp_service_files[file] = data
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e) show_grpc_error(e)
@ -304,7 +306,7 @@ class ConfigServiceConfigDialog(Dialog):
def click_apply(self): def click_apply(self):
current_listbox = self.master.current.listbox current_listbox = self.master.current.listbox
if not self.is_custom_service_config() and not self.is_custom_service_file(): if not self.is_custom():
if self.node_id in self.service_configs: if self.node_id in self.service_configs:
self.service_configs[self.node_id].pop(self.service_name, None) self.service_configs[self.node_id].pop(self.service_name, None)
current_listbox.itemconfig(current_listbox.curselection()[0], bg="") current_listbox.itemconfig(current_listbox.curselection()[0], bg="")
@ -316,7 +318,9 @@ class ConfigServiceConfigDialog(Dialog):
service_config = node_config.setdefault(self.service_name, {}) service_config = node_config.setdefault(self.service_name, {})
if self.config_frame: if self.config_frame:
self.config_frame.parse_config() self.config_frame.parse_config()
service_config["config"] = self.config service_config["config"] = {
x.name: x.value for x in self.config.values()
}
templates_config = service_config.setdefault("templates", {}) templates_config = service_config.setdefault("templates", {})
for file in self.modified_files: for file in self.modified_files:
templates_config[file] = self.temp_service_files[file] templates_config[file] = self.temp_service_files[file]
@ -346,18 +350,13 @@ class ConfigServiceConfigDialog(Dialog):
else: else:
self.modified_files.discard(template) self.modified_files.discard(template)
def is_custom_service_config(self): def is_custom(self):
startup_commands = self.startup_commands_listbox.get(0, "end") has_custom_templates = len(self.modified_files) > 0
shutdown_commands = self.shutdown_commands_listbox.get(0, "end") has_custom_config = False
validate_commands = self.validate_commands_listbox.get(0, "end") if self.config_frame:
return ( current = self.config_frame.parse_config()
set(self.default_startup) != set(startup_commands) has_custom_config = self.default_config != current
or set(self.default_validate) != set(validate_commands) return has_custom_templates or has_custom_config
or set(self.default_shutdown) != set(shutdown_commands)
)
def is_custom_service_file(self):
return len(self.modified_files) > 0
def click_defaults(self): def click_defaults(self):
if self.node_id in self.service_configs: if self.node_id in self.service_configs:
@ -368,8 +367,8 @@ class ConfigServiceConfigDialog(Dialog):
self.template_text.text.delete(1.0, "end") self.template_text.text.delete(1.0, "end")
self.template_text.text.insert("end", self.temp_service_files[filename]) self.template_text.text.insert("end", self.temp_service_files[filename])
if self.config_frame: if self.config_frame:
defaults = {x.id: x.value for x in self.default_config.values()} logging.info("resetting defaults: %s", self.default_config)
self.config_frame.set_values(defaults) self.config_frame.set_values(self.default_config)
def click_copy(self): def click_copy(self):
pass pass

View file

@ -153,14 +153,9 @@ class NodeConfigServiceDialog(Dialog):
return return
def is_custom_service(self, service: str) -> bool: def is_custom_service(self, service: str) -> bool:
service_configs = self.app.core.service_configs node_configs = self.app.core.config_service_configs.get(self.node_id, {})
file_configs = self.app.core.file_configs service_config = node_configs.get(service)
if self.node_id in service_configs and service in service_configs[self.node_id]: if node_configs and service_config:
return True return True
if ( else:
self.node_id in file_configs return False
and service in file_configs[self.node_id]
and file_configs[self.node_id][service]
):
return True
return False

View file

@ -9,7 +9,7 @@ from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData from core.emulator.data import LinkData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import NodeTypes from core.emulator.enumerations import NodeTypes
from core.nodes.base import CoreNetworkBase, NodeBase from core.nodes.base import CoreNetworkBase, CoreNodeBase, NodeBase
from core.nodes.network import CtrlNet from core.nodes.network import CtrlNet
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
@ -219,10 +219,15 @@ class DeviceElement(NodeElement):
service_elements = etree.Element("services") service_elements = etree.Element("services")
for service in self.node.services: for service in self.node.services:
etree.SubElement(service_elements, "service", name=service.name) etree.SubElement(service_elements, "service", name=service.name)
if service_elements.getchildren(): if service_elements.getchildren():
self.element.append(service_elements) self.element.append(service_elements)
config_service_elements = etree.Element("configservices")
for name, service in self.node.config_services.items():
etree.SubElement(config_service_elements, "service", name=name)
if config_service_elements.getchildren():
self.element.append(config_service_elements)
class NetworkElement(NodeElement): class NetworkElement(NodeElement):
def __init__(self, session: "Session", node: NodeBase) -> None: def __init__(self, session: "Session", node: NodeBase) -> None:
@ -261,6 +266,7 @@ class CoreXmlWriter:
self.write_mobility_configs() self.write_mobility_configs()
self.write_emane_configs() self.write_emane_configs()
self.write_service_configs() self.write_service_configs()
self.write_configservice_configs()
self.write_session_origin() self.write_session_origin()
self.write_session_hooks() self.write_session_hooks()
self.write_session_options() self.write_session_options()
@ -399,6 +405,32 @@ class CoreXmlWriter:
if service_configurations.getchildren(): if service_configurations.getchildren():
self.scenario.append(service_configurations) self.scenario.append(service_configurations)
def write_configservice_configs(self) -> None:
service_configurations = etree.Element("configservice_configurations")
for node in self.session.nodes.values():
if not isinstance(node, CoreNodeBase):
continue
for name, service in node.config_services.items():
service_element = etree.SubElement(
service_configurations, "service", name=name
)
add_attribute(service_element, "node", node.id)
if service.custom_config:
configs_element = etree.SubElement(service_element, "configs")
for key, value in service.custom_config.items():
etree.SubElement(
configs_element, "config", key=key, value=value
)
if service.custom_templates:
templates_element = etree.SubElement(service_element, "templates")
for template_name, template in service.custom_templates.items():
template_element = etree.SubElement(
templates_element, "template", name=template_name
)
template_element.text = etree.CDATA(template)
if service_configurations.getchildren():
self.scenario.append(service_configurations)
def write_default_services(self) -> None: def write_default_services(self) -> None:
node_types = etree.Element("default_services") node_types = etree.Element("default_services")
for node_type in self.session.services.default_services: for node_type in self.session.services.default_services:
@ -568,6 +600,7 @@ class CoreXmlReader:
self.read_mobility_configs() self.read_mobility_configs()
self.read_emane_configs() self.read_emane_configs()
self.read_nodes() self.read_nodes()
self.read_configservice_configs()
self.read_links() self.read_links()
def read_default_services(self) -> None: def read_default_services(self) -> None:
@ -768,6 +801,12 @@ class CoreXmlReader:
if service_elements is not None: if service_elements is not None:
options.services = [x.get("name") for x in service_elements.iterchildren()] options.services = [x.get("name") for x in service_elements.iterchildren()]
config_service_elements = device_element.find("configservices")
if config_service_elements is not None:
options.config_services = [
x.get("name") for x in config_service_elements.iterchildren()
]
position_element = device_element.find("position") position_element = device_element.find("position")
if position_element is not None: if position_element is not None:
x = get_float(position_element, "x") x = get_float(position_element, "x")
@ -808,6 +847,36 @@ class CoreXmlReader:
) )
self.session.add_node(_type=node_type, _id=node_id, options=options) self.session.add_node(_type=node_type, _id=node_id, options=options)
def read_configservice_configs(self) -> None:
configservice_configs = self.scenario.find("configservice_configurations")
if configservice_configs is None:
return
for configservice_element in configservice_configs.iterchildren():
name = configservice_element.get("name")
node_id = get_int(configservice_element, "node")
node = self.session.get_node(node_id)
service = node.config_services[name]
configs_element = configservice_element.find("configs")
if configs_element is not None:
config = {}
for config_element in configs_element.iterchildren():
key = config_element.get("key")
value = config_element.get("value")
config[key] = value
service.set_config(config)
templates_element = configservice_element.find("templates")
if templates_element is not None:
for template_element in templates_element.iterchildren():
name = template_element.get("name")
template = template_element.text
logging.info(
"loading xml template(%s): %s", type(template), template
)
service.set_template(name, template)
def read_links(self) -> None: def read_links(self) -> None:
link_elements = self.scenario.find("links") link_elements = self.scenario.find("links")
if link_elements is None: if link_elements is None:

View file

@ -57,6 +57,14 @@ message GetConfigServiceDefaultsResponse {
repeated ConfigMode modes = 3; repeated ConfigMode modes = 3;
} }
message GetNodeConfigServiceConfigsRequest {
int32 session_id = 1;
}
message GetNodeConfigServiceConfigsResponse {
repeated ConfigServiceConfig configs = 1;
}
message GetNodeConfigServiceRequest { message GetNodeConfigServiceRequest {
int32 session_id = 1; int32 session_id = 1;
int32 node_id = 2; int32 node_id = 2;

View file

@ -110,6 +110,8 @@ service CoreApi {
} }
rpc GetConfigServiceDefaults (configservices.GetConfigServiceDefaultsRequest) returns (configservices.GetConfigServiceDefaultsResponse) { rpc GetConfigServiceDefaults (configservices.GetConfigServiceDefaultsRequest) returns (configservices.GetConfigServiceDefaultsResponse) {
} }
rpc GetNodeConfigServiceConfigs (configservices.GetNodeConfigServiceConfigsRequest) returns (configservices.GetNodeConfigServiceConfigsResponse) {
}
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) {