From 3bdd6292cdc0161d75be89c07180f0e79d71774f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 27 Jul 2020 18:19:51 -0700 Subject: [PATCH 1/5] grpc: update GetSession to return all session related information, rather than needing 8 different calls, pygui: updated session protobuf wrapper to handle all new data --- daemon/core/api/grpc/grpcutils.py | 129 +++++++++++++++++++- daemon/core/api/grpc/server.py | 137 +++++++--------------- daemon/core/emulator/session.py | 2 +- daemon/core/gui/graph/graph.py | 2 +- daemon/core/gui/wrappers.py | 70 ++++++++++- daemon/proto/core/api/grpc/core.proto | 12 ++ daemon/proto/core/api/grpc/emane.proto | 8 +- daemon/proto/core/api/grpc/services.proto | 15 +-- 8 files changed, 260 insertions(+), 115 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 84b8ee6a..a024c064 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -8,13 +8,22 @@ from grpc import ServicerContext from core import utils from core.api.grpc import common_pb2, core_pb2 -from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig +from core.api.grpc.common_pb2 import MappedConfig +from core.api.grpc.configservices_pb2 import ConfigServiceConfig +from core.api.grpc.emane_pb2 import EmaneModelConfig +from core.api.grpc.services_pb2 import ( + NodeServiceConfig, + NodeServiceData, + ServiceConfig, + ServiceDefaults, +) from core.config import ConfigurableOptions from core.emane.nodes import EmaneNet from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import LinkTypes, NodeTypes from core.emulator.session import Session -from core.nodes.base import CoreNode, NodeBase +from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility +from core.nodes.base import CoreNode, CoreNodeBase, NodeBase from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService @@ -536,3 +545,119 @@ def get_nem_id( message = f"{node.name} interface {iface_id} nem id does not exist" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) return nem_id + + +def get_emane_model_configs(session: Session) -> List[EmaneModelConfig]: + configs = [] + for _id in session.emane.node_configurations: + if _id == -1: + continue + model_configs = session.emane.node_configurations[_id] + for model_name in model_configs: + model = session.emane.models[model_name] + current_config = session.emane.get_model_config(_id, model_name) + config = get_config_options(current_config, model) + node_id, iface_id = parse_emane_model_id(_id) + model_config = EmaneModelConfig( + node_id=node_id, 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 + return configs + + +def get_hooks(session: Session) -> List[core_pb2.Hook]: + hooks = [] + for state in session.hooks: + state_hooks = session.hooks[state] + for file_name, file_data in state_hooks: + hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data) + hooks.append(hook) + return hooks + + +def get_emane_models(session: Session) -> List[str]: + emane_models = [] + for model in session.emane.models.keys(): + if len(model.split("_")) != 2: + continue + emane_models.append(model) + return emane_models + + +def get_default_services(session: Session) -> List[ServiceDefaults]: + default_services = [] + for name, services in session.services.default_services.items(): + default_service = ServiceDefaults(node_type=name, services=services) + default_services.append(default_service) + 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_emane_config(session: Session) -> Dict[str, common_pb2.ConfigOption]: + current_config = session.emane.get_configs() + return get_config_options(current_config, session.emane.emane_config) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 38100e05..65029e0a 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -19,7 +19,6 @@ from core.api.grpc import ( core_pb2_grpc, grpcutils, ) -from core.api.grpc.common_pb2 import MappedConfig from core.api.grpc.configservices_pb2 import ( ConfigService, GetConfigServiceDefaultsRequest, @@ -89,7 +88,6 @@ from core.api.grpc.services_pb2 import ( ServiceAction, ServiceActionRequest, ServiceActionResponse, - ServiceDefaults, SetNodeServiceFileRequest, SetNodeServiceFileResponse, SetNodeServiceRequest, @@ -118,7 +116,7 @@ from core.emulator.enumerations import ( from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility -from core.nodes.base import CoreNode, CoreNodeBase, NodeBase +from core.nodes.base import CoreNode, NodeBase from core.nodes.network import PtpNet, WlanNode from core.services.coreservices import ServiceManager @@ -558,7 +556,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get session: %s", request) session = self.get_session(request.session_id, context) - links = [] nodes = [] for _id in session.nodes: @@ -568,9 +565,37 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): nodes.append(node_proto) node_links = get_links(node) links.extend(node_links) - + default_services = grpcutils.get_default_services(session) + x, y, z = session.location.refxyz + lat, lon, alt = session.location.refgeo + location = core_pb2.SessionLocation( + x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale + ) + hooks = grpcutils.get_hooks(session) + emane_models = grpcutils.get_emane_models(session) + emane_config = grpcutils.get_emane_config(session) + emane_model_configs = grpcutils.get_emane_model_configs(session) + wlan_configs = grpcutils.get_wlan_configs(session) + mobility_configs = grpcutils.get_mobility_configs(session) + service_configs = grpcutils.get_node_service_configs(session) + config_service_configs = grpcutils.get_node_config_service_configs(session) session_proto = core_pb2.Session( - state=session.state.value, nodes=nodes, links=links, dir=session.session_dir + state=session.state.value, + nodes=nodes, + links=links, + dir=session.session_dir, + user=session.user, + default_services=default_services, + location=location, + hooks=hooks, + emane_models=emane_models, + emane_config=emane_config, + 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, ) return core_pb2.GetSessionResponse(session=session_proto) @@ -1012,12 +1037,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get hooks: %s", request) session = self.get_session(request.session_id, context) - hooks = [] - for state in session.hooks: - state_hooks = session.hooks[state] - for file_name, file_data in state_hooks: - hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data) - hooks.append(hook) + hooks = grpcutils.get_hooks(session) return core_pb2.GetHooksResponse(hooks=hooks) def AddHook( @@ -1050,19 +1070,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get mobility configs: %s", request) session = self.get_session(request.session_id, context) - response = GetMobilityConfigsResponse() - 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) - response.configs[node_id].CopyFrom(mapped_config) - return response + configs = grpcutils.get_mobility_configs(session) + return GetMobilityConfigsResponse(configs=configs) def GetMobilityConfig( self, request: GetMobilityConfigRequest, context: ServicerContext @@ -1157,12 +1166,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get service defaults: %s", request) session = self.get_session(request.session_id, context) - all_service_defaults = [] - for node_type in session.services.default_services: - services = session.services.default_services[node_type] - service_defaults = ServiceDefaults(node_type=node_type, services=services) - all_service_defaults.append(service_defaults) - return GetServiceDefaultsResponse(defaults=all_service_defaults) + defaults = grpcutils.get_default_services(session) + return GetServiceDefaultsResponse(defaults=defaults) def SetServiceDefaults( self, request: SetServiceDefaultsRequest, context: ServicerContext @@ -1196,18 +1201,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get node service configs: %s", request) session = self.get_session(request.session_id, context) - 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 = grpcutils.get_service_configuration(service) - config = GetNodeServiceConfigsResponse.ServiceConfig( - node_id=node_id, - service=name, - data=service_proto, - files=service.config_data, - ) - configs.append(config) + configs = grpcutils.get_node_service_configs(session) return GetNodeServiceConfigsResponse(configs=configs) def GetNodeService( @@ -1337,19 +1331,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get wlan configs: %s", request) session = self.get_session(request.session_id, context) - response = GetWlanConfigsResponse() - 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) - response.configs[node_id].CopyFrom(mapped_config) - return response + configs = grpcutils.get_wlan_configs(session) + return GetWlanConfigsResponse(configs=configs) def GetWlanConfig( self, request: GetWlanConfigRequest, context: ServicerContext @@ -1401,8 +1384,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get emane config: %s", request) session = self.get_session(request.session_id, context) - current_config = session.emane.get_configs() - config = get_config_options(current_config, session.emane.emane_config) + config = grpcutils.get_emane_config(session) return GetEmaneConfigResponse(config=config) def SetEmaneConfig( @@ -1433,11 +1415,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get emane models: %s", request) session = self.get_session(request.session_id, context) - models = [] - for model in session.emane.models.keys(): - if len(model.split("_")) != 2: - continue - models.append(model) + models = grpcutils.get_emane_models(session) return GetEmaneModelsResponse(models=models) def GetEmaneModelConfig( @@ -1491,22 +1469,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("get emane model configs: %s", request) session = self.get_session(request.session_id, context) - - configs = [] - for _id in session.emane.node_configurations: - if _id == -1: - continue - - model_configs = session.emane.node_configurations[_id] - for model_name in model_configs: - model = session.emane.models[model_name] - current_config = session.emane.get_model_config(_id, model_name) - config = get_config_options(current_config, model) - node_id, iface_id = grpcutils.parse_emane_model_id(_id) - model_config = GetEmaneModelConfigsResponse.ModelConfig( - node_id=node_id, model=model_name, iface_id=iface_id, config=config - ) - configs.append(model_config) + configs = grpcutils.get_emane_model_configs(session) return GetEmaneModelConfigsResponse(configs=configs) def SaveXml( @@ -1713,21 +1676,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :return: get node config service configs response """ 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) + configs = grpcutils.get_node_config_service_configs(session) return GetNodeConfigServiceConfigsResponse(configs=configs) def GetNodeConfigServices( diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index cad6ae3c..4127b141 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -119,7 +119,7 @@ class Session: # states and hooks handlers self.state: EventTypes = EventTypes.DEFINITION_STATE self.state_time: float = time.monotonic() - self.hooks: Dict[EventTypes, Tuple[str, str]] = {} + self.hooks: Dict[EventTypes, List[Tuple[str, str]]] = {} self.state_hooks: Dict[EventTypes, List[Callable[[EventTypes], None]]] = {} self.add_state_hook( state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 81e0d1c6..f2a27444 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -330,7 +330,7 @@ class CanvasGraph(tk.Canvas): Draw existing session. """ # draw existing nodes - for core_node in session.nodes: + for core_node in session.nodes.values(): logging.debug("drawing node: %s", core_node) # peer to peer node is not drawn on the GUI if NodeUtils.is_ignore_node(core_node.type): diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py index 612c7646..835a9d17 100644 --- a/daemon/core/gui/wrappers.py +++ b/daemon/core/gui/wrappers.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from enum import Enum -from typing import Dict, List +from typing import Dict, List, Optional, Tuple from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2 @@ -119,6 +119,12 @@ class ConfigService: ) +@dataclass +class ConfigServiceData: + templates: Dict[str, str] + config: Dict[str, str] + + @dataclass class NodeServiceData: executables: List[str] @@ -508,6 +514,22 @@ class Node: dir: str = None channel: str = None + # configurations + emane_model_configs: Dict[ + Tuple[str, Optional[int]], Dict[str, ConfigOption] + ] = field(default_factory=dict, repr=False) + wlan_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False) + mobility_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False) + service_configs: Dict[str, NodeServiceData] = field( + default_factory=dict, repr=False + ) + service_file_configs: Dict[str, Dict[str, str]] = field( + default_factory=dict, repr=False + ) + config_service_configs: Dict[str, ConfigServiceData] = field( + default_factory=dict, repr=False + ) + @classmethod def from_proto(cls, proto: core_pb2.Node) -> "Node": return Node( @@ -549,20 +571,62 @@ class Node: class Session: id: int state: SessionState - nodes: List[Node] + nodes: Dict[int, Node] links: List[Link] dir: str + user: str + default_services: Dict[str, List[str]] + location: SessionLocation + hooks: List[Hook] + emane_models: List[str] + emane_config: Dict[str, ConfigOption] + metadata: Dict[str, str] @classmethod def from_proto(cls, proto: core_pb2.Session) -> "Session": - nodes = [Node.from_proto(x) for x in proto.nodes] + nodes: Dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes} links = [Link.from_proto(x) for x in proto.links] + default_services = {x.node_type: x.services for x in proto.default_services} + hooks = [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) return Session( id=proto.id, state=SessionState(proto.state), nodes=nodes, links=links, dir=proto.dir, + user=proto.user, + default_services=default_services, + location=SessionLocation.from_proto(proto.location), + hooks=hooks, + emane_models=list(proto.emane_models), + emane_config=ConfigOption.from_dict(proto.emane_config), + metadata=dict(proto.metadata), ) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 9214ad1b..1b20257c 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -714,6 +714,18 @@ message Session { repeated Node nodes = 3; repeated Link links = 4; string dir = 5; + string user = 6; + repeated services.ServiceDefaults default_services = 7; + SessionLocation location = 8; + repeated Hook hooks = 9; + repeated string emane_models = 10; + map emane_config = 11; + repeated emane.EmaneModelConfig emane_model_configs = 12; + map wlan_configs = 13; + repeated services.NodeServiceConfig service_configs = 14; + repeated configservices.ConfigServiceConfig config_service_configs = 15; + map mobility_configs = 16; + map metadata = 17; } message SessionSummary { diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index ac5456fd..ce9a4297 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -54,13 +54,7 @@ message GetEmaneModelConfigsRequest { } message GetEmaneModelConfigsResponse { - message ModelConfig { - int32 node_id = 1; - string model = 2; - int32 iface_id = 3; - map config = 4; - } - repeated ModelConfig configs = 1; + repeated EmaneModelConfig configs = 1; } message GetEmaneEventChannelRequest { diff --git a/daemon/proto/core/api/grpc/services.proto b/daemon/proto/core/api/grpc/services.proto index 7e8498a7..cf6d9cbf 100644 --- a/daemon/proto/core/api/grpc/services.proto +++ b/daemon/proto/core/api/grpc/services.proto @@ -59,6 +59,13 @@ message NodeServiceData { string meta = 10; } +message NodeServiceConfig { + int32 node_id = 1; + string service = 2; + NodeServiceData data = 3; + map files = 4; +} + message GetServicesRequest { } @@ -89,13 +96,7 @@ message GetNodeServiceConfigsRequest { } message GetNodeServiceConfigsResponse { - message ServiceConfig { - int32 node_id = 1; - string service = 2; - NodeServiceData data = 3; - map files = 4; - } - repeated ServiceConfig configs = 1; + repeated NodeServiceConfig configs = 1; } message GetNodeServiceRequest { From 588afaad13c8996c790f91bd743bf21cc06b1de6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 00:03:15 -0700 Subject: [PATCH 2/5] pygui: changes to make use of wrapped session object and wrapped nodes to maintain and retrieving configurations information --- daemon/core/api/grpc/server.py | 1 + daemon/core/gui/coreclient.py | 379 ++++++------------ daemon/core/gui/dialogs/canvassizeandscale.py | 4 +- .../core/gui/dialogs/configserviceconfig.py | 59 ++- daemon/core/gui/dialogs/copyserviceconfig.py | 21 +- daemon/core/gui/dialogs/emaneconfig.py | 30 +- daemon/core/gui/dialogs/hooks.py | 15 +- daemon/core/gui/dialogs/linkconfig.py | 2 +- daemon/core/gui/dialogs/mobilityconfig.py | 12 +- daemon/core/gui/dialogs/mobilityplayer.py | 45 +-- daemon/core/gui/dialogs/nodeconfigservice.py | 24 +- daemon/core/gui/dialogs/nodeservice.py | 23 +- daemon/core/gui/dialogs/runtool.py | 2 +- daemon/core/gui/dialogs/serviceconfig.py | 42 +- daemon/core/gui/dialogs/sessionoptions.py | 4 +- daemon/core/gui/dialogs/sessions.py | 2 +- daemon/core/gui/dialogs/wlanconfig.py | 6 +- daemon/core/gui/graph/graph.py | 21 +- daemon/core/gui/graph/node.py | 21 +- daemon/core/gui/task.py | 2 - daemon/core/gui/wrappers.py | 24 +- 21 files changed, 284 insertions(+), 455 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 65029e0a..cd9cf714 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -580,6 +580,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): service_configs = grpcutils.get_node_service_configs(session) config_service_configs = grpcutils.get_node_config_service_configs(session) session_proto = core_pb2.Session( + id=session.id, state=session.state.value, nodes=nodes, links=links, diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 5ddc28d7..36adf189 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -37,7 +37,6 @@ from core.gui.wrappers import ( ConfigOption, ConfigService, ExceptionEvent, - Hook, Interface, Link, LinkEvent, @@ -61,6 +60,10 @@ GUI_SOURCE = "gui" CPU_USAGE_DELAY = 3 +def to_dict(config: Dict[str, ConfigOption]) -> Dict[str, str]: + return {x: y.value for x, y in config.items()} + + class CoreClient: def __init__(self, app: "Application", proxy: bool) -> None: """ @@ -69,14 +72,13 @@ class CoreClient: self.app: "Application" = app self.master: tk.Tk = app.master self._client: client.CoreGrpcClient = client.CoreGrpcClient(proxy=proxy) - self.session_id: Optional[int] = None + self.session: Optional[Session] = None + self.user = getpass.getuser() + + # global service settings self.services: Dict[str, Set[str]] = {} self.config_services_groups: Dict[str, Set[str]] = {} self.config_services: Dict[str, ConfigService] = {} - self.default_services: Dict[NodeType, Set[str]] = {} - self.emane_models: List[str] = [] - self.observer: Optional[str] = None - self.user = getpass.getuser() # loaded configuration data self.servers: Dict[str, CoreServer] = {} @@ -87,15 +89,12 @@ class CoreClient: # helpers self.iface_to_edge: Dict[Tuple[int, ...], Tuple[int, ...]] = {} self.ifaces_manager: InterfaceManager = InterfaceManager(self.app) + self.observer: Optional[str] = None # session data - self.state: Optional[SessionState] = None - self.canvas_nodes: Dict[int, CanvasNode] = {} - self.location: Optional[SessionLocation] = None - self.links: Dict[Tuple[int, int], CanvasEdge] = {} - self.hooks: Dict[str, Hook] = {} - self.emane_config: Dict[str, ConfigOption] = {} self.mobility_players: Dict[int, MobilityPlayer] = {} + self.canvas_nodes: Dict[int, CanvasNode] = {} + self.links: Dict[Tuple[int, int], CanvasEdge] = {} self.handling_throughputs: Optional[grpc.Future] = None self.handling_cpu_usage: Optional[grpc.Future] = None self.handling_events: Optional[grpc.Future] = None @@ -104,15 +103,15 @@ class CoreClient: @property def client(self) -> client.CoreGrpcClient: - if self.session_id: - response = self._client.check_session(self.session_id) + if self.session: + response = self._client.check_session(self.session.id) if not response.result: throughputs_enabled = self.handling_throughputs is not None self.cancel_throughputs() self.cancel_events() - self._client.create_session(self.session_id) + self._client.create_session(self.session.id) self.handling_events = self._client.events( - self.session_id, self.handle_events + self.session.id, self.handle_events ) if throughputs_enabled: self.enable_throughputs() @@ -126,8 +125,6 @@ class CoreClient: # session data self.canvas_nodes.clear() self.links.clear() - self.hooks.clear() - self.emane_config = None self.close_mobility_players() self.mobility_players.clear() # clear streams @@ -145,12 +142,10 @@ class CoreClient: # read distributed servers for server in self.app.guiconfig.servers: self.servers[server.name] = server - # read custom nodes for custom_node in self.app.guiconfig.nodes: node_draw = NodeDraw.from_custom(custom_node) self.custom_nodes[custom_node.name] = node_draw - # read observers for observer in self.app.guiconfig.observers: self.custom_observers[observer.name] = observer @@ -158,11 +153,11 @@ class CoreClient: def handle_events(self, event: core_pb2.Event) -> None: if event.source == GUI_SOURCE: return - if event.session_id != self.session_id: + if event.session_id != self.session.id: logging.warning( "ignoring event session(%s) current(%s)", event.session_id, - self.session_id, + self.session.id, ) return @@ -173,7 +168,7 @@ class CoreClient: logging.info("session event: %s", event) session_event = event.session_event if session_event.event <= SessionState.SHUTDOWN.value: - self.state = SessionState(session_event.event) + self.session.state = SessionState(session_event.event) elif session_event.event in {7, 8, 9}: node_id = session_event.node_id dialog = self.mobility_players.get(node_id) @@ -253,7 +248,7 @@ class CoreClient: def enable_throughputs(self) -> None: self.handling_throughputs = self.client.throughputs( - self.session_id, self.handle_throughputs + self.session.id, self.handle_throughputs ) def cancel_throughputs(self) -> None: @@ -283,11 +278,11 @@ class CoreClient: def handle_throughputs(self, event: core_pb2.ThroughputsEvent) -> None: event = ThroughputsEvent.from_proto(event) - if event.session_id != self.session_id: + if event.session_id != self.session.id: logging.warning( "ignoring throughput event session(%s) current(%s)", event.session_id, - self.session_id, + self.session.id, ) return logging.debug("handling throughputs event: %s", event) @@ -300,126 +295,33 @@ class CoreClient: logging.info("exception event: %s", event) self.app.statusbar.add_alert(event) - def join_session(self, session_id: int, query_location: bool = True) -> None: - logging.info("join session(%s)", session_id) - # update session and title - self.session_id = session_id - self.master.title(f"CORE Session({self.session_id})") - - # clear session data + def join_session(self, session_id: int) -> None: + logging.info("joining session(%s)", session_id) self.reset() - - # get session data try: - response = self.client.get_session(self.session_id) - session = Session.from_proto(response.session) - self.state = session.state + response = self.client.get_session(session_id) + self.session = Session.from_proto(response.session) + self.client.set_session_user(self.session.id, self.user) + self.master.title(f"CORE Session({self.session.id})") self.handling_events = self.client.events( - self.session_id, self.handle_events + self.session.id, self.handle_events ) - - # set session user - self.client.set_session_user(self.session_id, self.user) - - # get session service defaults - response = self.client.get_service_defaults(self.session_id) - self.default_services = { - x.node_type: set(x.services) for x in response.defaults - } - - # get location - if query_location: - response = self.client.get_session_location(self.session_id) - self.location = SessionLocation.from_proto(response.location) - - # get emane models - response = self.client.get_emane_models(self.session_id) - self.emane_models = response.models - - # get hooks - response = self.client.get_hooks(self.session_id) - for hook_proto in response.hooks: - hook = Hook.from_proto(hook_proto) - self.hooks[hook.file] = hook - - # get emane config - response = self.client.get_emane_config(self.session_id) - self.emane_config = ConfigOption.from_dict(response.config) - - # update interface manager - self.ifaces_manager.joined(session.links) - - # draw session - self.app.canvas.reset_and_redraw(session) - - # get mobility configs - response = self.client.get_mobility_configs(self.session_id) - for node_id in response.configs: - config = response.configs[node_id].config - canvas_node = self.canvas_nodes[node_id] - canvas_node.mobility_config = ConfigOption.from_dict(config) - - # get emane model config - response = self.client.get_emane_model_configs(self.session_id) - for config in response.configs: - iface_id = None - if config.iface_id != -1: - iface_id = config.iface_id - canvas_node = self.canvas_nodes[config.node_id] - canvas_node.emane_model_configs[ - (config.model, iface_id) - ] = ConfigOption.from_dict(config.config) - - # get wlan configurations - response = self.client.get_wlan_configs(self.session_id) - for _id in response.configs: - mapped_config = response.configs[_id] - canvas_node = self.canvas_nodes[_id] - canvas_node.wlan_config = ConfigOption.from_dict(mapped_config.config) - - # get service configurations - response = self.client.get_node_service_configs(self.session_id) - for config in response.configs: - canvas_node = self.canvas_nodes[config.node_id] - canvas_node.service_configs[config.service] = config.data - logging.debug("service file configs: %s", config.files) - for file_name in config.files: - data = config.files[file_name] - files = canvas_node.service_file_configs.setdefault( - config.service, {} - ) - files[file_name] = data - - # get config service configurations - response = self.client.get_node_config_service_configs(self.session_id) - for config in response.configs: - canvas_node = self.canvas_nodes[config.node_id] - service_config = canvas_node.config_service_configs.setdefault( - config.name, {} - ) - if config.templates: - service_config["templates"] = config.templates - if config.config: - service_config["config"] = config.config - - # get metadata - response = self.client.get_session_metadata(self.session_id) - self.parse_metadata(response.config) + self.ifaces_manager.joined(self.session.links) + self.app.canvas.reset_and_redraw(self.session) + self.parse_metadata() + self.app.canvas.organize() + if self.is_runtime(): + self.show_mobility_players() + self.app.after(0, self.app.joined_session_update) except grpc.RpcError as e: self.app.show_grpc_exception("Join Session Error", e) - # organize canvas - self.app.canvas.organize() - if self.is_runtime(): - self.show_mobility_players() - # update ui to represent current state - self.app.after(0, self.app.joined_session_update) - def is_runtime(self) -> bool: - return self.state == SessionState.RUNTIME + return self.session and self.session.state == SessionState.RUNTIME - def parse_metadata(self, config: Dict[str, str]) -> None: + def parse_metadata(self) -> None: # canvas setting + config = self.session.metadata canvas_config = config.get("canvas") logging.debug("canvas metadata: %s", canvas_config) if canvas_config: @@ -447,7 +349,7 @@ class CoreClient: if shapes_config: shapes_config = json.loads(shapes_config) for shape_config in shapes_config: - logging.info("loading shape: %s", shape_config) + logging.debug("loading shape: %s", shape_config) shape_type = shape_config["type"] try: shape_type = ShapeType(shape_type) @@ -478,8 +380,9 @@ class CoreClient: try: response = self.client.create_session() logging.info("created session: %s", response) + self.join_session(response.session_id) location_config = self.app.guiconfig.location - self.location = SessionLocation( + self.session.location = SessionLocation( x=location_config.x, y=location_config.y, z=location_config.z, @@ -488,13 +391,12 @@ class CoreClient: alt=location_config.alt, scale=location_config.scale, ) - self.join_session(response.session_id, query_location=False) except grpc.RpcError as e: self.app.show_grpc_exception("New Session Error", e) def delete_session(self, session_id: int = None) -> None: if session_id is None: - session_id = self.session_id + session_id = self.session.id try: response = self.client.delete_session(session_id) logging.info("deleted session(%s), Result: %s", session_id, response) @@ -507,13 +409,11 @@ class CoreClient: """ try: self.client.connect() - - # get service information + # get all available services response = self.client.get_services() for service in response.services: group_services = self.services.setdefault(service.group, set()) group_services.add(service.name) - # get config service informations response = self.client.get_config_services() for service in response.services: @@ -522,7 +422,6 @@ class CoreClient: service.group, set() ) group_services.add(service.name) - # join provided session, create new session, or show dialog to select an # existing session response = self.client.get_sessions() @@ -553,14 +452,14 @@ class CoreClient: try: position = core_node.position.to_proto() self.client.edit_node( - self.session_id, core_node.id, position, source=GUI_SOURCE + self.session.id, core_node.id, position, source=GUI_SOURCE ) except grpc.RpcError as e: self.app.show_grpc_exception("Edit Node Error", e) def send_servers(self) -> None: for server in self.servers.values(): - self.client.add_session_server(self.session_id, server.name, server.address) + self.client.add_session_server(self.session.id, server.name, server.address) def start_session(self) -> Tuple[bool, List[str]]: self.ifaces_manager.reset_mac() @@ -576,26 +475,23 @@ class CoreClient: wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() emane_model_configs = self.get_emane_model_configs_proto() - hooks = [x.to_proto() for x in self.hooks.values()] + hooks = [x.to_proto() for x in self.session.hooks.values()] service_configs = self.get_service_configs_proto() file_configs = self.get_service_file_configs_proto() asymmetric_links = [ x.asymmetric_link for x in self.links.values() if x.asymmetric_link ] config_service_configs = self.get_config_service_configs_proto() - if self.emane_config: - emane_config = {x: self.emane_config[x].value for x in self.emane_config} - else: - emane_config = None + emane_config = to_dict(self.session.emane_config) result = False exceptions = [] try: self.send_servers() response = self.client.start_session( - self.session_id, + self.session.id, nodes, links, - self.location.to_proto(), + self.session.location.to_proto(), hooks, emane_config, emane_model_configs, @@ -607,7 +503,7 @@ class CoreClient: config_service_configs, ) logging.info( - "start session(%s), result: %s", self.session_id, response.result + "start session(%s), result: %s", self.session.id, response.result ) if response.result: self.set_metadata() @@ -619,7 +515,7 @@ class CoreClient: def stop_session(self, session_id: int = None) -> bool: if not session_id: - session_id = self.session_id + session_id = self.session.id result = False try: response = self.client.stop_session(session_id) @@ -630,15 +526,12 @@ class CoreClient: return result def show_mobility_players(self) -> None: - for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != NodeType.WIRELESS_LAN: + for node in self.session.nodes.values(): + if node.type != NodeType.WIRELESS_LAN: continue - if canvas_node.mobility_config: - mobility_player = MobilityPlayer( - self.app, canvas_node, canvas_node.mobility_config - ) - node_id = canvas_node.core_node.id - self.mobility_players[node_id] = mobility_player + if node.mobility_config: + mobility_player = MobilityPlayer(self.app, node) + self.mobility_players[node.id] = mobility_player mobility_player.show() def set_metadata(self) -> None: @@ -662,8 +555,8 @@ class CoreClient: shapes = json.dumps(shapes) metadata = {"canvas": canvas_config, "shapes": shapes} - response = self.client.set_session_metadata(self.session_id, metadata) - logging.info("set session metadata %s, result: %s", metadata, response) + response = self.client.set_session_metadata(self.session.id, metadata) + logging.debug("set session metadata %s, result: %s", metadata, response) def launch_terminal(self, node_id: int) -> None: try: @@ -675,7 +568,7 @@ class CoreClient: parent=self.app, ) return - response = self.client.get_node_terminal(self.session_id, node_id) + response = self.client.get_node_terminal(self.session.id, node_id) cmd = f"{terminal} {response.terminal} &" logging.info("launching terminal %s", cmd) os.system(cmd) @@ -687,10 +580,10 @@ class CoreClient: Save core session as to an xml file """ try: - if self.state != SessionState.RUNTIME: + if not self.is_runtime(): logging.debug("Send session data to the daemon") self.send_data() - response = self.client.save_xml(self.session_id, file_path) + response = self.client.save_xml(self.session.id, file_path) logging.info("saved xml file %s, result: %s", file_path, response) except grpc.RpcError as e: self.app.show_grpc_exception("Save XML Error", e) @@ -707,7 +600,7 @@ class CoreClient: self.app.show_grpc_exception("Open XML Error", e) def get_node_service(self, node_id: int, service_name: str) -> NodeServiceData: - response = self.client.get_node_service(self.session_id, node_id, service_name) + response = self.client.get_node_service(self.session.id, node_id, service_name) logging.debug( "get node(%s) %s service, response: %s", node_id, service_name, response ) @@ -724,7 +617,7 @@ class CoreClient: shutdowns: List[str], ) -> NodeServiceData: response = self.client.set_node_service( - self.session_id, + self.session.id, node_id, service_name, directories=dirs, @@ -744,14 +637,14 @@ class CoreClient: shutdowns, response, ) - response = self.client.get_node_service(self.session_id, node_id, service_name) + response = self.client.get_node_service(self.session.id, node_id, service_name) return NodeServiceData.from_proto(response.service) def get_node_service_file( self, node_id: int, service_name: str, file_name: str ) -> str: response = self.client.get_node_service_file( - self.session_id, node_id, service_name, file_name + self.session.id, node_id, service_name, file_name ) logging.debug( "get service file for node(%s), service: %s, file: %s, result: %s", @@ -766,7 +659,7 @@ class CoreClient: self, node_id: int, service_name: str, file_name: str, data: str ) -> None: response = self.client.set_node_service_file( - self.session_id, node_id, service_name, file_name, data + self.session.id, node_id, service_name, file_name, data ) logging.info( "set node(%s) service file, service: %s, file: %s, data: %s, result: %s", @@ -783,13 +676,13 @@ class CoreClient: """ node_protos = [x.core_node.to_proto() for x in self.canvas_nodes.values()] link_protos = [x.link.to_proto() for x in self.links.values()] - self.client.set_session_state(self.session_id, SessionState.DEFINITION.value) + self.client.set_session_state(self.session.id, SessionState.DEFINITION.value) for node_proto in node_protos: - response = self.client.add_node(self.session_id, node_proto) + response = self.client.add_node(self.session.id, node_proto) logging.debug("create node: %s", response) for link_proto in link_protos: response = self.client.add_link( - self.session_id, + self.session.id, link_proto.node1_id, link_proto.node2_id, link_proto.iface1, @@ -806,15 +699,15 @@ class CoreClient: self.create_nodes_and_links() for config_proto in self.get_wlan_configs_proto(): self.client.set_wlan_config( - self.session_id, config_proto.node_id, config_proto.config + self.session.id, config_proto.node_id, config_proto.config ) for config_proto in self.get_mobility_configs_proto(): self.client.set_mobility_config( - self.session_id, config_proto.node_id, config_proto.config + self.session.id, config_proto.node_id, config_proto.config ) for config_proto in self.get_service_configs_proto(): self.client.set_node_service( - self.session_id, + self.session.id, config_proto.node_id, config_proto.service, startup=config_proto.startup, @@ -823,38 +716,37 @@ class CoreClient: ) for config_proto in self.get_service_file_configs_proto(): self.client.set_node_service_file( - self.session_id, + self.session.id, config_proto.node_id, config_proto.service, config_proto.file, config_proto.data, ) - for hook in self.hooks.values(): + for hook in self.session.hooks.values(): self.client.add_hook( - self.session_id, hook.state.value, hook.file, hook.data + self.session.id, hook.state.value, hook.file, hook.data ) for config_proto in self.get_emane_model_configs_proto(): self.client.set_emane_model_config( - self.session_id, + self.session.id, config_proto.node_id, config_proto.model, config_proto.config, config_proto.iface_id, ) - if self.emane_config: - config = {x: self.emane_config[x].value for x in self.emane_config} - self.client.set_emane_config(self.session_id, config) - if self.location: - self.client.set_session_location( - self.session_id, - self.location.x, - self.location.y, - self.location.z, - self.location.lat, - self.location.lon, - self.location.alt, - self.location.scale, - ) + config = to_dict(self.session.emane_config) + self.client.set_emane_config(self.session.id, config) + location = self.session.location + self.client.set_session_location( + self.session.id, + location.x, + location.y, + location.z, + location.lat, + location.lon, + location.alt, + location.scale, + ) self.set_metadata() def close(self) -> None: @@ -888,16 +780,16 @@ class CoreClient: image = "ubuntu:latest" emane = None if node_type == NodeType.EMANE: - if not self.emane_models: + if not self.session.emane_models: dialog = EmaneInstallDialog(self.app) dialog.show() return - emane = self.emane_models[0] - name = f"EMANE{node_id}" + emane = self.session.emane_models[0] + name = f"emane{node_id}" elif node_type == NodeType.WIRELESS_LAN: - name = f"WLAN{node_id}" + name = f"wlan{node_id}" elif node_type in [NodeType.RJ45, NodeType.TUNNEL]: - name = "UNASSIGNED" + name = "unassigned" else: name = f"n{node_id}" node = Node( @@ -914,13 +806,13 @@ class CoreClient: node.services[:] = services # assign default services to CORE node else: - services = self.default_services.get(model) + services = self.session.default_services.get(model) if services: - node.services[:] = services + node.services = services.copy() logging.info( "add node(%s) to session(%s), coordinates(%s, %s)", node.name, - self.session_id, + self.session.id, x, y, ) @@ -1005,60 +897,56 @@ class CoreClient: def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != NodeType.WIRELESS_LAN: + for node in self.session.nodes.values(): + if node.type != NodeType.WIRELESS_LAN: continue - if not canvas_node.wlan_config: + if not node.wlan_config: continue - config = ConfigOption.to_dict(canvas_node.wlan_config) - node_id = canvas_node.core_node.id - wlan_config = wlan_pb2.WlanConfig(node_id=node_id, config=config) + config = ConfigOption.to_dict(node.wlan_config) + wlan_config = wlan_pb2.WlanConfig(node_id=node.id, config=config) configs.append(wlan_config) return configs def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != NodeType.WIRELESS_LAN: + for node in self.session.nodes.values(): + if node.type != NodeType.WIRELESS_LAN: continue - if not canvas_node.mobility_config: + if not node.mobility_config: continue - config = ConfigOption.to_dict(canvas_node.mobility_config) - node_id = canvas_node.core_node.id + config = ConfigOption.to_dict(node.mobility_config) mobility_config = mobility_pb2.MobilityConfig( - node_id=node_id, config=config + node_id=node.id, config=config ) configs.append(mobility_config) return configs def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != NodeType.EMANE: + for node in self.session.nodes.values(): + if node.type != NodeType.EMANE: continue - node_id = canvas_node.core_node.id - for key, config in canvas_node.emane_model_configs.items(): + for key, config in node.emane_model_configs.items(): model, iface_id = key config = ConfigOption.to_dict(config) if iface_id is None: iface_id = -1 config_proto = emane_pb2.EmaneModelConfig( - node_id=node_id, iface_id=iface_id, model=model, config=config + node_id=node.id, iface_id=iface_id, model=model, config=config ) configs.append(config_proto) return configs def get_service_configs_proto(self) -> List[services_pb2.ServiceConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if not NodeUtils.is_container_node(canvas_node.core_node.type): + for node in self.session.nodes.values(): + if not NodeUtils.is_container_node(node.type): continue - if not canvas_node.service_configs: + if not node.service_configs: continue - node_id = canvas_node.core_node.id - for name, config in canvas_node.service_configs.items(): + for name, config in node.service_configs.items(): config_proto = services_pb2.ServiceConfig( - node_id=node_id, + node_id=node.id, service=name, directories=config.dirs, files=config.configs, @@ -1071,16 +959,15 @@ class CoreClient: def get_service_file_configs_proto(self) -> List[services_pb2.ServiceFileConfig]: configs = [] - for canvas_node in self.canvas_nodes.values(): - if not NodeUtils.is_container_node(canvas_node.core_node.type): + for node in self.session.nodes.values(): + if not NodeUtils.is_container_node(node.type): continue - if not canvas_node.service_file_configs: + if not node.service_file_configs: continue - node_id = canvas_node.core_node.id - for service, file_configs in canvas_node.service_file_configs.items(): + for service, file_configs in node.service_file_configs.items(): for file, data in file_configs.items(): config_proto = services_pb2.ServiceFileConfig( - node_id=node_id, service=service, file=file, data=data + node_id=node.id, service=service, file=file, data=data ) configs.append(config_proto) return configs @@ -1089,29 +976,27 @@ class CoreClient: self ) -> List[configservices_pb2.ConfigServiceConfig]: config_service_protos = [] - for canvas_node in self.canvas_nodes.values(): - if not NodeUtils.is_container_node(canvas_node.core_node.type): + for node in self.session.nodes.values(): + if not NodeUtils.is_container_node(node.type): continue - if not canvas_node.config_service_configs: + if not node.config_service_configs: continue - node_id = canvas_node.core_node.id - for name, service_config in canvas_node.config_service_configs.items(): - config = service_config.get("config", {}) + for name, service_config in node.config_service_configs.items(): config_proto = configservices_pb2.ConfigServiceConfig( - node_id=node_id, + node_id=node.id, name=name, - templates=service_config["templates"], - config=config, + templates=service_config.templates, + config=service_config.config, ) config_service_protos.append(config_proto) return config_service_protos def run(self, node_id: int) -> str: logging.info("running node(%s) cmd: %s", node_id, self.observer) - return self.client.node_command(self.session_id, node_id, self.observer).output + return self.client.node_command(self.session.id, node_id, self.observer).output def get_wlan_config(self, node_id: int) -> Dict[str, ConfigOption]: - response = self.client.get_wlan_config(self.session_id, node_id) + response = self.client.get_wlan_config(self.session.id, node_id) config = response.config logging.debug( "get wlan configuration from node %s, result configuration: %s", @@ -1121,7 +1006,7 @@ class CoreClient: return ConfigOption.from_dict(config) def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]: - response = self.client.get_mobility_config(self.session_id, node_id) + response = self.client.get_mobility_config(self.session.id, node_id) config = response.config logging.debug( "get mobility config from node %s, result configuration: %s", @@ -1136,7 +1021,7 @@ class CoreClient: if iface_id is None: iface_id = -1 response = self.client.get_emane_model_config( - self.session_id, node_id, model, iface_id + self.session.id, node_id, model, iface_id ) config = response.config logging.debug( diff --git a/daemon/core/gui/dialogs/canvassizeandscale.py b/daemon/core/gui/dialogs/canvassizeandscale.py index 38cecc83..e8ad6693 100644 --- a/daemon/core/gui/dialogs/canvassizeandscale.py +++ b/daemon/core/gui/dialogs/canvassizeandscale.py @@ -27,7 +27,7 @@ class SizeAndScaleDialog(Dialog): width, height = self.canvas.current_dimensions self.pixel_width: tk.IntVar = tk.IntVar(value=width) self.pixel_height: tk.IntVar = tk.IntVar(value=height) - location = self.app.core.location + location = self.app.core.session.location self.x: tk.DoubleVar = tk.DoubleVar(value=location.x) self.y: tk.DoubleVar = tk.DoubleVar(value=location.y) self.lat: tk.DoubleVar = tk.DoubleVar(value=location.lat) @@ -192,7 +192,7 @@ class SizeAndScaleDialog(Dialog): self.canvas.redraw_canvas((width, height)) if self.canvas.wallpaper: self.canvas.redraw_wallpaper() - location = self.app.core.location + location = self.app.core.session.location location.x = self.x.get() location.y = self.y.get() location.lat = self.lat.get() diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 5463d88e..f778cf15 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -11,28 +11,26 @@ import grpc from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll -from core.gui.wrappers import ConfigOption, ServiceValidationMode +from core.gui.wrappers import ( + ConfigOption, + ConfigServiceData, + Node, + ServiceValidationMode, +) if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode from core.gui.coreclient import CoreClient class ConfigServiceConfigDialog(Dialog): def __init__( - self, - master: tk.BaseWidget, - app: "Application", - service_name: str, - canvas_node: "CanvasNode", - node_id: int, + self, master: tk.BaseWidget, app: "Application", service_name: str, node: Node ) -> None: title = f"{service_name} Config Service" super().__init__(app, title, master=master) self.core: "CoreClient" = app.core - self.canvas_node: "CanvasNode" = canvas_node - self.node_id: int = node_id + self.node: Node = node self.service_name: str = service_name self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(2) @@ -50,7 +48,7 @@ class ConfigServiceConfigDialog(Dialog): self.validation_time: Optional[int] = None self.validation_period: tk.StringVar = tk.StringVar() self.modes: List[str] = [] - self.mode_configs: Dict[str, str] = {} + self.mode_configs: Dict[str, Dict[str, str]] = {} self.notebook: Optional[ttk.Notebook] = None self.templates_combobox: Optional[ttk.Combobox] = None @@ -91,25 +89,18 @@ class ConfigServiceConfigDialog(Dialog): response = self.core.client.get_config_service_defaults(self.service_name) self.original_service_files = response.templates self.temp_service_files = dict(self.original_service_files) - self.modes = sorted(x.name for x in response.modes) self.mode_configs = {x.name: x.config for x in response.modes} - - service_config = self.canvas_node.config_service_configs.get( - self.service_name, {} - ) self.config = ConfigOption.from_dict(response.config) self.default_config = {x.name: x.value for x in self.config.values()} - custom_config = service_config.get("config") - if custom_config: - for key, value in custom_config.items(): + service_config = self.node.config_service_configs.get(self.service_name) + if service_config: + for key, value in service_config.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 + logging.info("default config: %s", self.default_config) + for file, data in service_config.templates.items(): + self.modified_files.add(file) + self.temp_service_files[file] = data except grpc.RpcError as e: self.app.show_grpc_exception("Get Config Service Error", e) self.has_error = True @@ -313,20 +304,18 @@ class ConfigServiceConfigDialog(Dialog): def click_apply(self) -> None: current_listbox = self.master.current.listbox if not self.is_custom(): - self.canvas_node.config_service_configs.pop(self.service_name, None) + self.node.config_service_configs.pop(self.service_name, None) current_listbox.itemconfig(current_listbox.curselection()[0], bg="") self.destroy() return - - service_config = self.canvas_node.config_service_configs.setdefault( - self.service_name, {} - ) + service_config = self.node.config_service_configs.get(self.service_name) + if not service_config: + service_config = ConfigServiceData() if self.config_frame: self.config_frame.parse_config() - service_config["config"] = {x.name: x.value for x in self.config.values()} - templates_config = service_config.setdefault("templates", {}) + service_config.config = {x.name: x.value for x in self.config.values()} for file in self.modified_files: - templates_config[file] = self.temp_service_files[file] + service_config.templates[file] = self.temp_service_files[file] all_current = current_listbox.get(0, tk.END) current_listbox.itemconfig(all_current.index(self.service_name), bg="green") self.destroy() @@ -360,9 +349,9 @@ class ConfigServiceConfigDialog(Dialog): return has_custom_templates or has_custom_config def click_defaults(self) -> None: - self.canvas_node.config_service_configs.pop(self.service_name, None) + self.node.config_service_configs.pop(self.service_name, None) logging.info( - "cleared config service config: %s", self.canvas_node.config_service_configs + "cleared config service config: %s", self.node.config_service_configs ) self.temp_service_files = dict(self.original_service_files) filename = self.templates_combobox.get() diff --git a/daemon/core/gui/dialogs/copyserviceconfig.py b/daemon/core/gui/dialogs/copyserviceconfig.py index 2a01249d..b60d5a0d 100644 --- a/daemon/core/gui/dialogs/copyserviceconfig.py +++ b/daemon/core/gui/dialogs/copyserviceconfig.py @@ -43,16 +43,15 @@ class CopyServiceConfigDialog(Dialog): listbox_scroll = ListboxScroll(self.top) listbox_scroll.grid(sticky="nsew", pady=PADY) self.listbox = listbox_scroll.listbox - for canvas_node in self.app.canvas.nodes.values(): - file_configs = canvas_node.service_file_configs.get(self.service) + for node in self.app.core.session.nodes.values(): + file_configs = node.service_file_configs.get(self.service) if not file_configs: continue data = file_configs.get(self.file_name) if not data: continue - name = canvas_node.core_node.name - self.nodes[name] = canvas_node.id - self.listbox.insert(tk.END, name) + self.nodes[node.name] = node.id + self.listbox.insert(tk.END, node.name) frame = ttk.Frame(self.top) frame.grid(sticky="ew") @@ -70,9 +69,9 @@ class CopyServiceConfigDialog(Dialog): if not selection: return name = self.listbox.get(selection) - canvas_node_id = self.nodes[name] - canvas_node = self.app.canvas.nodes[canvas_node_id] - data = canvas_node.service_file_configs[self.service][self.file_name] + node_id = self.nodes[name] + node = self.app.core.session.nodes[node_id] + data = node.service_file_configs[self.service][self.file_name] self.dialog.temp_service_files[self.file_name] = data self.dialog.modified_files.add(self.file_name) self.dialog.service_file_data.text.delete(1.0, tk.END) @@ -84,9 +83,9 @@ class CopyServiceConfigDialog(Dialog): if not selection: return name = self.listbox.get(selection) - canvas_node_id = self.nodes[name] - canvas_node = self.app.canvas.nodes[canvas_node_id] - data = canvas_node.service_file_configs[self.service][self.file_name] + node_id = self.nodes[name] + node = self.app.core.session.nodes[node_id] + data = node.service_file_configs[self.service][self.file_name] dialog = ViewConfigDialog( self.app, self, name, self.service, self.file_name, data ) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index d87e935a..019eeaa9 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -16,7 +16,6 @@ from core.gui.wrappers import ConfigOption, Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode class GlobalEmaneDialog(Dialog): @@ -29,8 +28,9 @@ class GlobalEmaneDialog(Dialog): def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) + session = self.app.core.session self.config_frame = ConfigFrame( - self.top, self.app, self.app.core.emane_config, self.enabled + self.top, self.app, session.emane_config, self.enabled ) self.config_frame.draw_config() self.config_frame.grid(sticky="nsew", pady=PADY) @@ -58,24 +58,19 @@ class EmaneModelDialog(Dialog): self, master: tk.BaseWidget, app: "Application", - canvas_node: "CanvasNode", + node: Node, model: str, iface_id: int = None, ) -> None: - super().__init__( - app, f"{canvas_node.core_node.name} {model} Configuration", master=master - ) - self.canvas_node: "CanvasNode" = canvas_node - self.node: Node = canvas_node.core_node + super().__init__(app, f"{node.name} {model} Configuration", master=master) + self.node: Node = node self.model: str = f"emane_{model}" self.iface_id: int = iface_id self.config_frame: Optional[ConfigFrame] = None self.enabled: bool = not self.app.core.is_runtime() self.has_error: bool = False try: - config = self.canvas_node.emane_model_configs.get( - (self.model, self.iface_id) - ) + config = self.node.emane_model_configs.get((self.model, self.iface_id)) if not config: config = self.app.core.get_emane_model_config( self.node.id, self.model, self.iface_id @@ -110,19 +105,18 @@ class EmaneModelDialog(Dialog): def click_apply(self) -> None: self.config_frame.parse_config() key = (self.model, self.iface_id) - self.canvas_node.emane_model_configs[key] = self.config + self.node.emane_model_configs[key] = self.config self.destroy() class EmaneConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: - super().__init__(app, f"{canvas_node.core_node.name} EMANE Configuration") - self.canvas_node: "CanvasNode" = canvas_node - self.node: Node = canvas_node.core_node + def __init__(self, app: "Application", node: Node) -> None: + super().__init__(app, f"{node.name} EMANE Configuration") + self.node: Node = node self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(1) self.emane_models: List[str] = [ - x.split("_")[1] for x in self.app.core.emane_models + x.split("_")[1] for x in self.app.core.session.emane_models ] model = self.node.emane.split("_")[1] self.emane_model: tk.StringVar = tk.StringVar(value=model) @@ -231,7 +225,7 @@ class EmaneConfigDialog(Dialog): draw emane model configuration """ model_name = self.emane_model.get() - dialog = EmaneModelDialog(self, self.app, self.canvas_node, model_name) + dialog = EmaneModelDialog(self, self.app, self.node, model_name) if not dialog.has_error: dialog.show() diff --git a/daemon/core/gui/dialogs/hooks.py b/daemon/core/gui/dialogs/hooks.py index b004dae2..31ef3e15 100644 --- a/daemon/core/gui/dialogs/hooks.py +++ b/daemon/core/gui/dialogs/hooks.py @@ -113,8 +113,9 @@ class HooksDialog(Dialog): listbox_scroll.grid(sticky="nsew", pady=PADY) self.listbox = listbox_scroll.listbox self.listbox.bind("<>", self.select) - for hook_file in self.app.core.hooks: - self.listbox.insert(tk.END, hook_file) + session = self.app.core.session + for file in session.hooks: + self.listbox.insert(tk.END, file) frame = ttk.Frame(self.top) frame.grid(sticky="ew") @@ -138,20 +139,22 @@ class HooksDialog(Dialog): dialog.show() hook = dialog.hook if hook: - self.app.core.hooks[hook.file] = hook + self.app.core.session.hooks[hook.file] = hook self.listbox.insert(tk.END, hook.file) def click_edit(self) -> None: - hook = self.app.core.hooks.pop(self.selected) + session = self.app.core.session + hook = session.hooks.pop(self.selected) dialog = HookDialog(self, self.app) dialog.set(hook) dialog.show() - self.app.core.hooks[hook.file] = hook + session.hooks[hook.file] = hook self.listbox.delete(self.selected_index) self.listbox.insert(self.selected_index, hook.file) def click_delete(self) -> None: - del self.app.core.hooks[self.selected] + session = self.app.core.session + del session.hooks[self.selected] self.listbox.delete(tk.ANCHOR) self.edit_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED) diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 87f43284..2a91da30 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -269,7 +269,7 @@ class LinkConfigurationDialog(Dialog): self.edge.asymmetric_link = None if self.app.core.is_runtime() and link.options: - session_id = self.app.core.session_id + session_id = self.app.core.session.id self.app.core.client.edit_link( session_id, link.node1_id, diff --git a/daemon/core/gui/dialogs/mobilityconfig.py b/daemon/core/gui/dialogs/mobilityconfig.py index ca9caf43..857167be 100644 --- a/daemon/core/gui/dialogs/mobilityconfig.py +++ b/daemon/core/gui/dialogs/mobilityconfig.py @@ -13,18 +13,16 @@ from core.gui.wrappers import ConfigOption, Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode class MobilityConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: - super().__init__(app, f"{canvas_node.core_node.name} Mobility Configuration") - self.canvas_node: "CanvasNode" = canvas_node - self.node: Node = canvas_node.core_node + def __init__(self, app: "Application", node: Node) -> None: + super().__init__(app, f"{node.name} Mobility Configuration") + self.node: Node = node self.config_frame: Optional[ConfigFrame] = None self.has_error: bool = False try: - config = self.canvas_node.mobility_config + config = self.node.mobility_config if not config: config = self.app.core.get_mobility_config(self.node.id) self.config: Dict[str, ConfigOption] = config @@ -56,5 +54,5 @@ class MobilityConfigDialog(Dialog): def click_apply(self) -> None: self.config_frame.parse_config() - self.canvas_node.mobility_config = self.config + self.node.mobility_config = self.config self.destroy() diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index 66833aff..1bee97d2 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -1,38 +1,31 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, Dict, Optional +from typing import TYPE_CHECKING, Optional import grpc from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum from core.gui.themes import PADX, PADY -from core.gui.wrappers import ConfigOption, MobilityAction, Node +from core.gui.wrappers import MobilityAction, Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode ICON_SIZE: int = 16 class MobilityPlayer: - def __init__( - self, - app: "Application", - canvas_node: "CanvasNode", - config: Dict[str, ConfigOption], - ) -> None: + def __init__(self, app: "Application", node: Node) -> None: self.app: "Application" = app - self.canvas_node: "CanvasNode" = canvas_node - self.config: Dict[str, ConfigOption] = config + self.node: Node = node self.dialog: Optional[MobilityPlayerDialog] = None self.state: Optional[MobilityAction] = None def show(self) -> None: if self.dialog: self.dialog.destroy() - self.dialog = MobilityPlayerDialog(self.app, self.canvas_node, self.config) + self.dialog = MobilityPlayerDialog(self.app, self.node) self.dialog.protocol("WM_DELETE_WINDOW", self.close) if self.state == MobilityAction.START: self.set_play() @@ -64,20 +57,11 @@ class MobilityPlayer: class MobilityPlayerDialog(Dialog): - def __init__( - self, - app: "Application", - canvas_node: "CanvasNode", - config: Dict[str, ConfigOption], - ) -> None: - super().__init__( - app, f"{canvas_node.core_node.name} Mobility Player", modal=False - ) + def __init__(self, app: "Application", node: Node) -> None: + super().__init__(app, f"{node.name} Mobility Player", modal=False) self.resizable(False, False) self.geometry("") - self.canvas_node: "CanvasNode" = canvas_node - self.node: Node = canvas_node.core_node - self.config: Dict[str, ConfigOption] = config + self.node: Node = node self.play_button: Optional[ttk.Button] = None self.pause_button: Optional[ttk.Button] = None self.stop_button: Optional[ttk.Button] = None @@ -85,9 +69,10 @@ class MobilityPlayerDialog(Dialog): self.draw() def draw(self) -> None: + config = self.node.mobility_config self.top.columnconfigure(0, weight=1) - file_name = self.config["file"].value + file_name = config["file"].value label = ttk.Label(self.top, text=file_name) label.grid(sticky="ew", pady=PADY) @@ -114,13 +99,13 @@ class MobilityPlayerDialog(Dialog): self.stop_button.image = image self.stop_button.grid(row=0, column=2, sticky="ew", padx=PADX) - loop = tk.IntVar(value=int(self.config["loop"].value == "1")) + loop = tk.IntVar(value=int(config["loop"].value == "1")) checkbutton = ttk.Checkbutton( frame, text="Loop?", variable=loop, state=tk.DISABLED ) checkbutton.grid(row=0, column=3, padx=PADX) - rate = self.config["refresh_ms"].value + rate = config["refresh_ms"].value label = ttk.Label(frame, text=f"rate {rate} ms") label.grid(row=0, column=4) @@ -146,7 +131,7 @@ class MobilityPlayerDialog(Dialog): def click_play(self) -> None: self.set_play() - session_id = self.app.core.session_id + session_id = self.app.core.session.id try: self.app.core.client.mobility_action( session_id, self.node.id, MobilityAction.START.value @@ -156,7 +141,7 @@ class MobilityPlayerDialog(Dialog): def click_pause(self) -> None: self.set_pause() - session_id = self.app.core.session_id + session_id = self.app.core.session.id try: self.app.core.client.mobility_action( session_id, self.node.id, MobilityAction.PAUSE.value @@ -166,7 +151,7 @@ class MobilityPlayerDialog(Dialog): def click_stop(self) -> None: self.set_stop() - session_id = self.app.core.session_id + session_id = self.app.core.session.id try: self.app.core.client.mobility_action( session_id, self.node.id, MobilityAction.STOP.value diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index b9a9a1f5..dee34f71 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -10,25 +10,24 @@ from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CheckboxList, ListboxScroll +from core.gui.wrappers import Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode class NodeConfigServiceDialog(Dialog): def __init__( - self, app: "Application", canvas_node: "CanvasNode", services: Set[str] = None + self, app: "Application", node: Node, services: Set[str] = None ) -> None: - title = f"{canvas_node.core_node.name} Config Services" + title = f"{node.name} Config Services" super().__init__(app, title) - self.canvas_node: "CanvasNode" = canvas_node - self.node_id: int = canvas_node.core_node.id + self.node: Node = node self.groups: Optional[ListboxScroll] = None self.services: Optional[CheckboxList] = None self.current: Optional[ListboxScroll] = None if services is None: - services = set(canvas_node.core_node.config_services) + services = set(node.config_services) self.current_services: Set[str] = services self.draw() @@ -102,7 +101,7 @@ class NodeConfigServiceDialog(Dialog): elif not var.get() and name in self.current_services: self.current_services.remove(name) self.draw_current_services() - self.canvas_node.core_node.config_services[:] = self.current_services + self.node.config_services[:] = self.current_services def click_configure(self) -> None: current_selection = self.current.listbox.curselection() @@ -111,8 +110,7 @@ class NodeConfigServiceDialog(Dialog): self, self.app, self.current.listbox.get(current_selection[0]), - self.canvas_node, - self.node_id, + self.node, ) if not dialog.has_error: dialog.show() @@ -132,10 +130,8 @@ class NodeConfigServiceDialog(Dialog): self.current.listbox.itemconfig(tk.END, bg="green") def click_save(self) -> None: - self.canvas_node.core_node.config_services[:] = self.current_services - logging.info( - "saved node config services: %s", self.canvas_node.core_node.config_services - ) + self.node.config_services[:] = self.current_services + logging.info("saved node config services: %s", self.node.config_services) self.destroy() def click_cancel(self) -> None: @@ -154,4 +150,4 @@ class NodeConfigServiceDialog(Dialog): return def is_custom_service(self, service: str) -> bool: - return service in self.canvas_node.config_service_configs + return service in self.node.config_service_configs diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index 6fcc2912..a56736d5 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -9,22 +9,21 @@ from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.serviceconfig import ServiceConfigDialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CheckboxList, ListboxScroll +from core.gui.wrappers import Node if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode class NodeServiceDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: - title = f"{canvas_node.core_node.name} Services" + def __init__(self, app: "Application", node: Node) -> None: + title = f"{node.name} Services" super().__init__(app, title) - self.canvas_node: "CanvasNode" = canvas_node - self.node_id: int = canvas_node.core_node.id + self.node: Node = node self.groups: Optional[ListboxScroll] = None self.services: Optional[CheckboxList] = None self.current: Optional[ListboxScroll] = None - services = set(canvas_node.core_node.services) + services = set(node.services) self.current_services: Set[str] = services self.draw() @@ -104,7 +103,7 @@ class NodeServiceDialog(Dialog): self.current.listbox.insert(tk.END, name) if self.is_custom_service(name): self.current.listbox.itemconfig(tk.END, bg="green") - self.canvas_node.core_node.services[:] = self.current_services + self.node.services = self.current_services.copy() def click_configure(self) -> None: current_selection = self.current.listbox.curselection() @@ -113,8 +112,7 @@ class NodeServiceDialog(Dialog): self, self.app, self.current.listbox.get(current_selection[0]), - self.canvas_node, - self.node_id, + self.node, ) # if error occurred when creating ServiceConfigDialog, don't show the dialog @@ -128,8 +126,7 @@ class NodeServiceDialog(Dialog): ) def click_save(self) -> None: - core_node = self.canvas_node.core_node - core_node.services[:] = self.current_services + self.node.services[:] = self.current_services self.destroy() def click_remove(self) -> None: @@ -144,6 +141,6 @@ class NodeServiceDialog(Dialog): return def is_custom_service(self, service: str) -> bool: - has_service_config = service in self.canvas_node.service_configs - has_file_config = service in self.canvas_node.service_file_configs + has_service_config = service in self.node.service_configs + has_file_config = service in self.node.service_file_configs return has_service_config or has_file_config diff --git a/daemon/core/gui/dialogs/runtool.py b/daemon/core/gui/dialogs/runtool.py index c66fea8f..a1517593 100644 --- a/daemon/core/gui/dialogs/runtool.py +++ b/daemon/core/gui/dialogs/runtool.py @@ -107,7 +107,7 @@ class RunToolDialog(Dialog): node_name = self.node_list.listbox.get(selection) node_id = self.executable_nodes[node_name] response = self.app.core.client.node_command( - self.app.core.session_id, node_id, command + self.app.core.session.id, node_id, command ) self.result.text.insert( tk.END, f"> {node_name} > {command}:\n{response.output}\n" diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index c033cfdc..13be0bcd 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -12,11 +12,10 @@ from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CodeText, ListboxScroll -from core.gui.wrappers import NodeServiceData, ServiceValidationMode +from core.gui.wrappers import Node, NodeServiceData, ServiceValidationMode if TYPE_CHECKING: from core.gui.app import Application - from core.gui.graph.node import CanvasNode from core.gui.coreclient import CoreClient ICON_SIZE: int = 16 @@ -24,18 +23,12 @@ ICON_SIZE: int = 16 class ServiceConfigDialog(Dialog): def __init__( - self, - master: tk.BaseWidget, - app: "Application", - service_name: str, - canvas_node: "CanvasNode", - node_id: int, + self, master: tk.BaseWidget, app: "Application", service_name: str, node: Node ) -> None: title = f"{service_name} Service" super().__init__(app, title, master=master) self.core: "CoreClient" = app.core - self.canvas_node: "CanvasNode" = canvas_node - self.node_id: int = node_id + self.node: Node = node self.service_name: str = service_name self.radiovar: tk.IntVar = tk.IntVar(value=2) self.metadata: str = "" @@ -84,15 +77,13 @@ class ServiceConfigDialog(Dialog): try: self.app.core.create_nodes_and_links() 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_validate = default_config.validate[:] self.default_shutdown = default_config.shutdown[:] self.default_directories = default_config.dirs[:] - custom_service_config = self.canvas_node.service_configs.get( - self.service_name - ) + custom_service_config = self.node.service_configs.get(self.service_name) self.default_config = default_config service_config = ( custom_service_config if custom_service_config else default_config @@ -109,15 +100,13 @@ class ServiceConfigDialog(Dialog): self.temp_directories = service_config.dirs[:] self.original_service_files = { x: self.app.core.get_node_service_file( - self.node_id, self.service_name, x + self.node.id, self.service_name, x ) for x in default_config.configs } self.temp_service_files = dict(self.original_service_files) - file_configs = self.canvas_node.service_file_configs.get( - self.service_name, {} - ) + file_configs = self.node.service_file_configs.get(self.service_name, {}) for file, data in file_configs.items(): self.temp_service_files[file] = data except grpc.RpcError as e: @@ -453,7 +442,7 @@ class ServiceConfigDialog(Dialog): and not self.has_new_files() and not self.is_custom_directory() ): - self.canvas_node.service_configs.pop(self.service_name, None) + self.node.service_configs.pop(self.service_name, None) self.current_service_color("") self.destroy() return @@ -466,7 +455,7 @@ class ServiceConfigDialog(Dialog): ): startup, validate, shutdown = self.get_commands() config = self.core.set_node_service( - self.node_id, + self.node.id, self.service_name, dirs=self.temp_directories, files=list(self.filename_combobox["values"]), @@ -474,15 +463,15 @@ class ServiceConfigDialog(Dialog): validations=validate, shutdowns=shutdown, ) - self.canvas_node.service_configs[self.service_name] = config + self.node.service_configs[self.service_name] = config for file in self.modified_files: - file_configs = self.canvas_node.service_file_configs.setdefault( + file_configs = self.node.service_file_configs.setdefault( self.service_name, {} ) file_configs[file] = self.temp_service_files[file] # TODO: check if this is really needed self.app.core.set_node_service_file( - self.node_id, self.service_name, file, self.temp_service_files[file] + self.node.id, self.service_name, file, self.temp_service_files[file] ) self.current_service_color("green") except grpc.RpcError as e: @@ -526,8 +515,8 @@ class ServiceConfigDialog(Dialog): clears out any custom configuration permanently """ # clear coreclient data - self.canvas_node.service_configs.pop(self.service_name, None) - file_configs = self.canvas_node.service_file_configs.pop(self.service_name, {}) + self.node.service_configs.pop(self.service_name, None) + file_configs = self.node.service_file_configs.pop(self.service_name, {}) file_configs.pop(self.service_name, None) self.temp_service_files = dict(self.original_service_files) self.modified_files.clear() @@ -564,9 +553,8 @@ class ServiceConfigDialog(Dialog): def click_copy(self) -> None: file_name = self.filename_combobox.get() - name = self.canvas_node.core_node.name dialog = CopyServiceConfigDialog( - self.app, self, name, self.service_name, file_name + self.app, self, self.node.name, self.service_name, file_name ) dialog.show() diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index 24bacb30..570bfbde 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -26,7 +26,7 @@ class SessionOptionsDialog(Dialog): def get_config(self) -> Dict[str, ConfigOption]: try: - session_id = self.app.core.session_id + session_id = self.app.core.session.id response = self.app.core.client.get_session_options(session_id) return ConfigOption.from_dict(response.config) except grpc.RpcError as e: @@ -54,7 +54,7 @@ class SessionOptionsDialog(Dialog): def save(self) -> None: config = self.config_frame.parse_config() try: - session_id = self.app.core.session_id + session_id = self.app.core.session.id response = self.app.core.client.set_session_options(session_id, config) logging.info("saved session config: %s", response) except grpc.RpcError as e: diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index 75b9dcf4..d41e2052 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -201,7 +201,7 @@ class SessionsDialog(Dialog): logging.debug("delete session: %s", self.selected_session) self.tree.delete(self.selected_id) self.app.core.delete_session(self.selected_session) - if self.selected_session == self.app.core.session_id: + if self.selected_session == self.app.core.session.id: self.click_new() self.destroy() self.click_select() diff --git a/daemon/core/gui/dialogs/wlanconfig.py b/daemon/core/gui/dialogs/wlanconfig.py index 17f62dfb..d4595556 100644 --- a/daemon/core/gui/dialogs/wlanconfig.py +++ b/daemon/core/gui/dialogs/wlanconfig.py @@ -29,7 +29,7 @@ class WlanConfigDialog(Dialog): self.ranges: Dict[int, int] = {} self.positive_int: int = self.app.master.register(self.validate_and_update) try: - config = self.canvas_node.wlan_config + config = self.node.wlan_config if not config: config = self.app.core.get_wlan_config(self.node.id) self.config: Dict[str, ConfigOption] = config @@ -83,9 +83,9 @@ class WlanConfigDialog(Dialog): retrieve user's wlan configuration and store the new configuration values """ config = self.config_frame.parse_config() - self.canvas_node.wlan_config = self.config + self.node.wlan_config = self.config if self.app.core.is_runtime(): - session_id = self.app.core.session_id + session_id = self.app.core.session.id self.app.core.client.set_wlan_config(session_id, self.node.id, config) self.remove_ranges() self.destroy() diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index f2a27444..54d2cae1 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -940,16 +940,19 @@ class CanvasGraph(tk.Canvas): if not copy: continue node = CanvasNode(self.app, scaled_x, scaled_y, copy, canvas_node.image) - # copy configurations and services - node.core_node.services[:] = canvas_node.core_node.services - node.core_node.config_services[:] = canvas_node.core_node.config_services - node.emane_model_configs = deepcopy(canvas_node.emane_model_configs) - node.wlan_config = deepcopy(canvas_node.wlan_config) - node.mobility_config = deepcopy(canvas_node.mobility_config) - node.service_configs = deepcopy(canvas_node.service_configs) - node.service_file_configs = deepcopy(canvas_node.service_file_configs) - node.config_service_configs = deepcopy(canvas_node.config_service_configs) + node.core_node.services = core_node.services.copy() + node.core_node.config_services = core_node.config_services.copy() + node.core_node.emane_model_configs = deepcopy(core_node.emane_model_configs) + node.core_node.wlan_config = deepcopy(core_node.wlan_config) + node.core_node.mobility_config = deepcopy(core_node.mobility_config) + node.core_node.service_configs = deepcopy(core_node.service_configs) + node.core_node.service_file_configs = deepcopy( + core_node.service_file_configs + ) + node.core_node.config_service_configs = deepcopy( + core_node.config_service_configs + ) copy_map[canvas_node.id] = node.id self.core.canvas_nodes[copy.id] = node diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 217389c0..ffc72fbf 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -1,7 +1,7 @@ import functools import logging import tkinter as tk -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Dict, List, Set import grpc from PIL.ImageTk import PhotoImage @@ -19,7 +19,7 @@ from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils -from core.gui.wrappers import ConfigOption, Interface, Node, NodeServiceData, NodeType +from core.gui.wrappers import Interface, Node, NodeType if TYPE_CHECKING: from core.gui.app import Application @@ -56,15 +56,6 @@ class CanvasNode: self.wireless_edges: Set[CanvasWirelessEdge] = set() self.antennas: List[int] = [] self.antenna_images: Dict[int, PhotoImage] = {} - # possible configurations - self.emane_model_configs: Dict[ - Tuple[str, Optional[int]], Dict[str, ConfigOption] - ] = {} - self.wlan_config: Dict[str, ConfigOption] = {} - self.mobility_config: Dict[str, ConfigOption] = {} - self.service_configs: Dict[str, NodeServiceData] = {} - self.service_file_configs: Dict[str, Dict[str, str]] = {} - self.config_service_configs: Dict[str, Any] = {} self.setup_bindings() self.context: tk.Menu = tk.Menu(self.canvas) themes.style_menu(self.context) @@ -299,7 +290,7 @@ class CanvasNode: dialog.show() def show_mobility_config(self) -> None: - dialog = MobilityConfigDialog(self.app, self) + dialog = MobilityConfigDialog(self.app, self.core_node) if not dialog.has_error: dialog.show() @@ -308,15 +299,15 @@ class CanvasNode: mobility_player.show() def show_emane_config(self) -> None: - dialog = EmaneConfigDialog(self.app, self) + dialog = EmaneConfigDialog(self.app, self.core_node) dialog.show() def show_services(self) -> None: - dialog = NodeServiceDialog(self.app, self) + dialog = NodeServiceDialog(self.app, self.core_node) dialog.show() def show_config_services(self) -> None: - dialog = NodeConfigServiceDialog(self.app, self) + dialog = NodeConfigServiceDialog(self.app, self.core_node) dialog.show() def has_emane_link(self, iface_id: int) -> Node: diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py index c60350f9..f56fd54b 100644 --- a/daemon/core/gui/task.py +++ b/daemon/core/gui/task.py @@ -33,7 +33,6 @@ class ProgressTask: thread.start() def run(self) -> None: - logging.info("running task") try: values = self.task(*self.args) if values is None: @@ -41,7 +40,6 @@ class ProgressTask: elif values and not isinstance(values, tuple): values = (values,) if self.callback: - logging.info("calling callback") self.app.after(0, self.callback, *values) except Exception as e: logging.exception("progress task exception") diff --git a/daemon/core/gui/wrappers.py b/daemon/core/gui/wrappers.py index 835a9d17..d86e20dd 100644 --- a/daemon/core/gui/wrappers.py +++ b/daemon/core/gui/wrappers.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from enum import Enum -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Set, Tuple from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2 @@ -121,8 +121,8 @@ class ConfigService: @dataclass class ConfigServiceData: - templates: Dict[str, str] - config: Dict[str, str] + templates: Dict[str, str] = field(default_factory=dict) + config: Dict[str, str] = field(default_factory=dict) @dataclass @@ -504,8 +504,8 @@ class Node: type: NodeType model: str = None position: Position = None - services: List[str] = field(default_factory=list) - config_services: List[str] = field(default_factory=list) + services: Set[str] = field(default_factory=set) + config_services: Set[str] = field(default_factory=set) emane: str = None icon: str = None image: str = None @@ -538,8 +538,8 @@ class Node: type=NodeType(proto.type), model=proto.model, position=Position.from_proto(proto.position), - services=list(proto.services), - config_services=list(proto.config_services), + services=set(proto.services), + config_services=set(proto.config_services), emane=proto.emane, icon=proto.icon, image=proto.image, @@ -575,9 +575,9 @@ class Session: links: List[Link] dir: str user: str - default_services: Dict[str, List[str]] + default_services: Dict[str, Set[str]] location: SessionLocation - hooks: List[Hook] + hooks: Dict[str, Hook] emane_models: List[str] emane_config: Dict[str, ConfigOption] metadata: Dict[str, str] @@ -586,8 +586,10 @@ class Session: def from_proto(cls, proto: core_pb2.Session) -> "Session": nodes: Dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes} links = [Link.from_proto(x) for x in proto.links] - default_services = {x.node_type: x.services for x in proto.default_services} - hooks = [Hook.from_proto(x) for x in proto.hooks] + default_services = { + 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 From 27495cbda10341fe0a802742cb0aed28d1524316 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 10:24:01 -0700 Subject: [PATCH 3/5] pygui: changes around using session.nodes instead of canvas_nodes when possible --- daemon/core/gui/coreclient.py | 68 +++++++++++++++--------------- daemon/core/gui/dialogs/find.py | 17 +++----- daemon/core/gui/dialogs/runtool.py | 6 +-- daemon/core/gui/frames/link.py | 10 ++--- daemon/core/gui/graph/graph.py | 24 +++++------ 5 files changed, 58 insertions(+), 67 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 36adf189..6129031a 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -118,6 +118,12 @@ class CoreClient: self.setup_cpu_usage() return self._client + def set_canvas_node(self, node: Node, canvas_node: CanvasNode) -> None: + self.canvas_nodes[node.id] = canvas_node + + def get_canvas_node(self, node_id: int) -> CanvasNode: + return self.canvas_nodes[node_id] + def reset(self) -> None: # helpers self.ifaces_manager.reset() @@ -231,18 +237,21 @@ class CoreClient: def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) + node = event.node if event.message_type == MessageType.NONE: - canvas_node = self.canvas_nodes[event.node.id] - x = event.node.position.x - y = event.node.position.y + canvas_node = self.canvas_nodes[node.id] + x = node.position.x + y = node.position.y canvas_node.move(x, y) elif event.message_type == MessageType.DELETE: - canvas_node = self.canvas_nodes[event.node.id] + canvas_node = self.canvas_nodes[node.id] self.app.canvas.clear_selection() self.app.canvas.select_object(canvas_node.id) self.app.canvas.delete_selected_objects() elif event.message_type == MessageType.ADD: - self.app.canvas.add_core_node(event.node) + if node.id in self.session.nodes: + logging.error("core node already exists: %s", node) + self.app.canvas.add_core_node(node) else: logging.warning("unknown node event: %s", event) @@ -463,10 +472,9 @@ class CoreClient: def start_session(self) -> Tuple[bool, List[str]]: self.ifaces_manager.reset_mac() - nodes = [x.core_node.to_proto() for x in self.canvas_nodes.values()] + nodes = [x.to_proto() for x in self.session.nodes.values()] links = [] - for edge in self.links.values(): - link = edge.link + for link in self.session.links: if link.iface1 and not link.iface1.mac: link.iface1.mac = self.ifaces_manager.next_mac() if link.iface2 and not link.iface2.mac: @@ -674,13 +682,12 @@ class CoreClient: """ create nodes and links that have not been created yet """ - node_protos = [x.core_node.to_proto() for x in self.canvas_nodes.values()] - link_protos = [x.link.to_proto() for x in self.links.values()] self.client.set_session_state(self.session.id, SessionState.DEFINITION.value) - for node_proto in node_protos: - response = self.client.add_node(self.session.id, node_proto) - logging.debug("create node: %s", response) - for link_proto in link_protos: + for node in self.session.nodes.values(): + response = self.client.add_node(self.session.id, node.to_proto()) + logging.debug("created node: %s", response) + for link in self.session.links: + link_proto = link.to_proto() response = self.client.add_link( self.session.id, link_proto.node1_id, @@ -689,7 +696,7 @@ class CoreClient: link_proto.iface2, link_proto.options, ) - logging.debug("create link: %s", response) + logging.debug("created link: %s", response) def send_data(self) -> None: """ @@ -762,7 +769,7 @@ class CoreClient: """ i = 1 while True: - if i not in self.canvas_nodes: + if i not in self.session.nodes: break i += 1 return i @@ -816,18 +823,20 @@ class CoreClient: x, y, ) + self.session.nodes[node.id] = node return node - def deleted_graph_nodes(self, canvas_nodes: List[CanvasNode]) -> None: + def deleted_canvas_nodes(self, canvas_nodes: List[CanvasNode]) -> None: """ remove the nodes selected by the user and anything related to that node such as link, configurations, interfaces """ for canvas_node in canvas_nodes: - node_id = canvas_node.core_node.id - del self.canvas_nodes[node_id] + node = canvas_node.core_node + del self.canvas_nodes[node.id] + del self.session.nodes[node.id] - def deleted_graph_edges(self, edges: Iterable[CanvasEdge]) -> None: + def deleted_canvas_edges(self, edges: Iterable[CanvasEdge]) -> None: links = [] for edge in edges: del self.links[edge.token] @@ -861,20 +870,19 @@ class CoreClient: """ src_node = canvas_src_node.core_node dst_node = canvas_dst_node.core_node - - # determine subnet self.ifaces_manager.determine_subnets(canvas_src_node, canvas_dst_node) - src_iface = None if NodeUtils.is_container_node(src_node.type): src_iface = self.create_iface(canvas_src_node) self.iface_to_edge[(src_node.id, src_iface.id)] = edge.token - + edge.src_iface = src_iface + canvas_src_node.ifaces[src_iface.id] = src_iface dst_iface = None if NodeUtils.is_container_node(dst_node.type): dst_iface = self.create_iface(canvas_dst_node) self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token - + edge.dst_iface = dst_iface + canvas_dst_node.ifaces[dst_iface.id] = dst_iface link = Link( type=LinkType.WIRED, node1_id=src_node.id, @@ -882,17 +890,9 @@ class CoreClient: iface1=src_iface, iface2=dst_iface, ) - # assign after creating link proto, since interfaces are copied - if src_iface: - iface1 = link.iface1 - edge.src_iface = iface1 - canvas_src_node.ifaces[iface1.id] = iface1 - if dst_iface: - iface2 = link.iface2 - edge.dst_iface = iface2 - canvas_dst_node.ifaces[iface2.id] = iface2 edge.set_link(link) self.links[edge.token] = edge + self.session.links.append(link) logging.info("Add link between %s and %s", src_node.name, dst_node.name) def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]: diff --git a/daemon/core/gui/dialogs/find.py b/daemon/core/gui/dialogs/find.py index 328f673e..a4600847 100644 --- a/daemon/core/gui/dialogs/find.py +++ b/daemon/core/gui/dialogs/find.py @@ -87,22 +87,19 @@ class FindDialog(Dialog): """ node_name = self.find_text.get().strip() self.clear_treeview_items() - for node_id, node in sorted( - self.app.core.canvas_nodes.items(), key=lambda x: x[0] - ): - name = node.core_node.name + for node in self.app.core.session.nodes.values(): + name = node.name if not node_name or node_name == name: - pos_x = round(node.core_node.position.x, 1) - pos_y = round(node.core_node.position.y, 1) + pos_x = round(node.position.x, 1) + pos_y = round(node.position.y, 1) # TODO: I am not sure what to insert for Detail column # leaving it blank for now self.tree.insert( "", tk.END, - text=str(node_id), - values=(node_id, name, f"<{pos_x}, {pos_y}>", ""), + text=str(node.id), + values=(node.id, name, f"<{pos_x}, {pos_y}>", ""), ) - results = self.tree.get_children("") if results: self.tree.selection_set(results[0]) @@ -121,7 +118,7 @@ class FindDialog(Dialog): if item: self.app.canvas.delete("find") node_id = int(self.tree.item(item, "text")) - canvas_node = self.app.core.canvas_nodes[node_id] + canvas_node = self.app.core.get_canvas_node(node_id) x0, y0, x1, y1 = self.app.canvas.bbox(canvas_node.id) dist = 5 * self.app.guiconfig.scale diff --git a/daemon/core/gui/dialogs/runtool.py b/daemon/core/gui/dialogs/runtool.py index a1517593..e36c4c9a 100644 --- a/daemon/core/gui/dialogs/runtool.py +++ b/daemon/core/gui/dialogs/runtool.py @@ -25,9 +25,9 @@ class RunToolDialog(Dialog): """ store all CORE nodes (nodes that execute commands) from all existing nodes """ - for nid, node in self.app.core.canvas_nodes.items(): - if NodeUtils.is_container_node(node.core_node.type): - self.executable_nodes[node.core_node.name] = nid + for node in self.app.core.session.nodes.values(): + if NodeUtils.is_container_node(node.type): + self.executable_nodes[node.name] = node.id def draw(self) -> None: self.top.rowconfigure(0, weight=1) diff --git a/daemon/core/gui/frames/link.py b/daemon/core/gui/frames/link.py index cbea9982..093f39eb 100644 --- a/daemon/core/gui/frames/link.py +++ b/daemon/core/gui/frames/link.py @@ -34,10 +34,8 @@ class EdgeInfoFrame(InfoFrameBase): self.columnconfigure(0, weight=1) link = self.edge.link options = link.options - src_canvas_node = self.app.core.canvas_nodes[link.node1_id] - src_node = src_canvas_node.core_node - dst_canvas_node = self.app.core.canvas_nodes[link.node2_id] - dst_node = dst_canvas_node.core_node + src_node = self.app.core.session.nodes[link.node1_id] + dst_node = self.app.core.session.nodes[link.node2_id] frame = DetailsFrame(self) frame.grid(sticky="ew") @@ -81,9 +79,9 @@ class WirelessEdgeInfoFrame(InfoFrameBase): def draw(self) -> None: link = self.edge.link - src_canvas_node = self.app.core.canvas_nodes[link.node1_id] + src_canvas_node = self.app.canvas.nodes[self.edge.src] src_node = src_canvas_node.core_node - dst_canvas_node = self.app.core.canvas_nodes[link.node2_id] + dst_canvas_node = self.app.canvas.nodes[self.edge.dst] dst_node = dst_canvas_node.core_node # find interface for each node connected to network diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 54d2cae1..69ae87cc 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -311,10 +311,7 @@ class CanvasGraph(tk.Canvas): edge.middle_label_text(link.label) def add_core_node(self, core_node: Node) -> None: - if core_node.id in self.core.canvas_nodes: - logging.error("core node already exists: %s", core_node) - return - logging.debug("adding node %s", core_node) + logging.debug("adding node: %s", core_node) # if the gui can't find node's image, default to the "edit-node" image image = NodeUtils.node_image(core_node, self.app.guiconfig, self.app.app_scale) if not image: @@ -323,7 +320,7 @@ class CanvasGraph(tk.Canvas): y = core_node.position.y node = CanvasNode(self.app, x, y, core_node, image) self.nodes[node.id] = node - self.core.canvas_nodes[core_node.id] = node + self.core.set_canvas_node(core_node, node) def draw_session(self, session: Session) -> None: """ @@ -336,12 +333,11 @@ class CanvasGraph(tk.Canvas): if NodeUtils.is_ignore_node(core_node.type): continue self.add_core_node(core_node) - - # draw existing links + # draw existing links for link in session.links: logging.debug("drawing link: %s", link) - canvas_node1 = self.core.canvas_nodes[link.node1_id] - canvas_node2 = self.core.canvas_nodes[link.node2_id] + canvas_node1 = self.core.get_canvas_node(link.node1_id) + canvas_node2 = self.core.get_canvas_node(link.node2_id) if link.type == LinkType.WIRELESS: self.add_wireless_edge(canvas_node1, canvas_node2, link) else: @@ -544,8 +540,8 @@ class CanvasGraph(tk.Canvas): shape.delete() self.selection.clear() - self.core.deleted_graph_nodes(nodes) - self.core.deleted_graph_edges(edges) + self.core.deleted_canvas_nodes(nodes) + self.core.deleted_canvas_edges(edges) def delete_edge(self, edge: CanvasEdge) -> None: edge.delete() @@ -564,7 +560,7 @@ class CanvasGraph(tk.Canvas): dst_wireless = NodeUtils.is_wireless_node(dst_node.core_node.type) if dst_wireless: src_node.delete_antenna() - self.core.deleted_graph_edges([edge]) + self.core.deleted_canvas_edges([edge]) def zoom(self, event: tk.Event, factor: float = None) -> None: if not factor: @@ -750,8 +746,8 @@ class CanvasGraph(tk.Canvas): image_file = self.node_draw.image_file self.node_draw.image = self.app.get_custom_icon(image_file, ICON_SIZE) node = CanvasNode(self.app, x, y, core_node, self.node_draw.image) - self.core.canvas_nodes[core_node.id] = node self.nodes[node.id] = node + self.core.set_canvas_node(core_node, node) def width_and_height(self) -> Tuple[int, int]: """ @@ -955,8 +951,8 @@ class CanvasGraph(tk.Canvas): ) copy_map[canvas_node.id] = node.id - self.core.canvas_nodes[copy.id] = node self.nodes[node.id] = node + self.core.set_canvas_node(copy, node) for edge in canvas_node.edges: if edge.src not in self.to_copy or edge.dst not in self.to_copy: if canvas_node.id == edge.src: From 858e771efd8f040a18c7e222e4dd971536afe145 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 21:49:34 -0700 Subject: [PATCH 4/5] pygui: fixes for copying links/asymmetric links, fixes for configuring asymmetric links, fixed issues adding nodes/links and editing links from gui due to not being able to identify same source changes --- daemon/core/gui/coreclient.py | 56 +++++++++++++++++++------ daemon/core/gui/dialogs/linkconfig.py | 37 ++++------------- daemon/core/gui/graph/graph.py | 59 ++++++++++++++++++--------- 3 files changed, 91 insertions(+), 61 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 6129031a..9fe8130a 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -474,21 +474,22 @@ class CoreClient: self.ifaces_manager.reset_mac() nodes = [x.to_proto() for x in self.session.nodes.values()] links = [] - for link in self.session.links: + asymmetric_links = [] + for edge in self.links.values(): + link = edge.link if link.iface1 and not link.iface1.mac: link.iface1.mac = self.ifaces_manager.next_mac() if link.iface2 and not link.iface2.mac: link.iface2.mac = self.ifaces_manager.next_mac() links.append(link.to_proto()) + if edge.asymmetric_link: + asymmetric_links.append(edge.asymmetric_link.to_proto()) wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() emane_model_configs = self.get_emane_model_configs_proto() hooks = [x.to_proto() for x in self.session.hooks.values()] service_configs = self.get_service_configs_proto() file_configs = self.get_service_file_configs_proto() - asymmetric_links = [ - x.asymmetric_link for x in self.links.values() if x.asymmetric_link - ] config_service_configs = self.get_config_service_configs_proto() emane_config = to_dict(self.session.emane_config) result = False @@ -686,17 +687,32 @@ class CoreClient: for node in self.session.nodes.values(): response = self.client.add_node(self.session.id, node.to_proto()) logging.debug("created node: %s", response) - for link in self.session.links: - link_proto = link.to_proto() + asymmetric_links = [] + for edge in self.links.values(): + link = edge.link response = self.client.add_link( self.session.id, - link_proto.node1_id, - link_proto.node2_id, - link_proto.iface1, - link_proto.iface2, - link_proto.options, + link.node1_id, + link.node2_id, + link.iface1, + link.iface2, + link.options, + source=GUI_SOURCE, ) logging.debug("created link: %s", response) + if edge.asymmetric_link: + asymmetric_links.append(edge.asymmetric_link) + for link in asymmetric_links: + response = self.client.add_link( + self.session.id, + link.node1_id, + link.node2_id, + link.iface1, + link.iface2, + link.options, + source=GUI_SOURCE, + ) + logging.debug("created asymmetric link: %s", response) def send_data(self) -> None: """ @@ -892,8 +908,7 @@ class CoreClient: ) edge.set_link(link) self.links[edge.token] = edge - self.session.links.append(link) - logging.info("Add link between %s and %s", src_node.name, dst_node.name) + logging.info("added link between %s and %s", src_node.name, dst_node.name) def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]: configs = [] @@ -1039,3 +1054,18 @@ class CoreClient: logging.info("execute python script %s", response) if response.session_id != -1: self.join_session(response.session_id) + + def edit_link(self, link: Link) -> None: + iface1_id = link.iface1.id if link.iface1 else None + iface2_id = link.iface2.id if link.iface2 else None + response = self.client.edit_link( + self.session.id, + link.node1_id, + link.node2_id, + link.options.to_proto(), + iface1_id, + iface2_id, + source=GUI_SOURCE, + ) + if not response.result: + logging.error("error editing link: %s", link) diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 2a91da30..914bad1e 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -228,21 +228,15 @@ class LinkConfigurationDialog(Dialog): bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, loss=loss ) link.options = options - - iface1_id = None - if link.iface1: - iface1_id = link.iface1.id - iface2_id = None - if link.iface2: - iface2_id = link.iface2.id - + iface1_id = link.iface1.id if link.iface1 else None + iface2_id = link.iface2.id if link.iface2 else None if not self.is_symmetric: link.options.unidirectional = True asym_iface1 = None - if iface1_id: + if iface1_id is not None: asym_iface1 = Interface(id=iface1_id) asym_iface2 = None - if iface2_id: + if iface2_id is not None: asym_iface2 = Interface(id=iface2_id) down_bandwidth = get_int(self.down_bandwidth) down_jitter = get_int(self.down_jitter) @@ -260,8 +254,8 @@ class LinkConfigurationDialog(Dialog): self.edge.asymmetric_link = Link( node1_id=link.node2_id, node2_id=link.node1_id, - iface1=asym_iface1, - iface2=asym_iface2, + iface1=asym_iface2, + iface2=asym_iface1, options=options, ) else: @@ -269,24 +263,9 @@ class LinkConfigurationDialog(Dialog): self.edge.asymmetric_link = None if self.app.core.is_runtime() and link.options: - session_id = self.app.core.session.id - self.app.core.client.edit_link( - session_id, - link.node1_id, - link.node2_id, - link.options, - iface1_id, - iface2_id, - ) + self.app.core.edit_link(link) if self.edge.asymmetric_link: - self.app.core.client.edit_link( - session_id, - link.node2_id, - link.node1_id, - self.edge.asymmetric_link.options, - iface1_id, - iface2_id, - ) + self.app.core.edit_link(self.edge.asymmetric_link) # update edge label self.edge.draw_link_options() diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 69ae87cc..bb762bb8 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -262,7 +262,7 @@ class CanvasGraph(tk.Canvas): edge = self.edges.get(token) if not edge: return - edge.link.options.CopyFrom(link.options) + edge.link.options = deepcopy(link.options) def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: network_id = link.network_id if link.network_id else None @@ -924,7 +924,8 @@ class CanvasGraph(tk.Canvas): # maps original node canvas id to copy node canvas id copy_map = {} # the edges that will be copy over - to_copy_edges = [] + to_copy_edges = set() + to_copy_ids = {x.id for x in self.to_copy} for canvas_node in self.to_copy: core_node = canvas_node.core_node actual_x = core_node.position.x + 50 @@ -954,15 +955,39 @@ class CanvasGraph(tk.Canvas): self.nodes[node.id] = node self.core.set_canvas_node(copy, node) for edge in canvas_node.edges: - if edge.src not in self.to_copy or edge.dst not in self.to_copy: + if edge.src not in to_copy_ids or edge.dst not in to_copy_ids: if canvas_node.id == edge.src: dst_node = self.nodes[edge.dst] self.create_edge(node, dst_node) + token = create_edge_token(node.id, dst_node.id) elif canvas_node.id == edge.dst: src_node = self.nodes[edge.src] self.create_edge(src_node, node) + token = create_edge_token(src_node.id, node.id) + copy_edge = self.edges[token] + copy_link = copy_edge.link + iface1_id = copy_link.iface1.id if copy_link.iface1 else None + iface2_id = copy_link.iface2.id if copy_link.iface2 else None + options = edge.link.options + if options: + copy_edge.link.options = deepcopy(options) + if options and options.unidirectional: + asym_iface1 = None + if iface1_id is not None: + asym_iface1 = Interface(id=iface1_id) + asym_iface2 = None + if iface2_id is not None: + asym_iface2 = Interface(id=iface2_id) + copy_edge.asymmetric_link = Link( + node1_id=copy_link.node2_id, + node2_id=copy_link.node1_id, + iface1=asym_iface2, + iface2=asym_iface1, + options=deepcopy(edge.asymmetric_link.options), + ) + copy_edge.redraw() else: - to_copy_edges.append(edge) + to_copy_edges.add(edge) # copy link and link config for edge in to_copy_edges: @@ -974,30 +999,26 @@ class CanvasGraph(tk.Canvas): token = create_edge_token(src_node_copy.id, dst_node_copy.id) copy_edge = self.edges[token] copy_link = copy_edge.link + iface1_id = copy_link.iface1.id if copy_link.iface1 else None + iface2_id = copy_link.iface2.id if copy_link.iface2 else None options = edge.link.options - copy_link.options = deepcopy(options) - iface1_id = None - if copy_link.iface1: - iface1_id = copy_link.iface1.id - iface2_id = None - if copy_link.iface2: - iface2_id = copy_link.iface2.id - if not options.unidirectional: - copy_edge.asymmetric_link = None - else: + if options: + copy_link.options = deepcopy(options) + if options and options.unidirectional: asym_iface1 = None - if iface1_id: + if iface1_id is not None: asym_iface1 = Interface(id=iface1_id) asym_iface2 = None - if iface2_id: + if iface2_id is not None: asym_iface2 = Interface(id=iface2_id) copy_edge.asymmetric_link = Link( node1_id=copy_link.node2_id, node2_id=copy_link.node1_id, - iface1=asym_iface1, - iface2=asym_iface2, - options=edge.asymmetric_link.options, + iface1=asym_iface2, + iface2=asym_iface1, + options=deepcopy(edge.asymmetric_link.options), ) + copy_edge.redraw() self.itemconfig( copy_edge.id, width=self.itemcget(edge.id, "width"), From fe36d28522f31559aa27f913db4d4bb23d867fc6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 28 Jul 2020 22:45:42 -0700 Subject: [PATCH 5/5] pygui: fixed issue with changes to protobuf files for getting emane model configs on a session --- daemon/core/api/grpc/grpcutils.py | 6 +++--- daemon/core/gui/coreclient.py | 2 ++ daemon/proto/core/api/grpc/core.proto | 2 +- daemon/proto/core/api/grpc/emane.proto | 9 ++++++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index a024c064..51be85fe 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -10,7 +10,7 @@ from core import utils from core.api.grpc import common_pb2, core_pb2 from core.api.grpc.common_pb2 import MappedConfig from core.api.grpc.configservices_pb2 import ConfigServiceConfig -from core.api.grpc.emane_pb2 import EmaneModelConfig +from core.api.grpc.emane_pb2 import GetEmaneModelConfig from core.api.grpc.services_pb2 import ( NodeServiceConfig, NodeServiceData, @@ -547,7 +547,7 @@ def get_nem_id( return nem_id -def get_emane_model_configs(session: Session) -> List[EmaneModelConfig]: +def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]: configs = [] for _id in session.emane.node_configurations: if _id == -1: @@ -558,7 +558,7 @@ def get_emane_model_configs(session: Session) -> List[EmaneModelConfig]: current_config = session.emane.get_model_config(_id, model_name) config = get_config_options(current_config, model) node_id, iface_id = parse_emane_model_id(_id) - model_config = EmaneModelConfig( + model_config = GetEmaneModelConfig( node_id=node_id, model=model_name, iface_id=iface_id, config=config ) configs.append(model_config) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 9fe8130a..b30a265b 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -404,6 +404,8 @@ class CoreClient: self.app.show_grpc_exception("New Session Error", e) def delete_session(self, session_id: int = None) -> None: + if session_id is None and not self.session: + return if session_id is None: session_id = self.session.id try: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 1b20257c..d5ffda59 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -720,7 +720,7 @@ message Session { repeated Hook hooks = 9; repeated string emane_models = 10; map emane_config = 11; - repeated emane.EmaneModelConfig emane_model_configs = 12; + repeated emane.GetEmaneModelConfig emane_model_configs = 12; map wlan_configs = 13; repeated services.NodeServiceConfig service_configs = 14; repeated configservices.ConfigServiceConfig config_service_configs = 15; diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index ce9a4297..ad6a22ca 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -53,8 +53,15 @@ message GetEmaneModelConfigsRequest { int32 session_id = 1; } +message GetEmaneModelConfig { + int32 node_id = 1; + string model = 2; + int32 iface_id = 3; + map config = 4; +} + message GetEmaneModelConfigsResponse { - repeated EmaneModelConfig configs = 1; + repeated GetEmaneModelConfig configs = 1; } message GetEmaneEventChannelRequest {