Merge branch 'coretk' into coretk-painttool

This commit is contained in:
Huy Pham 2019-12-19 08:46:56 -08:00
commit 62afbbae56
12 changed files with 397 additions and 220 deletions

View file

@ -445,13 +445,16 @@ class CoreClient:
def start_session(self): def start_session(self):
nodes = [x.core_node for x in self.canvas_nodes.values()] nodes = [x.core_node for x in self.canvas_nodes.values()]
links = list(self.links.values()) links = [x.link for x in self.links.values()]
wlan_configs = self.get_wlan_configs_proto() wlan_configs = self.get_wlan_configs_proto()
mobility_configs = self.get_mobility_configs_proto() mobility_configs = self.get_mobility_configs_proto()
emane_model_configs = self.get_emane_model_configs_proto() emane_model_configs = self.get_emane_model_configs_proto()
hooks = list(self.hooks.values()) hooks = list(self.hooks.values())
service_configs = self.get_service_configs_proto() service_configs = self.get_service_configs_proto()
file_configs = self.get_service_file_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
]
if self.emane_config: if self.emane_config:
emane_config = {x: self.emane_config[x].value for x in self.emane_config} emane_config = {x: self.emane_config[x].value for x in self.emane_config}
else: else:
@ -471,6 +474,7 @@ class CoreClient:
mobility_configs, mobility_configs,
service_configs, service_configs,
file_configs, file_configs,
asymmetric_links,
) )
self.set_metadata() self.set_metadata()
process_time = time.perf_counter() - start process_time = time.perf_counter() - start
@ -602,7 +606,7 @@ class CoreClient:
:return: nothing :return: nothing
""" """
node_protos = [x.core_node for x in self.canvas_nodes.values()] node_protos = [x.core_node for x in self.canvas_nodes.values()]
link_protos = list(self.links.values()) link_protos = [x.link for x in self.links.values()]
if self.state != core_pb2.SessionState.DEFINITION: if self.state != core_pb2.SessionState.DEFINITION:
self.client.set_session_state( self.client.set_session_state(
self.session_id, core_pb2.SessionState.DEFINITION self.session_id, core_pb2.SessionState.DEFINITION
@ -813,8 +817,8 @@ class CoreClient:
interface_one=src_interface, interface_one=src_interface,
interface_two=dst_interface, interface_two=dst_interface,
) )
self.links[edge.token] = link edge.set_link(link)
return link self.links[edge.token] = edge
def get_wlan_configs_proto(self): def get_wlan_configs_proto(self):
configs = [] configs = []

View file

@ -5,33 +5,54 @@ import logging
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from core.api.grpc import core_pb2
from coretk.dialogs.colorpicker import ColorPicker
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
from coretk.themes import PADX, PADY
def get_int(var):
value = var.get()
if value != "":
return int(value)
else:
return None
def get_float(var):
value = var.get()
if value != "":
return float(value)
else:
return None
class LinkConfiguration(Dialog): class LinkConfiguration(Dialog):
def __init__(self, master, app, edge): def __init__(self, master, app, edge):
super().__init__(master, app, "link configuration", modal=True) super().__init__(master, app, "Link Configuration", modal=True)
self.app = app self.app = app
self.edge = edge self.edge = edge
self.is_symmetric = True self.is_symmetric = edge.link.options.unidirectional is False
if self.is_symmetric: if self.is_symmetric:
self.symmetry_var = tk.StringVar(value=">>") self.symmetry_var = tk.StringVar(value=">>")
else: else:
self.symmetry_var = tk.StringVar(value="<<") self.symmetry_var = tk.StringVar(value="<<")
self.bandwidth = tk.DoubleVar() self.bandwidth = tk.StringVar()
self.delay = tk.DoubleVar() self.delay = tk.StringVar()
self.jitter = tk.DoubleVar() self.jitter = tk.StringVar()
self.loss = tk.DoubleVar() self.loss = tk.StringVar()
self.duplicate = tk.DoubleVar() self.duplicate = tk.StringVar()
self.color = tk.StringVar(value="#000000")
self.width = tk.DoubleVar()
self.down_bandwidth = tk.DoubleVar() self.down_bandwidth = tk.StringVar()
self.down_delay = tk.DoubleVar() self.down_delay = tk.StringVar()
self.down_jitter = tk.DoubleVar() self.down_jitter = tk.StringVar()
self.down_loss = tk.DoubleVar() self.down_loss = tk.StringVar()
self.down_duplicate = tk.DoubleVar() self.down_duplicate = tk.StringVar()
self.color = tk.StringVar(value="#000000")
self.color_button = None
self.width = tk.DoubleVar()
self.load_link_config() self.load_link_config()
self.symmetric_frame = None self.symmetric_frame = None
@ -44,17 +65,16 @@ class LinkConfiguration(Dialog):
source_name = self.app.canvas.nodes[self.edge.src].core_node.name source_name = self.app.canvas.nodes[self.edge.src].core_node.name
dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name
label = ttk.Label( label = ttk.Label(
self.top, self.top, text=f"Link from {source_name} to {dest_name}", anchor=tk.CENTER
text="Link from %s to %s" % (source_name, dest_name),
anchor=tk.CENTER,
) )
label.grid(row=0, column=0, sticky="nsew") label.grid(row=0, column=0, sticky="ew", pady=PADY)
frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1) frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1) frame.columnconfigure(1, weight=1)
frame.grid(row=1, column=0, sticky="nsew") frame.grid(row=1, column=0, sticky="ew", pady=PADY)
button = ttk.Button(frame, text="unlimited") button = ttk.Button(frame, text="Unlimited")
button.grid(row=0, column=0, sticky="nsew") button.grid(row=0, column=0, sticky="ew", padx=PADX)
if self.is_symmetric: if self.is_symmetric:
button = ttk.Button( button = ttk.Button(
frame, textvariable=self.symmetry_var, command=self.change_symmetry frame, textvariable=self.symmetry_var, command=self.change_symmetry
@ -63,128 +83,239 @@ class LinkConfiguration(Dialog):
button = ttk.Button( button = ttk.Button(
frame, textvariable=self.symmetry_var, command=self.change_symmetry frame, textvariable=self.symmetry_var, command=self.change_symmetry
) )
button.grid(row=0, column=1, sticky="nsew") button.grid(row=0, column=1, sticky="ew")
if self.is_symmetric: if self.is_symmetric:
self.symmetric_frame = self.get_frame() self.symmetric_frame = self.get_frame()
self.symmetric_frame.grid(row=2, column=0, sticky="nsew") self.symmetric_frame.grid(row=2, column=0, sticky="ew", pady=PADY)
else: else:
self.asymmetric_frame = self.get_frame() self.asymmetric_frame = self.get_frame()
self.asymmetric_frame.grid(row=2, column=0, sticky="nsew") self.asymmetric_frame.grid(row=2, column=0, sticky="ew", pady=PADY)
self.draw_spacer(row=3)
frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1) frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1) frame.columnconfigure(1, weight=1)
frame.grid(row=3, column=0, sticky="nsew") frame.grid(row=4, column=0, sticky="ew")
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button = ttk.Button(frame, text="Apply", command=self.apply) button.grid(row=0, column=0, sticky="ew", padx=PADX)
button.grid(row=0, column=0, sticky="nsew")
button = ttk.Button(frame, text="Cancel", command=self.destroy) button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="nsew") button.grid(row=0, column=1, sticky="ew")
def get_frame(self): def get_frame(self):
main_frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
main_frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1)
if self.is_symmetric: if self.is_symmetric:
label_name = "Symmetric link effects: " label_name = "Symmetric Link Effects"
else: else:
label_name = "Asymmetric effects: downstream / upstream " label_name = "Asymmetric Effects: Downstream / Upstream "
row = 0 row = 0
label = ttk.Label(main_frame, text=label_name, anchor=tk.CENTER) label = ttk.Label(frame, text=label_name, anchor=tk.CENTER)
label.grid(row=row, column=0, sticky="nsew") label.grid(row=row, column=0, columnspan=2, sticky="ew", pady=PADY)
row = row + 1 row = row + 1
frame = ttk.Frame(main_frame) label = ttk.Label(frame, text="Bandwidth (bps)")
frame.columnconfigure(0, weight=1) label.grid(row=row, column=0, sticky="ew")
frame.columnconfigure(1, weight=4) entry = ttk.Entry(
frame.grid(row=row, column=0, sticky="nsew") frame,
label = ttk.Label(frame, text="Bandwidth (bps): ") textvariable=self.bandwidth,
label.grid(row=0, column=0, sticky="nsew") validate="key",
entry = ttk.Entry(frame, textvariable=self.bandwidth) validatecommand=(self.app.validation.positive_int, "%P"),
entry.grid(row=0, column=1, sticky="nsew") )
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric: if not self.is_symmetric:
entry = ttk.Entry(frame, textvariable=self.down_bandwidth) entry = ttk.Entry(
entry.grid(row=0, column=2, sticky="nsew") frame,
textvariable=self.down_bandwidth,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1 row = row + 1
frame = ttk.Frame(main_frame) label = ttk.Label(frame, text="Delay (us)")
frame.columnconfigure(0, weight=1) label.grid(row=row, column=0, sticky="ew")
frame.columnconfigure(1, weight=4) entry = ttk.Entry(
frame.grid(row=row, column=0, sticky="nsew") frame,
label = ttk.Label(frame, text="Delay (us): ") textvariable=self.delay,
label.grid(row=0, column=0, sticky="nsew") validate="key",
entry = ttk.Entry(frame, textvariable=self.delay) validatecommand=(self.app.validation.positive_int, "%P"),
entry.grid(row=0, column=1, sticky="nsew") )
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric: if not self.is_symmetric:
entry = ttk.Entry(frame, textvariable=self.down_delay) entry = ttk.Entry(
entry.grid(row=0, column=2, sticky="nsew") frame,
textvariable=self.down_delay,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1 row = row + 1
frame = ttk.Frame(main_frame) label = ttk.Label(frame, text="Jitter (us)")
frame.columnconfigure(0, weight=1) label.grid(row=row, column=0, sticky="ew")
frame.columnconfigure(1, weight=4) entry = ttk.Entry(
frame.grid(row=row, column=0, sticky="nsew") frame,
label = ttk.Label(frame, text="Jitter (us): ") textvariable=self.jitter,
label.grid(row=0, column=0, sticky="nsew") validate="key",
entry = ttk.Entry(frame, textvariable=self.jitter) validatecommand=(self.app.validation.positive_int, "%P"),
entry.grid(row=0, column=1, sticky="nsew") )
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric: if not self.is_symmetric:
entry = ttk.Entry(frame, textvariable=self.down_jitter) entry = ttk.Entry(
entry.grid(row=0, column=2, sticky="nsew") frame,
textvariable=self.down_jitter,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1 row = row + 1
frame = ttk.Frame(main_frame) label = ttk.Label(frame, text="Loss (%)")
frame.columnconfigure(0, weight=1) label.grid(row=row, column=0, sticky="ew")
frame.columnconfigure(1, weight=4) entry = ttk.Entry(
frame.grid(row=row, column=0, sticky="nsew") frame,
label = ttk.Label(frame, text="Loss (%): ") textvariable=self.loss,
label.grid(row=0, column=0, sticky="nsew") validate="key",
entry = ttk.Entry(frame, textvariable=self.loss) validatecommand=(self.app.validation.positive_float, "%P"),
entry.grid(row=0, column=1, sticky="nsew") )
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric: if not self.is_symmetric:
entry = ttk.Entry(frame, textvariable=self.down_loss) entry = ttk.Entry(
entry.grid(row=0, column=1, sticky="nsew") frame,
textvariable=self.down_loss,
validate="key",
validatecommand=(self.app.validation.positive_float, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1 row = row + 1
frame = ttk.Frame(main_frame) label = ttk.Label(frame, text="Duplicate (%)")
frame.columnconfigure(0, weight=1) label.grid(row=row, column=0, sticky="ew")
frame.columnconfigure(1, weight=4) entry = ttk.Entry(
frame.grid(row=row, column=0, sticky="nsew") frame,
label = ttk.Label(frame, text="Duplicate (%): ") textvariable=self.duplicate,
label.grid(row=0, column=0, sticky="nsew") validate="key",
entry = ttk.Entry(frame, textvariable=self.duplicate) validatecommand=(self.app.validation.positive_int, "%P"),
entry.grid(row=0, column=1, sticky="nsew") )
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric: if not self.is_symmetric:
entry = ttk.Entry(frame, textvariable=self.down_duplicate) entry = ttk.Entry(
entry.grid(row=0, column=1, sticky="nsew") frame,
textvariable=self.down_duplicate,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1 row = row + 1
frame = ttk.Frame(main_frame) label = ttk.Label(frame, text="Color")
frame.columnconfigure(0, weight=1) label.grid(row=row, column=0, sticky="ew")
frame.columnconfigure(1, weight=4) self.color_button = tk.Button(
frame.grid(row=row, column=0, sticky="nsew") frame,
label = ttk.Label(frame, text="Color: ") textvariable=self.color,
label.grid(row=0, column=0, sticky="nsew") background=self.color.get(),
button = ttk.Button(frame, textvariable=self.color) bd=0,
button.grid(row=0, column=1, sticky="nsew") relief=tk.FLAT,
highlightthickness=0,
command=self.click_color,
)
self.color_button.grid(row=row, column=1, sticky="ew", pady=PADY)
row = row + 1 row = row + 1
frame = ttk.Frame(main_frame) label = ttk.Label(frame, text="Width")
frame.columnconfigure(0, weight=1) label.grid(row=row, column=0, sticky="ew")
frame.columnconfigure(1, weight=4) entry = ttk.Entry(
frame.grid(row=row, column=0, sticky="nsew") frame,
label = ttk.Label(frame, text="Width: ") textvariable=self.width,
label.grid(row=0, column=0, sticky="nsew") validate="key",
entry = ttk.Entry(frame, textvariable=self.width) validatecommand=(self.app.validation.positive_float, "%P"),
entry.grid(row=0, column=1, sticky="nsew") )
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
return main_frame return frame
def apply(self): def click_color(self):
dialog = ColorPicker(self, self.app, self.color.get())
color = dialog.askcolor()
self.color.set(color)
self.color_button.config(background=color)
def click_apply(self):
logging.debug("click apply") logging.debug("click apply")
width = self.width.get() self.app.canvas.itemconfigure(self.edge.id, width=self.width.get())
self.app.canvas.itemconfigure(self.edge.id, width=width) self.app.canvas.itemconfigure(self.edge.id, fill=self.color.get())
link = self.edge.link
bandwidth = get_int(self.bandwidth)
jitter = get_int(self.jitter)
delay = get_int(self.delay)
duplicate = get_int(self.duplicate)
loss = get_float(self.loss)
options = core_pb2.LinkOptions(
bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, per=loss
)
link.options.CopyFrom(options)
interface_one = None
if link.HasField("interface_one"):
interface_one = link.interface_one.id
interface_two = None
if link.HasField("interface_two"):
interface_two = link.interface_two.id
if not self.is_symmetric:
link.options.unidirectional = True
asym_interface_one = None
if interface_one:
asym_interface_one = core_pb2.Interface(id=interface_one)
asym_interface_two = None
if interface_two:
asym_interface_two = core_pb2.Interface(id=interface_two)
down_bandwidth = get_int(self.down_bandwidth)
down_jitter = get_int(self.down_jitter)
down_delay = get_int(self.down_delay)
down_duplicate = get_int(self.down_duplicate)
down_loss = get_float(self.down_loss)
options = core_pb2.LinkOptions(
bandwidth=down_bandwidth,
jitter=down_jitter,
delay=down_delay,
dup=down_duplicate,
per=down_loss,
unidirectional=True,
)
self.edge.asymmetric_link = core_pb2.Link(
node_one_id=link.node_two_id,
node_two_id=link.node_one_id,
interface_one=asym_interface_one,
interface_two=asym_interface_two,
options=options,
)
else:
link.options.unidirectional = False
self.edge.asymmetric_link = None
if self.app.core.is_runtime() and link.HasField("options"):
session_id = self.app.core.session_id
self.app.core.client.edit_link(
session_id,
link.node_one_id,
link.node_two_id,
link.options,
interface_one,
interface_two,
)
if self.edge.asymmetric_link:
self.app.core.client.edit_link(
session_id,
link.node_two_id,
link.node_one_id,
self.edge.asymmetric_link.options,
interface_one,
interface_two,
)
self.destroy() self.destroy()
def change_symmetry(self): def change_symmetry(self):
@ -212,6 +343,20 @@ class LinkConfiguration(Dialog):
:return: nothing :return: nothing
""" """
width = self.app.canvas.itemcget(self.edge.id, "width") width = self.app.canvas.itemcget(self.edge.id, "width")
# color = self.app.canvas.itemcget(self.edge.id, "fill")
self.width.set(width) self.width.set(width)
# self.color color = self.app.canvas.itemcget(self.edge.id, "fill")
self.color.set(color)
link = self.edge.link
if link.HasField("options"):
self.bandwidth.set(str(link.options.bandwidth))
self.jitter.set(str(link.options.jitter))
self.duplicate.set(str(link.options.dup))
self.loss.set(str(link.options.per))
self.delay.set(str(link.options.delay))
if not self.is_symmetric:
asym_link = self.edge.asymmetric_link
self.down_bandwidth.set(str(asym_link.options.bandwidth))
self.down_jitter.set(str(asym_link.options.jitter))
self.down_duplicate.set(str(asym_link.options.dup))
self.down_loss.set(str(asym_link.options.per))
self.down_delay.set(str(asym_link.options.delay))

View file

@ -1,11 +1,14 @@
import logging import logging
import tkinter as tk import tkinter as tk
from tkinter.font import Font
from coretk import themes from coretk import themes
from coretk.dialogs.linkconfig import LinkConfiguration from coretk.dialogs.linkconfig import LinkConfiguration
from coretk.graph import tags from coretk.graph import tags
from coretk.nodeutils import NodeUtils from coretk.nodeutils import NodeUtils
TEXT_DISTANCE = 0.30
class CanvasWirelessEdge: class CanvasWirelessEdge:
def __init__(self, token, position, src, dst, canvas): def __init__(self, token, position, src, dst, canvas):
@ -46,14 +49,75 @@ class CanvasEdge:
self.id = self.canvas.create_line( self.id = self.canvas.create_line(
x1, y1, x2, y2, tags=tags.EDGE, width=self.width, fill="#ff0000" x1, y1, x2, y2, tags=tags.EDGE, width=self.width, fill="#ff0000"
) )
self.text_src = None
self.text_dst = None
self.token = None self.token = None
self.link_info = None self.font = Font(size=8)
self.link = None
self.asymmetric_link = None
self.throughput = None self.throughput = None
self.set_binding() self.set_binding()
def set_binding(self): def set_binding(self):
self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.create_context) self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.create_context)
def set_link(self, link):
self.link = link
self.draw_labels()
def get_coordinates(self):
x1, y1, x2, y2 = self.canvas.coords(self.id)
v1 = x2 - x1
v2 = y2 - y1
ux = TEXT_DISTANCE * v1
uy = TEXT_DISTANCE * v2
x1 = x1 + ux
y1 = y1 + uy
x2 = x2 - ux
y2 = y2 - uy
return x1, y1, x2, y2
def draw_labels(self):
x1, y1, x2, y2 = self.get_coordinates()
label_one = None
if self.link.HasField("interface_one"):
label_one = (
f"{self.link.interface_one.ip4}/{self.link.interface_one.ip4mask}\n"
f"{self.link.interface_one.ip6}/{self.link.interface_one.ip6mask}\n"
)
label_two = None
if self.link.HasField("interface_two"):
label_two = (
f"{self.link.interface_two.ip4}/{self.link.interface_two.ip4mask}\n"
f"{self.link.interface_two.ip6}/{self.link.interface_two.ip6mask}\n"
)
self.text_src = self.canvas.create_text(
x1,
y1,
text=label_one,
justify=tk.CENTER,
font=self.font,
tags=tags.LINK_INFO,
)
self.text_dst = self.canvas.create_text(
x2,
y2,
text=label_two,
justify=tk.CENTER,
font=self.font,
tags=tags.LINK_INFO,
)
def update_labels(self):
"""
Move edge labels based on current position.
:return: nothing
"""
x1, y1, x2, y2 = self.get_coordinates()
self.canvas.coords(self.text_src, x1, y1)
self.canvas.coords(self.text_dst, x2, y2)
def complete(self, dst): def complete(self, dst):
self.dst = dst self.dst = dst
self.token = tuple(sorted((self.src, self.dst))) self.token = tuple(sorted((self.src, self.dst)))
@ -93,9 +157,9 @@ class CanvasEdge:
def delete(self): def delete(self):
self.canvas.delete(self.id) self.canvas.delete(self.id)
if self.link_info: if self.link:
self.canvas.delete(self.link_info.id1) self.canvas.delete(self.text_src)
self.canvas.delete(self.link_info.id2) self.canvas.delete(self.text_dst)
def create_context(self, event): def create_context(self, event):
logging.debug("create link context") logging.debug("create link context")

View file

@ -9,7 +9,7 @@ from coretk.dialogs.shapemod import ShapeDialog
from coretk.graph import tags from coretk.graph import tags
from coretk.graph.edges import CanvasEdge, CanvasWirelessEdge from coretk.graph.edges import CanvasEdge, CanvasWirelessEdge
from coretk.graph.enums import GraphMode, ScaleOption from coretk.graph.enums import GraphMode, ScaleOption
from coretk.graph.linkinfo import LinkInfo, Throughput from coretk.graph.linkinfo import Throughput
from coretk.graph.node import CanvasNode from coretk.graph.node import CanvasNode
from coretk.graph.shape import Shape from coretk.graph.shape import Shape
from coretk.graph.shapeutils import ShapeType, is_draw_shape, is_marker from coretk.graph.shapeutils import ShapeType, is_draw_shape, is_marker
@ -202,12 +202,12 @@ class CanvasGraph(tk.Canvas):
""" """
# draw existing nodes # draw existing nodes
for core_node in session.nodes: for core_node in session.nodes:
logging.info("drawing core node: %s", core_node)
# peer to peer node is not drawn on the GUI # peer to peer node is not drawn on the GUI
if NodeUtils.is_ignore_node(core_node.type): if NodeUtils.is_ignore_node(core_node.type):
continue continue
# draw nodes on the canvas # draw nodes on the canvas
logging.info("drawing core node: %s", core_node)
image = NodeUtils.node_icon(core_node.type, core_node.model) image = NodeUtils.node_icon(core_node.type, core_node.model)
if core_node.icon: if core_node.icon:
try: try:
@ -223,33 +223,42 @@ class CanvasGraph(tk.Canvas):
# draw existing links # draw existing links
for link in session.links: for link in session.links:
logging.info("drawing link: %s", link)
canvas_node_one = self.core.canvas_nodes[link.node_one_id] canvas_node_one = self.core.canvas_nodes[link.node_one_id]
node_one = canvas_node_one.core_node node_one = canvas_node_one.core_node
canvas_node_two = self.core.canvas_nodes[link.node_two_id] canvas_node_two = self.core.canvas_nodes[link.node_two_id]
node_two = canvas_node_two.core_node node_two = canvas_node_two.core_node
token = tuple(sorted((canvas_node_one.id, canvas_node_two.id)))
if link.type == core_pb2.LinkType.WIRELESS: if link.type == core_pb2.LinkType.WIRELESS:
self.add_wireless_edge(canvas_node_one, canvas_node_two) self.add_wireless_edge(canvas_node_one, canvas_node_two)
else: else:
edge = CanvasEdge( if token not in self.edges:
node_one.position.x, edge = CanvasEdge(
node_one.position.y, node_one.position.x,
node_two.position.x, node_one.position.y,
node_two.position.y, node_two.position.x,
canvas_node_one.id, node_two.position.y,
self, canvas_node_one.id,
) self,
edge.token = tuple(sorted((canvas_node_one.id, canvas_node_two.id))) )
edge.dst = canvas_node_two.id edge.token = token
edge.check_wireless() edge.dst = canvas_node_two.id
canvas_node_one.edges.add(edge) edge.set_link(link)
canvas_node_two.edges.add(edge) edge.check_wireless()
self.edges[edge.token] = edge canvas_node_one.edges.add(edge)
self.core.links[edge.token] = link canvas_node_two.edges.add(edge)
edge.link_info = LinkInfo(self, edge, link) self.edges[edge.token] = edge
if link.HasField("interface_one"): self.core.links[edge.token] = edge
canvas_node_one.interfaces.append(link.interface_one) if link.HasField("interface_one"):
if link.HasField("interface_two"): canvas_node_one.interfaces.append(link.interface_one)
canvas_node_two.interfaces.append(link.interface_two) if link.HasField("interface_two"):
canvas_node_two.interfaces.append(link.interface_two)
elif link.options.unidirectional:
edge = self.edges[token]
edge.asymmetric_link = link
else:
logging.error("duplicate link received: %s", link)
# raise the nodes so they on top of the links # raise the nodes so they on top of the links
self.tag_raise(tags.NODE) self.tag_raise(tags.NODE)
@ -373,8 +382,7 @@ class CanvasGraph(tk.Canvas):
node_src.edges.add(edge) node_src.edges.add(edge)
node_dst = self.nodes[edge.dst] node_dst = self.nodes[edge.dst]
node_dst.edges.add(edge) node_dst.edges.add(edge)
link = self.core.create_link(edge, node_src, node_dst) self.core.create_link(edge, node_src, node_dst)
edge.link_info = LinkInfo(self, edge, link)
def select_object(self, object_id, choose_multiple=False): def select_object(self, object_id, choose_multiple=False):
""" """
@ -819,7 +827,7 @@ class CanvasGraph(tk.Canvas):
:param CanvasNode dest: destination node :param CanvasNode dest: destination node
:return: nothing :return: nothing
""" """
if tuple([source.id, dest.id]) not in self.edges: if (source.id, dest.id) not in self.edges:
pos0 = source.core_node.position pos0 = source.core_node.position
x0 = pos0.x x0 = pos0.x
y0 = pos0.y y0 = pos0.y
@ -828,5 +836,4 @@ class CanvasGraph(tk.Canvas):
self.edges[edge.token] = edge self.edges[edge.token] = edge
self.nodes[source.id].edges.add(edge) self.nodes[source.id].edges.add(edge)
self.nodes[dest.id].edges.add(edge) self.nodes[dest.id].edges.add(edge)
link = self.core.create_link(edge, source, dest) self.core.create_link(edge, source, dest)
edge.link_info = LinkInfo(self, edge, link)

View file

@ -1,83 +1,8 @@
""" """
Link information, such as IPv4, IPv6 and throughput drawn in the canvas Link information, such as IPv4, IPv6 and throughput drawn in the canvas
""" """
import tkinter as tk
from tkinter import font
from core.api.grpc import core_pb2 from core.api.grpc import core_pb2
from coretk.graph import tags
TEXT_DISTANCE = 0.30
class LinkInfo:
def __init__(self, canvas, edge, link):
"""
create an instance of LinkInfo object
:param coretk.graph.Graph canvas: canvas object
:param coretk.graph.CanvasEdge edge: canvas edge onject
:param link: core link to draw info for
"""
self.canvas = canvas
self.edge = edge
self.link = link
self.id1 = None
self.id2 = None
self.font = font.Font(size=8)
self.draw_labels()
def get_coordinates(self):
x1, y1, x2, y2 = self.canvas.coords(self.edge.id)
v1 = x2 - x1
v2 = y2 - y1
ux = TEXT_DISTANCE * v1
uy = TEXT_DISTANCE * v2
x1 = x1 + ux
y1 = y1 + uy
x2 = x2 - ux
y2 = y2 - uy
return x1, y1, x2, y2
def draw_labels(self):
x1, y1, x2, y2 = self.get_coordinates()
label_one = None
if self.link.HasField("interface_one"):
label_one = (
f"{self.link.interface_one.ip4}/{self.link.interface_one.ip4mask}\n"
f"{self.link.interface_one.ip6}/{self.link.interface_one.ip6mask}\n"
)
label_two = None
if self.link.HasField("interface_two"):
label_two = (
f"{self.link.interface_two.ip4}/{self.link.interface_two.ip4mask}\n"
f"{self.link.interface_two.ip6}/{self.link.interface_two.ip6mask}\n"
)
self.id1 = self.canvas.create_text(
x1,
y1,
text=label_one,
justify=tk.CENTER,
font=self.font,
tags=tags.LINK_INFO,
)
self.id2 = self.canvas.create_text(
x2,
y2,
text=label_two,
justify=tk.CENTER,
font=self.font,
tags=tags.LINK_INFO,
)
def recalculate_info(self):
"""
move the node info when the canvas node move
:return: nothing
"""
x1, y1, x2, y2 = self.get_coordinates()
self.canvas.coords(self.id1, x1, y1)
self.canvas.coords(self.id2, x2, y2)
class Throughput: class Throughput:

View file

@ -129,7 +129,7 @@ class CanvasNode:
else: else:
self.canvas.coords(edge.id, x1, y1, x, y) self.canvas.coords(edge.id, x1, y1, x, y)
self.canvas.throughput_draw.move(edge) self.canvas.throughput_draw.move(edge)
edge.link_info.recalculate_info() edge.update_labels()
for edge in self.wireless_edges: for edge in self.wireless_edges:
x1, y1, x2, y2 = self.canvas.coords(edge.id) x1, y1, x2, y2 = self.canvas.coords(edge.id)

View file

@ -161,6 +161,7 @@ class CoreGrpcClient:
mobility_configs=None, mobility_configs=None,
service_configs=None, service_configs=None,
service_file_configs=None, service_file_configs=None,
asymmetric_links=None,
): ):
""" """
Start a session. Start a session.
@ -176,6 +177,7 @@ class CoreGrpcClient:
:param list mobility_configs: node mobility configurations :param list mobility_configs: node mobility configurations
:param list service_configs: node service configurations :param list service_configs: node service configurations
:param list service_file_configs: node service file configurations :param list service_file_configs: node service file configurations
:param list asymmetric_links: asymmetric links to edit
:return: start session response :return: start session response
:rtype: core_pb2.StartSessionResponse :rtype: core_pb2.StartSessionResponse
""" """
@ -191,6 +193,7 @@ class CoreGrpcClient:
mobility_configs=mobility_configs, mobility_configs=mobility_configs,
service_configs=service_configs, service_configs=service_configs,
service_file_configs=service_file_configs, service_file_configs=service_file_configs,
asymmetric_links=asymmetric_links,
) )
return self.stub.StartSession(request) return self.stub.StartSession(request)

View file

@ -128,7 +128,7 @@ def create_nodes(session, node_protos):
def create_links(session, link_protos): def create_links(session, link_protos):
""" """
Create nodes using a thread pool and wait for completion. Create links using a thread pool and wait for completion.
:param core.emulator.session.Session session: session to create nodes in :param core.emulator.session.Session session: session to create nodes in
:param list[core_pb2.Link] link_protos: link proto messages :param list[core_pb2.Link] link_protos: link proto messages
@ -149,6 +149,29 @@ def create_links(session, link_protos):
return results, exceptions return results, exceptions
def edit_links(session, link_protos):
"""
Edit links using a thread pool and wait for completion.
:param core.emulator.session.Session session: session to create nodes in
:param list[core_pb2.Link] link_protos: link proto messages
:return: results and exceptions for created links
:rtype: tuple
"""
funcs = []
for link_proto in link_protos:
node_one_id = link_proto.node_one_id
node_two_id = link_proto.node_two_id
interface_one, interface_two, options = add_link_data(link_proto)
args = (node_one_id, node_two_id, interface_one.id, interface_two.id, options)
funcs.append((session.update_link, args, {}))
start = time.monotonic()
results, exceptions = utils.threadpool(funcs)
total = time.monotonic() - start
logging.debug("grpc edit links time: %s", total)
return results, exceptions
def convert_value(value): def convert_value(value):
""" """
Convert value into string. Convert value into string.

View file

@ -158,6 +158,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
# create links # create links
grpcutils.create_links(session, request.links) grpcutils.create_links(session, request.links)
# asymmetric links
grpcutils.edit_links(session, request.asymmetric_links)
# set to instantiation and start # set to instantiation and start
session.set_state(EventTypes.INSTANTIATION_STATE) session.set_state(EventTypes.INSTANTIATION_STATE)
session.instantiate() session.instantiate()

View file

@ -1053,6 +1053,7 @@ class CoreNetworkBase(NodeBase):
message_type=0, message_type=0,
node1_id=linked_node.id, node1_id=linked_node.id,
node2_id=self.id, node2_id=self.id,
link_type=self.linktype,
unidirectional=1, unidirectional=1,
delay=netif.getparam("delay"), delay=netif.getparam("delay"),
bandwidth=netif.getparam("bw"), bandwidth=netif.getparam("bw"),

View file

@ -965,6 +965,7 @@ class PtpNet(CoreNetwork):
if unidirectional: if unidirectional:
link_data = LinkData( link_data = LinkData(
message_type=0, message_type=0,
link_type=self.linktype,
node1_id=if2.node.id, node1_id=if2.node.id,
node2_id=if1.node.id, node2_id=if1.node.id,
delay=if2.getparam("delay"), delay=if2.getparam("delay"),

View file

@ -150,6 +150,7 @@ message StartSessionRequest {
repeated MobilityConfig mobility_configs = 9; repeated MobilityConfig mobility_configs = 9;
repeated ServiceConfig service_configs = 10; repeated ServiceConfig service_configs = 10;
repeated ServiceFileConfig service_file_configs = 11; repeated ServiceFileConfig service_file_configs = 11;
repeated Link asymmetric_links = 12;
} }
message StartSessionResponse { message StartSessionResponse {