From b983a09ae7f49fbf82e5135ad5ba479c8277c9c0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 21 Nov 2019 14:41:05 -0800 Subject: [PATCH] small cleanup to emane config dialog, fixed default service storage to just use names --- coretk/coretk/coreclient.py | 33 +- coretk/coretk/dialogs/emaneconfig.py | 478 +++++++++++---------------- coretk/coretk/emaneodelnodeconfig.py | 2 +- coretk/coretk/graph.py | 10 + 4 files changed, 209 insertions(+), 314 deletions(-) diff --git a/coretk/coretk/coreclient.py b/coretk/coretk/coreclient.py index beeb1c0a..dbcbcf18 100644 --- a/coretk/coretk/coreclient.py +++ b/coretk/coretk/coreclient.py @@ -50,6 +50,7 @@ class CoreClient: self.master = app.master self.interface_helper = None self.services = {} + self.emane_models = [] self.observer = None # loaded configuration data @@ -70,6 +71,7 @@ class CoreClient: self.interfaces_manager = InterfaceManager() self.wlan_configs = {} self.mobility_configs = {} + self.emane_model_configs = {} self.emaneconfig_management = EmaneModelNodeConfig(app) self.emane_config = None self.serviceconfig_manager = ServiceNodeConfig(app) @@ -145,6 +147,10 @@ class CoreClient: self.state = session.state self.client.events(self.session_id, self.handle_events) + # 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) logging.info("joined session hooks: %s", response) @@ -240,8 +246,8 @@ class CoreClient: # get service information response = self.client.get_services() for service in response.services: - group_services = self.services.setdefault(service.group, []) - group_services.append(service) + group_services = self.services.setdefault(service.group, set()) + group_services.add(service.name) # if there are no sessions, create a new session, else join a session response = self.client.get_sessions() @@ -443,6 +449,9 @@ class CoreClient: image = None if NodeUtils.is_image_node(node_type): image = "ubuntu:latest" + emane = None + if node_type == core_pb2.NodeType.EMANE: + emane = self.emane_models[0] node = core_pb2.Node( id=node_id, type=node_type, @@ -450,6 +459,7 @@ class CoreClient: model=model, position=position, image=image, + emane=emane, ) # set default emane configuration for emane node @@ -571,25 +581,6 @@ class CoreClient: if interface_two is not None: self.interface_to_edge[(node_two.id, interface_two.id)] = token - # emane setup - # TODO: determine if this is needed - if ( - node_one.type == core_pb2.NodeType.EMANE - and node_two.type == core_pb2.NodeType.DEFAULT - ): - if node_two.model == "mdr": - self.emaneconfig_management.set_default_for_mdr( - node_one.node_id, node_two.node_id, interface_two.id - ) - elif ( - node_two.type == core_pb2.NodeType.EMANE - and node_one.type == core_pb2.NodeType.DEFAULT - ): - if node_one.model == "mdr": - self.emaneconfig_management.set_default_for_mdr( - node_two.node_id, node_one.node_id, interface_one.id - ) - link = core_pb2.Link( type=core_pb2.LinkType.WIRED, node_one_id=node_one.id, diff --git a/coretk/coretk/dialogs/emaneconfig.py b/coretk/coretk/dialogs/emaneconfig.py index 2c885511..046b0d62 100644 --- a/coretk/coretk/dialogs/emaneconfig.py +++ b/coretk/coretk/dialogs/emaneconfig.py @@ -1,7 +1,6 @@ """ emane configuration """ - import logging import tkinter as tk import webbrowser @@ -11,271 +10,122 @@ from coretk.dialogs.dialog import Dialog from coretk.images import ImageEnum, Images from coretk.widgets import ConfigFrame -PAD_X = 2 -PAD_Y = 2 +PAD = 5 -class EmaneConfiguration(Dialog): +class GlobalEmaneDialog(Dialog): + def __init__(self, master, app): + super().__init__(master, app, "EMANE Configuration", modal=True) + self.config_frame = None + self.draw() + + def draw(self): + self.top.columnconfigure(0, weight=1) + self.top.rowconfigure(0, weight=1) + self.config_frame = ConfigFrame( + self.top, self.app, self.app.core.emane_config, borderwidth=0 + ) + self.config_frame.draw_config() + self.config_frame.grid(sticky="nsew", pady=PAD) + self.draw_buttons() + + def draw_buttons(self): + frame = ttk.Frame(self.top) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + button = ttk.Button(frame, text="Apply", command=self.click_apply) + button.grid(row=0, column=0, sticky="ew", padx=PAD) + + button = ttk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, sticky="ew") + + def click_apply(self): + self.config_frame.parse_config() + self.destroy() + + +class EmaneModelDialog(Dialog): + def __init__(self, master, app, node, model): + super().__init__(master, app, f"{node.name} {model} Configuration", modal=True) + self.node = node + self.model = f"emane_{model}" + self.config_frame = None + session_id = self.app.core.session_id + response = self.app.core.client.get_emane_model_config( + session_id, self.node.id, self.model + ) + self.config = response.config + self.draw() + + def draw(self): + self.top.columnconfigure(0, weight=1) + self.top.rowconfigure(0, weight=1) + self.config_frame = ConfigFrame(self.top, self.app, self.config, borderwidth=0) + self.config_frame.draw_config() + self.config_frame.grid(sticky="nsew", pady=PAD) + self.draw_buttons() + + def draw_buttons(self): + frame = ttk.Frame(self.top) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + button = ttk.Button(frame, text="Apply", command=self.click_apply) + button.grid(row=0, column=0, sticky="ew", padx=PAD) + + button = ttk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, sticky="ew") + + def click_apply(self): + self.config_frame.parse_config() + self.app.core.emaneconfig_management.set_custom_emane_cloud_config( + self.node.id, self.model + ) + self.destroy() + + +class EmaneConfigDialog(Dialog): def __init__(self, master, app, canvas_node): - super().__init__(master, app, "emane configuration", modal=False) + super().__init__( + master, app, f"{canvas_node.core_node.name} EMANE Configuration", modal=True + ) self.app = app self.canvas_node = canvas_node self.node = canvas_node.core_node self.radiovar = tk.IntVar() self.radiovar.set(1) - self.columnconfigure(0, weight=1) + self.emane_models = [x.split("_")[1] for x in self.app.core.emane_models] + emane_model = None + if self.emane_models: + emane_model = self.emane_models[0] + self.emane_model = tk.StringVar(value=emane_model) + self.emane_model_button = None + self.draw() - # list(string) of emane models - self.emane_models = None - - self.emane_dialog = Dialog(self, app, "emane configuration", modal=False) - self.emane_model_dialog = None - self.emane_model_combobox = None - - # draw - self.node_name_and_image() - self.emane_configuration() - self.draw_ip_subnets() - self.emane_options() + def draw(self): + self.top.columnconfigure(0, weight=1) + self.draw_emane_configuration() + self.draw_emane_models() + self.draw_emane_buttons() self.draw_apply_and_cancel() - self.emane_config_frame = None - self.options = app.core.emane_config - self.model_options = None - self.model_config_frame = None - - def create_text_variable(self, val): - """ - create a string variable for convenience - - :param str val: entry text - :return: nothing - """ - var = tk.StringVar() - var.set(val) - return var - - def choose_core(self): - logging.info("not implemented") - - def node_name_and_image(self): - f = ttk.Frame(self.top) - - lbl = ttk.Label(f, text="Node name:") - lbl.grid(row=0, column=0, padx=2, pady=2) - e = ttk.Entry(f, textvariable=self.create_text_variable("")) - e.grid(row=0, column=1, padx=2, pady=2) - - cbb = ttk.Combobox(f, values=["(none)", "core1", "core2"], state="readonly") - cbb.current(0) - cbb.grid(row=0, column=2, padx=2, pady=2) - - b = ttk.Button(f, image=self.canvas_node.image) - b.grid(row=0, column=3, padx=2, pady=2) - - f.grid(row=0, column=0, sticky="nsew") - - def save_emane_option(self): - self.emane_config_frame.parse_config() - self.emane_dialog.destroy() - - def draw_emane_options(self): - if not self.emane_dialog.winfo_exists(): - self.emane_dialog = Dialog( - self, self.app, "emane configuration", modal=False - ) - - if self.options is None: - session_id = self.app.core.session_id - response = self.app.core.client.get_emane_config(session_id) - logging.info("emane config: %s", response) - self.options = response.config - - self.emane_dialog.top.columnconfigure(0, weight=1) - self.emane_dialog.top.rowconfigure(0, weight=1) - self.emane_config_frame = ConfigFrame( - self.emane_dialog.top, self.app, config=self.options - ) - self.emane_config_frame.draw_config() - self.emane_config_frame.grid(sticky="nsew") - - frame = ttk.Frame(self.emane_dialog.top) - frame.grid(sticky="ew") - for i in range(2): - frame.columnconfigure(i, weight=1) - b1 = ttk.Button(frame, text="Appy", command=self.save_emane_option) - b1.grid(row=0, column=0, sticky="ew") - b2 = ttk.Button(frame, text="Cancel", command=self.emane_dialog.destroy) - b2.grid(row=0, column=1, sticky="ew") - self.emane_dialog.show() - - def save_emane_model_options(self): - """ - configure the node's emane model on the fly - - :return: nothing - """ - # get model name - model_name = self.emane_models[self.emane_model_combobox.current()] - - # parse configuration - config = self.model_config_frame.parse_config() - - # add string emane_ infront for grpc call - response = self.app.core.client.set_emane_model_config( - self.app.core.session_id, self.node.id, f"emane_{model_name}", config - ) - logging.info( - "emaneconfig.py config emane model (%s), result: %s", self.node.id, response - ) - - # store the change locally - self.app.core.emaneconfig_management.set_custom_emane_cloud_config( - self.node.id, f"emane_{model_name}" - ) - - self.emane_model_dialog.destroy() - - def draw_model_options(self): - """ - draw emane model configuration - - :return: nothing - """ - # get model name - model_name = self.emane_models[self.emane_model_combobox.current()] - - # create the dialog and the necessry widget - if not self.emane_model_dialog or not self.emane_model_dialog.winfo_exists(): - self.emane_model_dialog = Dialog( - self, self.app, f"{model_name} configuration", modal=False - ) - self.emane_model_dialog.top.columnconfigure(0, weight=1) - self.emane_model_dialog.top.rowconfigure(0, weight=1) - - # query for configurations - session_id = self.app.core.session_id - # add string emane_ before model name for grpc call - response = self.app.core.client.get_emane_model_config( - session_id, self.node.id, f"emane_{model_name}" - ) - logging.info("emane model config %s", response) - - self.model_options = response.config - self.model_config_frame = ConfigFrame( - self.emane_model_dialog.top, self.app, config=self.model_options - ) - self.model_config_frame.grid(sticky="nsew") - self.model_config_frame.draw_config() - - frame = ttk.Frame(self.emane_model_dialog.top) - frame.grid(sticky="ew") - for i in range(2): - frame.columnconfigure(i, weight=1) - b1 = ttk.Button(frame, text="Apply", command=self.save_emane_model_options) - b1.grid(row=0, column=0, sticky="ew") - b2 = ttk.Button(frame, text="Cancel", command=self.emane_model_dialog.destroy) - b2.grid(row=0, column=1, sticky="ew") - self.emane_model_dialog.show() - - def draw_option_buttons(self, parent): - f = ttk.Frame(parent) - f.grid(row=4, column=0, sticky="nsew") - f.columnconfigure(0, weight=1) - f.columnconfigure(1, weight=1) - - image = Images.get(ImageEnum.EDITNODE, 16) - b = ttk.Button( - f, - text=self.emane_models[0] + " options", - image=image, - compound=tk.RIGHT, - command=self.draw_model_options, - ) - b.image = image - b.grid(row=0, column=0, padx=10, pady=2, sticky="nsew") - - image = Images.get(ImageEnum.EDITNODE, 16) - b = ttk.Button( - f, - text="EMANE options", - image=image, - compound=tk.RIGHT, - command=self.draw_emane_options, - ) - b.image = image - b.grid(row=0, column=1, padx=10, pady=2, sticky="nsew") - - def combobox_select(self, event): - """ - update emane model options button - - :param event: - :return: nothing - """ - # get model name - model_name = self.emane_models[self.emane_model_combobox.current()] - - # get the button and configure button text - config_frame = self.grid_slaves(row=2, column=0)[0] - option_button_frame = config_frame.grid_slaves(row=4, column=0)[0] - b = option_button_frame.grid_slaves(row=0, column=0)[0] - b.config(text=model_name + " options") - - def draw_emane_models(self, parent): - """ - create a combobox that has all the known emane models - - :param parent: parent - :return: nothing - """ - # query for all the known model names - session_id = self.app.core.session_id - response = self.app.core.client.get_emane_models(session_id) - self.emane_models = [x.split("_")[1] for x in response.models] - - # create combo box and its binding - f = ttk.Frame(parent) - self.emane_model_combobox = ttk.Combobox( - f, values=self.emane_models, state="readonly" - ) - self.emane_model_combobox.grid() - self.emane_model_combobox.current(0) - self.emane_model_combobox.bind("<>", self.combobox_select) - f.grid(row=3, column=0, sticky="ew") - - def draw_text_label_and_entry(self, parent, label_text, entry_text): - """ - draw a label and an entry on a single row - - :return: nothing - """ - var = tk.StringVar() - var.set(entry_text) - f = ttk.Frame(parent) - lbl = ttk.Label(f, text=label_text) - lbl.grid(row=0, column=0) - e = ttk.Entry(f, textvariable=var) - e.grid(row=0, column=1) - f.grid(stick=tk.W, padx=2, pady=2) - - def emane_configuration(self): + def draw_emane_configuration(self): """ draw the main frame for emane configuration :return: nothing """ - # draw label - lbl = ttk.Label(self.top, text="Emane") - lbl.grid(row=1, column=0) - - # main frame that has emane wiki, a short description, emane models and the configure buttons - f = ttk.Frame(self.top) - f.columnconfigure(0, weight=1) + label = ttk.Label( + self.top, + text="The EMANE emulation system provides more complex wireless radio emulation " + "\nusing pluggable MAC and PHY modules. Refer to the wiki for configuration option details", + ) + label.grid(sticky="ew", pady=PAD) image = Images.get(ImageEnum.EDITNODE, 16) - b = ttk.Button( - f, + button = ttk.Button( + self.top, image=image, text="EMANE Wiki", compound=tk.RIGHT, @@ -283,55 +133,99 @@ class EmaneConfiguration(Dialog): "https://github.com/adjacentlink/emane/wiki" ), ) - b.image = image - b.grid(row=0, column=0, sticky="w") + button.image = image + button.grid(sticky="ew", pady=PAD) - lbl = ttk.Label( - f, - text="The EMANE emulation system provides more complex wireless radio emulation " - "\nusing pluggable MAC and PHY modules. Refer to the wiki for configuration option details", + def draw_emane_models(self): + """ + create a combobox that has all the known emane models + + :return: nothing + """ + frame = ttk.Frame(self.top) + frame.grid(sticky="ew", pady=PAD) + frame.columnconfigure(1, weight=1) + + label = ttk.Label(frame, text="Model") + label.grid(row=0, column=0, sticky="w") + + # create combo box and its binding + combobox = ttk.Combobox( + frame, + textvariable=self.emane_model, + values=self.emane_models, + state="readonly", ) - lbl.grid(row=1, column=0, sticky="nsew") + combobox.grid(row=0, column=1, sticky="ew") + combobox.bind("<>", self.emane_model_change) - lbl = ttk.Label(f, text="EMANE Models") - lbl.grid(row=2, column=0, sticky="w") + def draw_emane_buttons(self): + frame = ttk.Frame(self.top) + frame.grid(sticky="ew", pady=PAD) + for i in range(2): + frame.columnconfigure(i, weight=1) - self.draw_emane_models(f) - self.draw_option_buttons(f) + image = Images.get(ImageEnum.EDITNODE, 16) + self.emane_model_button = ttk.Button( + frame, + text=f"{self.emane_model.get()} options", + image=image, + compound=tk.RIGHT, + command=self.click_model_config, + ) + self.emane_model_button.image = image + self.emane_model_button.grid(row=0, column=0, padx=PAD, sticky="ew") - f.grid(row=2, column=0, sticky="nsew") - - def draw_ip_subnets(self): - self.draw_text_label_and_entry(self.top, "IPv4 subnet", "") - self.draw_text_label_and_entry(self.top, "IPv6 subnet", "") - - def emane_options(self): - """ - create wireless node options - - :return: - """ - f = ttk.Frame(self.top) - f.columnconfigure(0, weight=1) - f.columnconfigure(1, weight=1) - b = ttk.Button(f, text="Link to all routers") - b.grid(row=0, column=0, padx=10, pady=2, sticky="nsew") - b = ttk.Button(f, text="Choose WLAN members") - b.grid(row=0, column=1, padx=10, pady=2, sticky="nsew") - f.grid(row=5, column=0, sticky="nsew") - - def apply(self): - # save emane configuration - self.app.core.emane_config = self.options - self.destroy() + image = Images.get(ImageEnum.EDITNODE, 16) + button = ttk.Button( + frame, + text="EMANE options", + image=image, + compound=tk.RIGHT, + command=self.click_emane_config, + ) + button.image = image + button.grid(row=0, column=1, sticky="ew") def draw_apply_and_cancel(self): - f = ttk.Frame(self.top) - f.columnconfigure(0, weight=1) - f.columnconfigure(1, weight=1) - b = ttk.Button(f, text="Apply", command=self.apply) - b.grid(row=0, column=0, padx=10, pady=2, sticky="nsew") - b = ttk.Button(f, text="Cancel", command=self.destroy) - b.grid(row=0, column=1, padx=10, pady=2, sticky="nsew") + frame = ttk.Frame(self.top) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) - f.grid(sticky="nsew") + button = ttk.Button(frame, text="Apply", command=self.click_apply) + button.grid(row=0, column=0, padx=PAD, sticky="ew") + + button = ttk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, sticky="ew") + + def click_emane_config(self): + dialog = GlobalEmaneDialog(self, self.app) + dialog.show() + + def click_model_config(self): + """ + draw emane model configuration + + :return: nothing + """ + model_name = self.emane_model.get() + logging.info("configuring emane model: %s", model_name) + dialog = EmaneModelDialog( + self, self.app, self.canvas_node.core_node, model_name + ) + dialog.show() + + def emane_model_change(self, event): + """ + update emane model options button + + :param event: + :return: nothing + """ + model_name = self.emane_model.get() + self.emane_model_button.config(text=f"{model_name} options") + + def click_apply(self): + self.node.emane = f"emane_{self.emane_model.get()}" + self.destroy() diff --git a/coretk/coretk/emaneodelnodeconfig.py b/coretk/coretk/emaneodelnodeconfig.py index e3a9cc33..e1aaadbb 100644 --- a/coretk/coretk/emaneodelnodeconfig.py +++ b/coretk/coretk/emaneodelnodeconfig.py @@ -28,7 +28,7 @@ class EmaneModelNodeConfig: """ session_id = self.app.core.session_id client = self.app.core.client - default_emane_model = client.get_emane_models(session_id).models[0] + default_emane_model = self.app.core.emane_models[0] response = client.get_emane_model_config( session_id, node_id, default_emane_model ) diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index 86ce617e..e2722c77 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -7,6 +7,7 @@ from PIL import ImageTk from core.api.grpc import core_pb2 from core.api.grpc.core_pb2 import NodeType from coretk.canvastooltip import CanvasTooltip +from coretk.dialogs.emaneconfig import EmaneConfigDialog from coretk.dialogs.mobilityconfig import MobilityConfigDialog from coretk.dialogs.nodeconfig import NodeConfigDialog from coretk.dialogs.wlanconfig import WlanConfigDialog @@ -77,6 +78,10 @@ class CanvasGraph(tk.Canvas): context.add_command( label="Mobility Config", command=canvas_node.show_mobility_config ) + if node.type == NodeType.EMANE: + context.add_command( + label="EMANE Config", command=canvas_node.show_emane_config + ) context.add_command(label="Select adjacent", state=tk.DISABLED) context.add_command(label="Create link to", state=tk.DISABLED) context.add_command(label="Assign to", state=tk.DISABLED) @@ -711,3 +716,8 @@ class CanvasNode: self.canvas.context = None dialog = MobilityConfigDialog(self.app, self.app, self) dialog.show() + + def show_emane_config(self): + self.canvas.context = None + dialog = EmaneConfigDialog(self.app, self.app, self) + dialog.show()