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,
GetConfigServicesRequest,
GetConfigServicesResponse,
GetNodeConfigServiceConfigsRequest,
GetNodeConfigServiceConfigsResponse,
GetNodeConfigServiceRequest,
GetNodeConfigServiceResponse,
GetNodeConfigServicesRequest,
@ -1103,6 +1105,12 @@ class CoreGrpcClient:
request = GetConfigServiceDefaultsRequest(name=name)
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(
self, session_id: int, node_id: int, name: str
) -> GetNodeConfigServiceResponse:

View file

@ -23,6 +23,8 @@ from core.api.grpc.configservices_pb2 import (
GetConfigServiceDefaultsResponse,
GetConfigServicesRequest,
GetConfigServicesResponse,
GetNodeConfigServiceConfigsRequest,
GetNodeConfigServiceConfigsResponse,
GetNodeConfigServiceRequest,
GetNodeConfigServiceResponse,
GetNodeConfigServicesRequest,
@ -45,7 +47,7 @@ from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags
from core.emulator.session import Session
from core.errors import CoreCommandError, CoreError
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.lxd import LxcNode
from core.services.coreservices import ServiceManager
@ -192,7 +194,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if config.config:
service.set_config(config.config)
for name, template in config.templates.items():
service.custom_template(name, template)
service.set_template(name, template)
# service file configs
for config in request.service_file_configs:
@ -463,6 +465,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if services is None:
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
if isinstance(node, EmaneNet):
emane_model = node.model.name
@ -478,6 +482,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
services=services,
icon=node.icon,
image=image,
config_services=config_services,
)
if isinstance(node, (DockerNode, LxcNode)):
node_proto.image = node.image
@ -1528,6 +1533,27 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
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(
self, request: GetNodeConfigServicesRequest, context: ServicerContext
) -> GetNodeConfigServicesResponse:

View file

@ -38,6 +38,7 @@ class ConfigService(abc.ABC):
self.templates = TemplateLookup(directories=templates_path)
self.config = {}
self.custom_templates = {}
self.custom_config = {}
configs = self.default_configs[:]
self._define_config(configs)
@ -134,7 +135,7 @@ class ConfigService(abc.ABC):
def data(self) -> Dict[str, Any]:
return {}
def custom_template(self, name: str, template: str) -> None:
def set_template(self, name: str, template: str) -> None:
self.custom_templates[name] = template
def get_text(self, name: str) -> str:
@ -248,11 +249,13 @@ class ConfigService(abc.ABC):
self.config[config.id] = config
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:
for key, value in data.items():
config = self.config.get(key)
if config is None:
if key not in self.config:
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]
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
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 name, service_config in node_config.items():
config = service_config.get("config", {})
config_values = {}
for key, option in config.items():
config_values[key] = option.value
config_proto = configservices_pb2.ConfigServiceConfig(
node_id=node_id,
name=name,
templates=service_config["templates"],
config=config_values,
config=config,
)
config_service_protos.append(config_proto)
return config_service_protos

View file

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

View file

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

View file

@ -9,7 +9,7 @@ from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
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.services.coreservices import CoreService
@ -219,10 +219,15 @@ class DeviceElement(NodeElement):
service_elements = etree.Element("services")
for service in self.node.services:
etree.SubElement(service_elements, "service", name=service.name)
if service_elements.getchildren():
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):
def __init__(self, session: "Session", node: NodeBase) -> None:
@ -261,6 +266,7 @@ class CoreXmlWriter:
self.write_mobility_configs()
self.write_emane_configs()
self.write_service_configs()
self.write_configservice_configs()
self.write_session_origin()
self.write_session_hooks()
self.write_session_options()
@ -399,6 +405,32 @@ class CoreXmlWriter:
if service_configurations.getchildren():
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:
node_types = etree.Element("default_services")
for node_type in self.session.services.default_services:
@ -568,6 +600,7 @@ class CoreXmlReader:
self.read_mobility_configs()
self.read_emane_configs()
self.read_nodes()
self.read_configservice_configs()
self.read_links()
def read_default_services(self) -> None:
@ -768,6 +801,12 @@ class CoreXmlReader:
if service_elements is not None:
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")
if position_element is not None:
x = get_float(position_element, "x")
@ -808,6 +847,36 @@ class CoreXmlReader:
)
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:
link_elements = self.scenario.find("links")
if link_elements is None:

View file

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

View file

@ -110,6 +110,8 @@ service CoreApi {
}
rpc GetConfigServiceDefaults (configservices.GetConfigServiceDefaultsRequest) returns (configservices.GetConfigServiceDefaultsResponse) {
}
rpc GetNodeConfigServiceConfigs (configservices.GetNodeConfigServiceConfigsRequest) returns (configservices.GetNodeConfigServiceConfigsResponse) {
}
rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) {
}
rpc GetNodeConfigServices (configservices.GetNodeConfigServicesRequest) returns (configservices.GetNodeConfigServicesResponse) {