Merge pull request #502 from coreemu/cleanup/pygui-session-wrapper

Cleanup/pygui session wrapper
This commit is contained in:
bharnden 2020-07-29 12:19:17 -07:00 committed by GitHub
commit 787f02f024
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 687 additions and 683 deletions

View file

@ -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 GetEmaneModelConfig
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[GetEmaneModelConfig]:
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 = GetEmaneModelConfig(
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)

View file

@ -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,38 @@ 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
id=session.id,
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 +1038,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 +1071,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 +1167,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 +1202,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 +1332,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 +1385,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 +1416,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 +1470,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 +1677,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(

View file

@ -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

View file

@ -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,21 +103,27 @@ 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()
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()
@ -126,8 +131,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 +148,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 +159,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 +174,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)
@ -236,24 +237,27 @@ 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)
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 +287,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 +304,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 +358,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 +389,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 +400,14 @@ 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 and not self.session:
return
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 +420,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 +433,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,19 +463,20 @@ 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()
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 = []
asymmetric_links = []
for edge in self.links.values():
link = edge.link
if link.iface1 and not link.iface1.mac:
@ -573,29 +484,25 @@ class CoreClient:
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.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 +514,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 +526,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 +537,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 +566,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 +579,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 +591,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 +611,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 +628,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 +648,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 +670,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",
@ -781,22 +685,36 @@ 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:
self.client.set_session_state(self.session.id, SessionState.DEFINITION.value)
for node in self.session.nodes.values():
response = self.client.add_node(self.session.id, node.to_proto())
logging.debug("created node: %s", response)
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,
self.session.id,
link.node1_id,
link.node2_id,
link.iface1,
link.iface2,
link.options,
source=GUI_SOURCE,
)
logging.debug("create link: %s", response)
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:
"""
@ -806,15 +724,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 +741,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:
@ -870,7 +787,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
@ -888,16 +805,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,28 +831,30 @@ 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,
)
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]
@ -969,20 +888,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,
@ -990,75 +908,62 @@ 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
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 = []
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 +976,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 +993,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 +1023,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 +1038,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(
@ -1154,3 +1056,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)

View file

@ -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()

View file

@ -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()

View file

@ -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
)

View file

@ -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()

View file

@ -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

View file

@ -113,8 +113,9 @@ class HooksDialog(Dialog):
listbox_scroll.grid(sticky="nsew", pady=PADY)
self.listbox = listbox_scroll.listbox
self.listbox.bind("<<ListboxSelect>>", 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)

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)
@ -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"

View file

@ -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()

View file

@ -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:

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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
@ -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,25 +320,24 @@ 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:
"""
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):
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]:
"""
@ -928,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
@ -940,30 +937,57 @@ 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
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:
@ -975,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"),

View file

@ -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:

View file

@ -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")

View file

@ -1,6 +1,6 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List
from typing import Dict, List, Optional, Set, 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] = field(default_factory=dict)
config: Dict[str, str] = field(default_factory=dict)
@dataclass
class NodeServiceData:
executables: List[str]
@ -498,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
@ -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(
@ -516,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,
@ -549,20 +571,64 @@ 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, Set[str]]
location: SessionLocation
hooks: Dict[str, 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: set(x.services) for x in proto.default_services
}
hooks = {x.file: Hook.from_proto(x) for x in proto.hooks}
# update nodes with their current configurations
for model in proto.emane_model_configs:
iface_id = None
if model.iface_id != -1:
iface_id = model.iface_id
node = nodes[model.node_id]
key = (model.model, iface_id)
node.emane_model_configs[key] = ConfigOption.from_dict(model.config)
for node_id, mapped_config in proto.wlan_configs.items():
node = nodes[node_id]
node.wlan_config = ConfigOption.from_dict(mapped_config.config)
for config in proto.service_configs:
service = config.service
node = nodes[config.node_id]
node.service_configs[service] = NodeServiceData.from_proto(config.data)
for file, data in config.files.items():
files = node.service_file_configs.setdefault(service, {})
files[file] = data
for config in proto.config_service_configs:
node = nodes[config.node_id]
node.config_service_configs[config.name] = ConfigServiceData(
templates=dict(config.templates), config=dict(config.config)
)
for node_id, mapped_config in proto.mobility_configs.items():
node = nodes[node_id]
node.mobility_config = ConfigOption.from_dict(mapped_config.config)
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),
)

View file

@ -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<string, common.ConfigOption> emane_config = 11;
repeated emane.GetEmaneModelConfig emane_model_configs = 12;
map<int32, common.MappedConfig> wlan_configs = 13;
repeated services.NodeServiceConfig service_configs = 14;
repeated configservices.ConfigServiceConfig config_service_configs = 15;
map<int32, common.MappedConfig> mobility_configs = 16;
map<string, string> metadata = 17;
}
message SessionSummary {

View file

@ -53,14 +53,15 @@ message GetEmaneModelConfigsRequest {
int32 session_id = 1;
}
message GetEmaneModelConfig {
int32 node_id = 1;
string model = 2;
int32 iface_id = 3;
map<string, common.ConfigOption> config = 4;
}
message GetEmaneModelConfigsResponse {
message ModelConfig {
int32 node_id = 1;
string model = 2;
int32 iface_id = 3;
map<string, common.ConfigOption> config = 4;
}
repeated ModelConfig configs = 1;
repeated GetEmaneModelConfig configs = 1;
}
message GetEmaneEventChannelRequest {

View file

@ -59,6 +59,13 @@ message NodeServiceData {
string meta = 10;
}
message NodeServiceConfig {
int32 node_id = 1;
string service = 2;
NodeServiceData data = 3;
map<string, string> 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<string, string> files = 4;
}
repeated ServiceConfig configs = 1;
repeated NodeServiceConfig configs = 1;
}
message GetNodeServiceRequest {