From 54ac807a4f629cd8f6defebebacf784826ee1e26 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 12 Jul 2021 10:29:53 -0700 Subject: [PATCH 1/2] grpc: changes to support nodes containing configuration data, allowing for node creation with configs and querying nodes with their configs --- daemon/core/api/grpc/client.py | 93 +-------- daemon/core/api/grpc/grpcutils.py | 183 ++++++++++-------- daemon/core/api/grpc/server.py | 77 +++----- daemon/core/api/grpc/wrappers.py | 141 +++++++++++--- daemon/core/gui/coreclient.py | 13 +- .../core/gui/dialogs/configserviceconfig.py | 6 +- daemon/core/gui/dialogs/nodeconfigservice.py | 8 + daemon/core/gui/dialogs/nodeservice.py | 16 +- daemon/core/nodes/base.py | 1 + daemon/proto/core/api/grpc/core.proto | 37 ++-- daemon/proto/core/api/grpc/emane.proto | 6 + 11 files changed, 290 insertions(+), 291 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 2785a037..6057d655 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -11,16 +11,7 @@ from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tup import grpc -from core.api.grpc import ( - configservices_pb2, - core_pb2, - core_pb2_grpc, - emane_pb2, - mobility_pb2, - services_pb2, - wlan_pb2, - wrappers, -) +from core.api.grpc import core_pb2, core_pb2_grpc, emane_pb2, wrappers from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsRequest, GetNodeConfigServiceRequest, @@ -233,95 +224,17 @@ class CoreGrpcClient: self.proxy: bool = proxy def start_session( - self, - session: wrappers.Session, - asymmetric_links: List[wrappers.Link] = None, - definition: bool = False, + self, session: wrappers.Session, definition: bool = False ) -> Tuple[bool, List[str]]: """ Start a session. :param session: session to start - :param asymmetric_links: link configuration for asymmetric links :param definition: True to only define session data, False to start session :return: tuple of result and exception strings """ - nodes = [x.to_proto() for x in session.nodes.values()] - links = [x.to_proto() for x in session.links] - if asymmetric_links: - asymmetric_links = [x.to_proto() for x in asymmetric_links] - hooks = [x.to_proto() for x in session.hooks.values()] - emane_model_configs = [] - mobility_configs = [] - wlan_configs = [] - service_configs = [] - service_file_configs = [] - config_service_configs = [] - for node in session.nodes.values(): - for key, config in node.emane_model_configs.items(): - model, iface_id = key - config = wrappers.ConfigOption.to_dict(config) - if iface_id is None: - iface_id = -1 - emane_model_config = emane_pb2.EmaneModelConfig( - node_id=node.id, iface_id=iface_id, model=model, config=config - ) - emane_model_configs.append(emane_model_config) - if node.wlan_config: - config = wrappers.ConfigOption.to_dict(node.wlan_config) - wlan_config = wlan_pb2.WlanConfig(node_id=node.id, config=config) - wlan_configs.append(wlan_config) - if node.mobility_config: - config = wrappers.ConfigOption.to_dict(node.mobility_config) - mobility_config = mobility_pb2.MobilityConfig( - node_id=node.id, config=config - ) - mobility_configs.append(mobility_config) - for name, config in node.service_configs.items(): - service_config = services_pb2.ServiceConfig( - node_id=node.id, - service=name, - directories=config.dirs, - files=config.configs, - startup=config.startup, - validate=config.validate, - shutdown=config.shutdown, - ) - service_configs.append(service_config) - for service, file_configs in node.service_file_configs.items(): - for file, data in file_configs.items(): - service_file_config = services_pb2.ServiceFileConfig( - node_id=node.id, service=service, file=file, data=data - ) - service_file_configs.append(service_file_config) - for name, service_config in node.config_service_configs.items(): - config_service_config = configservices_pb2.ConfigServiceConfig( - node_id=node.id, - name=name, - templates=service_config.templates, - config=service_config.config, - ) - config_service_configs.append(config_service_config) - options = {k: v.value for k, v in session.options.items()} - servers = [x.to_proto() for x in session.servers] request = core_pb2.StartSessionRequest( - session_id=session.id, - nodes=nodes, - links=links, - location=session.location.to_proto(), - hooks=hooks, - emane_model_configs=emane_model_configs, - wlan_configs=wlan_configs, - mobility_configs=mobility_configs, - service_configs=service_configs, - service_file_configs=service_file_configs, - asymmetric_links=asymmetric_links, - config_service_configs=config_service_configs, - options=options, - user=session.user, - definition=definition, - metadata=session.metadata, - servers=servers, + session=session.to_proto(), definition=definition ) response = self.stub.StartSession(request) return response.result, list(response.exceptions) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 169819ba..c585a135 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -8,9 +8,8 @@ from grpc import ServicerContext from core import utils from core.api.grpc import common_pb2, core_pb2, wrappers -from core.api.grpc.common_pb2 import MappedConfig from core.api.grpc.configservices_pb2 import ConfigServiceConfig -from core.api.grpc.emane_pb2 import GetEmaneModelConfig +from core.api.grpc.emane_pb2 import NodeEmaneConfig from core.api.grpc.services_pb2 import ( NodeServiceConfig, NodeServiceData, @@ -252,12 +251,15 @@ def get_config_options( return results -def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node: +def get_node_proto( + session: Session, node: NodeBase, emane_configs: List[NodeEmaneConfig] +) -> core_pb2.Node: """ Convert CORE node to protobuf representation. :param session: session containing node :param node: node to convert + :param emane_configs: emane configs related to node :return: node proto """ node_type = session.get_node_type(node.__class__) @@ -283,6 +285,42 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node: image = None if isinstance(node, (DockerNode, LxcNode)): image = node.image + # check for wlan config + wlan_config = session.mobility.get_configs( + node.id, config_type=BasicRangeModel.name + ) + if wlan_config: + wlan_config = get_config_options(wlan_config, BasicRangeModel) + # check for mobility config + mobility_config = session.mobility.get_configs( + node.id, config_type=Ns2ScriptedMobility.name + ) + if mobility_config: + mobility_config = get_config_options(mobility_config, Ns2ScriptedMobility) + # check for service configs + custom_services = session.services.custom_services.get(node.id) + service_configs = {} + if custom_services: + for service in custom_services.values(): + service_proto = get_service_configuration(service) + service_configs[service.name] = NodeServiceConfig( + node_id=node.id, + service=service.name, + data=service_proto, + files=service.config_data, + ) + # check for config service configs + config_service_configs = {} + if isinstance(node, CoreNode): + for service in node.config_services.values(): + if not service.custom_templates and not service.custom_config: + continue + config_service_configs[service.name] = ConfigServiceConfig( + node_id=node.id, + name=service.name, + templates=service.custom_templates, + config=service.custom_config, + ) return core_pb2.Node( id=node.id, name=node.name, @@ -298,6 +336,11 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node: dir=node_dir, channel=channel, canvas=node.canvas, + wlan_config=wlan_config, + mobility_config=mobility_config, + service_configs=service_configs, + config_service_configs=config_service_configs, + emane_configs=emane_configs, ) @@ -530,8 +573,8 @@ def get_nem_id( return nem_id -def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]: - configs = [] +def get_emane_model_configs_dict(session: Session) -> Dict[int, List[NodeEmaneConfig]]: + configs = {} for _id, model_configs in session.emane.node_configs.items(): for model_name in model_configs: model_class = session.emane.get_model(model_name) @@ -539,42 +582,11 @@ def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]: config = get_config_options(current_config, model_class) node_id, iface_id = utils.parse_iface_config_id(_id) iface_id = iface_id if iface_id is not None else -1 - model_config = GetEmaneModelConfig( - node_id=node_id, model=model_name, iface_id=iface_id, config=config + node_config = NodeEmaneConfig( + model=model_name, iface_id=iface_id, config=config ) - configs.append(model_config) - return configs - - -def get_wlan_configs(session: Session) -> Dict[int, MappedConfig]: - configs = {} - for node_id in session.mobility.node_configurations: - model_config = session.mobility.node_configurations[node_id] - if node_id == -1: - continue - for model_name in model_config: - if model_name != BasicRangeModel.name: - continue - current_config = session.mobility.get_model_config(node_id, model_name) - config = get_config_options(current_config, BasicRangeModel) - mapped_config = MappedConfig(config=config) - configs[node_id] = mapped_config - return configs - - -def get_mobility_configs(session: Session) -> Dict[int, MappedConfig]: - configs = {} - for node_id in session.mobility.node_configurations: - model_config = session.mobility.node_configurations[node_id] - if node_id == -1: - continue - for model_name in model_config: - if model_name != Ns2ScriptedMobility.name: - continue - current_config = session.mobility.get_model_config(node_id, model_name) - config = get_config_options(current_config, Ns2ScriptedMobility) - mapped_config = MappedConfig(config=config) - configs[node_id] = mapped_config + node_configs = configs.setdefault(node_id, []) + node_configs.append(node_config) return configs @@ -596,40 +608,6 @@ def get_default_services(session: Session) -> List[ServiceDefaults]: return default_services -def get_node_service_configs(session: Session) -> List[NodeServiceConfig]: - configs = [] - for node_id, service_configs in session.services.custom_services.items(): - for name in service_configs: - service = session.services.get_service(node_id, name) - service_proto = get_service_configuration(service) - config = NodeServiceConfig( - node_id=node_id, - service=name, - data=service_proto, - files=service.config_data, - ) - configs.append(config) - return configs - - -def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfig]: - 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 = ConfigServiceConfig( - node_id=node.id, - name=name, - templates=service.custom_templates, - config=service.custom_config, - ) - configs.append(config_proto) - return configs - - def get_mobility_node( session: Session, node_id: int, context: ServicerContext ) -> Union[WlanNode, EmaneNet]: @@ -645,10 +623,12 @@ def get_mobility_node( def convert_session(session: Session) -> wrappers.Session: links = [] nodes = [] + emane_configs = get_emane_model_configs_dict(session) for _id in session.nodes: node = session.nodes[_id] if not isinstance(node, (PtpNet, CtrlNet)): - node_proto = get_node_proto(session, node) + node_emane_configs = emane_configs.get(node.id, []) + node_proto = get_node_proto(session, node, node_emane_configs) nodes.append(node_proto) node_links = get_links(node) links.extend(node_links) @@ -659,11 +639,6 @@ def convert_session(session: Session) -> wrappers.Session: x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale ) hooks = get_hooks(session) - emane_model_configs = get_emane_model_configs(session) - wlan_configs = get_wlan_configs(session) - mobility_configs = get_mobility_configs(session) - service_configs = get_node_service_configs(session) - config_service_configs = get_node_config_service_configs(session) session_file = str(session.file_path) if session.file_path else None options = get_config_options(session.options.get_configs(), session.options) servers = [ @@ -680,13 +655,51 @@ def convert_session(session: Session) -> wrappers.Session: default_services=default_services, location=location, hooks=hooks, - emane_model_configs=emane_model_configs, - wlan_configs=wlan_configs, - service_configs=service_configs, - config_service_configs=config_service_configs, - mobility_configs=mobility_configs, metadata=session.metadata, file=session_file, options=options, servers=servers, ) + + +def configure_node( + session: Session, node: core_pb2.Node, core_node: NodeBase, context: ServicerContext +) -> None: + for emane_config in node.emane_configs: + _id = utils.iface_config_id(node.id, emane_config.iface_id) + config = {k: v.value for k, v in emane_config.config.items()} + session.emane.set_config(_id, emane_config.model, config) + if node.wlan_config: + config = {k: v.value for k, v in node.wlan_config.items()} + session.mobility.set_model_config(node.id, BasicRangeModel.name, config) + if node.mobility_config: + config = {k: v.value for k, v in node.mobility_config.items()} + session.mobility.set_model_config(node.id, Ns2ScriptedMobility.name, config) + for service_name, service_config in node.service_configs.items(): + data = service_config.data + config = ServiceConfig( + node_id=node.id, + service=service_name, + startup=data.startup, + validate=data.validate, + shutdown=data.shutdown, + files=data.configs, + directories=data.dirs, + ) + service_configuration(session, config) + for file_name, file_data in service_config.files.items(): + session.services.set_service_file( + node.id, service_name, file_name, file_data + ) + if node.config_service_configs: + if not isinstance(core_node, CoreNode): + context.abort( + grpc.StatusCode.INVALID_ARGUMENT, + "invalid node type with config service configs", + ) + for service_name, service_config in node.config_service_configs.items(): + service = core_node.config_services[service_name] + if service_config.config: + service.set_config(service_config.config) + for name, template in service_config.templates.items(): + service.set_template(name, template) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 8b0b903a..dd455b31 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -224,7 +224,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :return: start session response """ logger.debug("start session: %s", request) - session = self.get_session(request.session_id, context) + session = self.get_session(request.session.id, context) # clear previous state and setup for creation session.clear() @@ -234,77 +234,51 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): state = EventTypes.CONFIGURATION_STATE session.directory.mkdir(exist_ok=True) session.set_state(state) - session.user = request.user + session.user = request.session.user # session options session.options.config_reset() - for key, value in request.options.items(): - session.options.set_config(key, value) - session.metadata = dict(request.metadata) + for option in request.session.options.values(): + session.options.set_config(option.name, option.value) + session.metadata = dict(request.session.metadata) # add servers - for server in request.servers: + for server in request.session.servers: session.distributed.add_server(server.name, server.host) # location - if request.HasField("location"): - grpcutils.session_location(session, request.location) + if request.session.HasField("location"): + grpcutils.session_location(session, request.session.location) # add all hooks - for hook in request.hooks: + for hook in request.session.hooks: state = EventTypes(hook.state) session.add_hook(state, hook.file, hook.data) # create nodes - _, exceptions = grpcutils.create_nodes(session, request.nodes) + _, exceptions = grpcutils.create_nodes(session, request.session.nodes) if exceptions: exceptions = [str(x) for x in exceptions] return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) - # emane configs - for config in request.emane_model_configs: - _id = utils.iface_config_id(config.node_id, config.iface_id) - session.emane.set_config(_id, config.model, config.config) - - # wlan configs - for config in request.wlan_configs: - session.mobility.set_model_config( - config.node_id, BasicRangeModel.name, config.config - ) - - # mobility configs - for config in request.mobility_configs: - session.mobility.set_model_config( - config.node_id, Ns2ScriptedMobility.name, config.config - ) - - # service configs - for config in request.service_configs: - grpcutils.service_configuration(session, config) - - # service file configs - for config in request.service_file_configs: - session.services.set_service_file( - config.node_id, config.service, config.file, config.data - ) - - # config service configs - for config in request.config_service_configs: - node = self.get_node(session, config.node_id, context, CoreNode) - service = node.config_services[config.name] - if config.config: - service.set_config(config.config) - for name, template in config.templates.items(): - service.set_template(name, template) + # check for configurations + for node in request.session.nodes: + core_node = self.get_node(session, node.id, context, NodeBase) + grpcutils.configure_node(session, node, core_node, context) # create links - _, exceptions = grpcutils.create_links(session, request.links) + links = [] + asym_links = [] + for link in request.session.links: + if link.options.unidirectional: + asym_links.append(link) + else: + links.append(link) + _, exceptions = grpcutils.create_links(session, links) if exceptions: exceptions = [str(x) for x in exceptions] return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) - - # asymmetric links - _, exceptions = grpcutils.edit_links(session, request.asymmetric_links) + _, exceptions = grpcutils.edit_links(session, asym_links) if exceptions: exceptions = [str(x) for x in exceptions] return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) @@ -541,6 +515,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): _type, _id, options = grpcutils.add_node_data(request.node) _class = session.get_node_class(_type) node = session.add_node(_class, _id, options) + grpcutils.configure_node(session, request.node, node, context) source = request.source if request.source else None session.broadcast_node(node, MessageFlags.ADD, source) return core_pb2.AddNodeResponse(node_id=node.id) @@ -563,7 +538,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): iface = node.ifaces[iface_id] iface_proto = grpcutils.iface_to_proto(request.node_id, iface) ifaces.append(iface_proto) - node_proto = grpcutils.get_node_proto(session, node) + emane_configs = grpcutils.get_emane_model_configs_dict(session) + node_emane_configs = emane_configs.get(node.id, []) + node_proto = grpcutils.get_node_proto(session, node, node_emane_configs) links = get_links(node) return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links) diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index 802af3c3..ffeb6793 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -239,12 +239,26 @@ class NodeServiceData: configs=proto.configs, startup=proto.startup, validate=proto.validate, - validation_mode=proto.validation_mode, + validation_mode=ServiceValidationMode(proto.validation_mode), validation_timer=proto.validation_timer, shutdown=proto.shutdown, meta=proto.meta, ) + def to_proto(self) -> services_pb2.NodeServiceData: + return services_pb2.NodeServiceData( + executables=self.executables, + dependencies=self.dependencies, + dirs=self.dirs, + configs=self.configs, + startup=self.startup, + validate=self.validate, + validation_mode=self.validation_mode.value, + validation_timer=self.validation_timer, + shutdown=self.shutdown, + meta=self.meta, + ) + @dataclass class NodeServiceConfig: @@ -430,15 +444,27 @@ class ConfigOption: @classmethod def from_proto(cls, proto: common_pb2.ConfigOption) -> "ConfigOption": + config_type = ConfigOptionType(proto.type) if proto.type is not None else None return ConfigOption( label=proto.label, name=proto.name, value=proto.value, - type=ConfigOptionType(proto.type), + type=config_type, group=proto.group, select=proto.select, ) + def to_proto(self) -> common_pb2.ConfigOption: + config_type = self.type.value if self.type is not None else None + return common_pb2.ConfigOption( + label=self.label, + name=self.name, + value=self.value, + type=config_type, + select=self.select, + group=self.group, + ) + @dataclass class Interface: @@ -714,6 +740,23 @@ class Node: @classmethod def from_proto(cls, proto: core_pb2.Node) -> "Node": + service_configs = {} + service_file_configs = {} + for service, node_config in proto.service_configs.items(): + service_configs[service] = NodeServiceData.from_proto(node_config.data) + service_file_configs[service] = dict(node_config.files) + emane_configs = {} + for emane_config in proto.emane_configs: + iface_id = None if emane_config.iface_id == -1 else emane_config.iface_id + model = emane_config.model + key = (model, iface_id) + emane_configs[key] = ConfigOption.from_dict(emane_config.config) + config_service_configs = {} + for service, service_config in proto.config_service_configs.items(): + config_service_configs[service] = ConfigServiceData( + templates=dict(service_config.templates), + config=dict(service_config.config), + ) return Node( id=proto.id, name=proto.name, @@ -730,9 +773,43 @@ class Node: dir=proto.dir, channel=proto.channel, canvas=proto.canvas, + wlan_config=ConfigOption.from_dict(proto.wlan_config), + mobility_config=ConfigOption.from_dict(proto.mobility_config), + service_configs=service_configs, + service_file_configs=service_file_configs, + config_service_configs=config_service_configs, + emane_model_configs=emane_configs, ) def to_proto(self) -> core_pb2.Node: + emane_configs = [] + for key, config in self.emane_model_configs.items(): + model, iface_id = key + if iface_id is None: + iface_id = -1 + config = {k: v.to_proto() for k, v in config.items()} + emane_config = emane_pb2.NodeEmaneConfig( + iface_id=iface_id, model=model, config=config + ) + emane_configs.append(emane_config) + service_configs = {} + for service, service_data in self.service_configs.items(): + service_configs[service] = services_pb2.NodeServiceConfig( + service=service, data=service_data.to_proto() + ) + for service, file_configs in self.service_file_configs.items(): + service_config = service_configs.get(service) + if service_config: + service_config.files.update(file_configs) + else: + service_configs[service] = services_pb2.NodeServiceConfig( + service=service, files=file_configs + ) + config_service_configs = {} + for service, service_config in self.config_service_configs.items(): + config_service_configs[service] = configservices_pb2.ConfigServiceConfig( + templates=service_config.templates, config=service_config.config + ) return core_pb2.Node( id=self.id, name=self.name, @@ -748,6 +825,11 @@ class Node: dir=self.dir, channel=self.channel, canvas=self.canvas, + wlan_config={k: v.to_proto() for k, v in self.wlan_config.items()}, + mobility_config={k: v.to_proto() for k, v in self.mobility_config.items()}, + service_configs=service_configs, + config_service_configs=config_service_configs, + emane_configs=emane_configs, ) def set_wlan(self, config: Dict[str, str]) -> None: @@ -796,32 +878,6 @@ class Session: x.node_type: set(x.services) for x in proto.default_services } hooks = {x.file: Hook.from_proto(x) for x in proto.hooks} - # update nodes with their current configurations - for model in proto.emane_model_configs: - iface_id = None - if model.iface_id != -1: - iface_id = model.iface_id - node = nodes[model.node_id] - key = (model.model, iface_id) - node.emane_model_configs[key] = ConfigOption.from_dict(model.config) - for node_id, mapped_config in proto.wlan_configs.items(): - node = nodes[node_id] - node.wlan_config = ConfigOption.from_dict(mapped_config.config) - for config in proto.service_configs: - service = config.service - node = nodes[config.node_id] - node.service_configs[service] = NodeServiceData.from_proto(config.data) - for file, data in config.files.items(): - files = node.service_file_configs.setdefault(service, {}) - files[file] = data - for config in proto.config_service_configs: - node = nodes[config.node_id] - node.config_service_configs[config.name] = ConfigServiceData( - templates=dict(config.templates), config=dict(config.config) - ) - for node_id, mapped_config in proto.mobility_configs.items(): - node = nodes[node_id] - node.mobility_config = ConfigOption.from_dict(mapped_config.config) file_path = Path(proto.file) if proto.file else None options = ConfigOption.from_dict(proto.options) servers = [Server.from_proto(x) for x in proto.servers] @@ -841,6 +897,35 @@ class Session: servers=servers, ) + def to_proto(self) -> core_pb2.Session: + nodes = [x.to_proto() for x in self.nodes.values()] + links = [x.to_proto() for x in self.links] + hooks = [x.to_proto() for x in self.hooks.values()] + options = {k: v.to_proto() for k, v in self.options.items()} + servers = [x.to_proto() for x in self.servers] + default_services = [] + for node_type, services in self.default_services.items(): + default_service = services_pb2.ServiceDefaults( + node_type=node_type, services=services + ) + default_services.append(default_service) + file = str(self.file) if self.file else None + return core_pb2.Session( + id=self.id, + state=self.state.value, + nodes=nodes, + links=links, + dir=self.dir, + user=self.user, + default_services=default_services, + location=self.location.to_proto(), + hooks=hooks, + metadata=self.metadata, + file=file, + options=options, + servers=servers, + ) + def add_node( self, _id: int, diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 059266bc..ef550785 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -439,7 +439,7 @@ class CoreClient: def get_links(self, definition: bool = False) -> Tuple[List[Link], List[Link]]: if not definition: self.ifaces_manager.set_macs([x.link for x in self.links.values()]) - links, asym_links = [], [] + links = [] for edge in self.links.values(): link = edge.link if not definition: @@ -449,12 +449,11 @@ class CoreClient: link.iface2.mac = self.ifaces_manager.next_mac() links.append(link) if edge.asymmetric_link: - asym_links.append(edge.asymmetric_link) - return links, asym_links + links.append(edge.asymmetric_link) + return links def start_session(self, definition: bool = False) -> Tuple[bool, List[str]]: - links, asym_links = self.get_links(definition) - self.session.links = links + self.session.links = self.get_links(definition) self.session.metadata = self.get_metadata() self.session.servers.clear() for server in self.servers.values(): @@ -462,9 +461,7 @@ class CoreClient: result = False exceptions = [] try: - result, exceptions = self.client.start_session( - self.session, asym_links, definition - ) + result, exceptions = self.client.start_session(self.session, definition) logger.info( "start session(%s) definition(%s), result: %s", self.session.id, diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 4935ddda..870f9639 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -310,9 +310,9 @@ class ConfigServiceConfigDialog(Dialog): current_listbox.itemconfig(current_listbox.curselection()[0], bg="") self.destroy() return - service_config = self.node.config_service_configs.get(self.service_name) - if not service_config: - service_config = ConfigServiceData() + service_config = self.node.config_service_configs.setdefault( + self.service_name, ConfigServiceData() + ) if self.config_frame: self.config_frame.parse_config() service_config.config = {x.name: x.value for x in self.config.values()} diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index de97ea15..7b55fba4 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -31,6 +31,7 @@ class NodeConfigServiceDialog(Dialog): if services is None: services = set(node.config_services) self.current_services: Set[str] = services + self.protocol("WM_DELETE_WINDOW", self.click_cancel) self.draw() def draw(self) -> None: @@ -131,13 +132,20 @@ class NodeConfigServiceDialog(Dialog): if self.is_custom_service(name): self.current.listbox.itemconfig(tk.END, bg="green") + def cleanup_custom_services(self) -> None: + for service in list(self.node.config_service_configs): + if service not in self.node.config_services: + self.node.config_service_configs.pop(service) + def click_save(self) -> None: self.node.config_services = self.current_services.copy() + self.cleanup_custom_services() logger.info("saved node config services: %s", self.node.config_services) self.destroy() def click_cancel(self) -> None: self.current_services = None + self.cleanup_custom_services() self.destroy() def click_remove(self) -> None: diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index f27f9cf5..dd9b4419 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -25,6 +25,7 @@ class NodeServiceDialog(Dialog): self.current: Optional[ListboxScroll] = None services = set(node.services) self.current_services: Set[str] = services + self.protocol("WM_DELETE_WINDOW", self.click_cancel) self.draw() def draw(self) -> None: @@ -77,7 +78,7 @@ class NodeServiceDialog(Dialog): button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Remove", command=self.click_remove) button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) - button = ttk.Button(frame, text="Cancel", command=self.destroy) + button = ttk.Button(frame, text="Cancel", command=self.click_cancel) button.grid(row=0, column=3, sticky=tk.EW) # trigger group change @@ -125,8 +126,21 @@ class NodeServiceDialog(Dialog): "Service Configuration", "Select a service to configure", parent=self ) + def cleanup_custom_services(self) -> None: + for service in list(self.node.service_configs): + if service not in self.node.services: + self.node.service_configs.pop(service) + for service in list(self.node.service_file_configs): + if service not in self.node.services: + self.node.service_file_configs.pop(service) + + def click_cancel(self) -> None: + self.cleanup_custom_services() + self.destroy() + def click_save(self) -> None: self.node.services = self.current_services.copy() + self.cleanup_custom_services() self.destroy() def click_remove(self) -> None: diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 926f4d7e..960ec056 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -655,6 +655,7 @@ class CoreNode(CoreNodeBase): :param dir_path: path to create :return: nothing """ + logger.info("creating private directory: %s", dir_path) if not str(dir_path).startswith("/"): raise CoreError(f"private directory path not fully qualified: {dir_path}") host_path = self.host_path(dir_path, is_dir=True) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 87cb722b..3fb55f73 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -135,23 +135,8 @@ message GetConfigResponse { message StartSessionRequest { - int32 session_id = 1; - repeated Node nodes = 2; - repeated Link links = 3; - repeated Hook hooks = 4; - SessionLocation location = 5; - repeated wlan.WlanConfig wlan_configs = 6; - repeated emane.EmaneModelConfig emane_model_configs = 7; - repeated mobility.MobilityConfig mobility_configs = 8; - repeated services.ServiceConfig service_configs = 9; - repeated services.ServiceFileConfig service_file_configs = 10; - repeated Link asymmetric_links = 11; - repeated configservices.ConfigServiceConfig config_service_configs = 12; - map options = 13; - string user = 14; - bool definition = 15; - map metadata = 16; - repeated Server servers = 17; + Session session = 1; + bool definition = 2; } message StartSessionResponse { @@ -577,15 +562,10 @@ message Session { repeated services.ServiceDefaults default_services = 7; SessionLocation location = 8; repeated Hook hooks = 9; - repeated emane.GetEmaneModelConfig emane_model_configs = 10; - map wlan_configs = 11; - repeated services.NodeServiceConfig service_configs = 12; - repeated configservices.ConfigServiceConfig config_service_configs = 13; - map mobility_configs = 14; - map metadata = 15; - string file = 16; - map options = 17; - repeated Server servers = 18; + map metadata = 10; + string file = 11; + map options = 12; + repeated Server servers = 13; } message SessionSummary { @@ -612,6 +592,11 @@ message Node { string dir = 13; string channel = 14; int32 canvas = 15; + map wlan_config = 16; + map mobility_config = 17; + map service_configs = 18; + map config_service_configs= 19; + repeated emane.NodeEmaneConfig emane_configs = 20; } message Link { diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index 5aa0c952..b8579917 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -31,6 +31,12 @@ message GetEmaneModelConfig { map config = 4; } +message NodeEmaneConfig { + int32 iface_id = 1; + string model = 2; + map config = 3; +} + message GetEmaneEventChannelRequest { int32 session_id = 1; int32 nem_id = 2; From 4879d6e2974e14005058446767a4fa16977ce273 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 12 Jul 2021 11:01:36 -0700 Subject: [PATCH 2/2] pygui: small cleanup for removing service configuration when they are removed/toggled --- daemon/core/gui/dialogs/nodeconfigservice.py | 9 ++------- daemon/core/gui/dialogs/nodeservice.py | 14 ++++---------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index 7b55fba4..cddbdb03 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -103,6 +103,7 @@ class NodeConfigServiceDialog(Dialog): self.current_services.add(name) elif not var.get() and name in self.current_services: self.current_services.remove(name) + self.node.config_service_configs.pop(name, None) self.draw_current_services() self.node.config_services = self.current_services.copy() @@ -132,20 +133,13 @@ class NodeConfigServiceDialog(Dialog): if self.is_custom_service(name): self.current.listbox.itemconfig(tk.END, bg="green") - def cleanup_custom_services(self) -> None: - for service in list(self.node.config_service_configs): - if service not in self.node.config_services: - self.node.config_service_configs.pop(service) - def click_save(self) -> None: self.node.config_services = self.current_services.copy() - self.cleanup_custom_services() logger.info("saved node config services: %s", self.node.config_services) self.destroy() def click_cancel(self) -> None: self.current_services = None - self.cleanup_custom_services() self.destroy() def click_remove(self) -> None: @@ -154,6 +148,7 @@ class NodeConfigServiceDialog(Dialog): service = self.current.listbox.get(cur[0]) self.current.listbox.delete(cur[0]) self.current_services.remove(service) + self.node.config_service_configs.pop(service, None) for checkbutton in self.services.frame.winfo_children(): if checkbutton["text"] == service: checkbutton.invoke() diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index dd9b4419..431d5c3d 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -99,6 +99,8 @@ class NodeServiceDialog(Dialog): self.current_services.add(name) elif not var.get() and name in self.current_services: self.current_services.remove(name) + self.node.service_configs.pop(name, None) + self.node.service_file_configs.pop(name, None) self.current.listbox.delete(0, tk.END) for name in sorted(self.current_services): self.current.listbox.insert(tk.END, name) @@ -126,21 +128,11 @@ class NodeServiceDialog(Dialog): "Service Configuration", "Select a service to configure", parent=self ) - def cleanup_custom_services(self) -> None: - for service in list(self.node.service_configs): - if service not in self.node.services: - self.node.service_configs.pop(service) - for service in list(self.node.service_file_configs): - if service not in self.node.services: - self.node.service_file_configs.pop(service) - def click_cancel(self) -> None: - self.cleanup_custom_services() self.destroy() def click_save(self) -> None: self.node.services = self.current_services.copy() - self.cleanup_custom_services() self.destroy() def click_remove(self) -> None: @@ -149,6 +141,8 @@ class NodeServiceDialog(Dialog): service = self.current.listbox.get(cur[0]) self.current.listbox.delete(cur[0]) self.current_services.remove(service) + self.node.service_configs.pop(service, None) + self.node.service_file_configs.pop(service, None) for checkbutton in self.services.frame.winfo_children(): if checkbutton["text"] == service: checkbutton.invoke()