Merge branch 'develop' into pygui-rmallservices-fix

This commit is contained in:
Huy Pham 2020-04-30 13:48:38 -07:00
commit b116d525d9
7 changed files with 248 additions and 34 deletions

View file

@ -357,6 +357,8 @@ class Session:
) )
interface = create_interface(node_one, net_one, interface_one) interface = create_interface(node_one, net_one, interface_one)
node_one_interface = interface node_one_interface = interface
wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
if not wireless_net:
link_config(net_one, interface, link_options) link_config(net_one, interface, link_options)
# network to node # network to node
@ -368,7 +370,8 @@ class Session:
) )
interface = create_interface(node_two, net_one, interface_two) interface = create_interface(node_two, net_one, interface_two)
node_two_interface = interface node_two_interface = interface
if not link_options.unidirectional: wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
if not link_options.unidirectional and not wireless_net:
link_config(net_one, interface, link_options) link_config(net_one, interface, link_options)
# network to network # network to network

View file

@ -0,0 +1,158 @@
import logging
import tkinter as tk
from tkinter import ttk
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
class FindDialog(Dialog):
def __init__(self, master, app) -> None:
super().__init__(master, app, "Find", modal=True)
self.find_text = tk.StringVar(value="")
self.tree = None
self.draw()
self.protocol("WM_DELETE_WINDOW", self.close_dialog)
self.bind("<Return>", self.find_node)
def draw(self) -> None:
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.top.rowconfigure(1, weight=5)
self.top.rowconfigure(2, weight=1)
# Find node frame
frame = ttk.Frame(self.top, padding=FRAME_PAD)
frame.grid(sticky="nsew")
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Find:")
label.grid()
entry = ttk.Entry(frame, textvariable=self.find_text)
entry.grid(row=0, column=1, sticky="nsew")
# node list frame
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1)
frame.grid(sticky="nsew", padx=PADX, pady=PADY)
self.tree = ttk.Treeview(
frame,
columns=("nodeid", "name", "location", "detail"),
show="headings",
selectmode=tk.BROWSE,
)
self.tree.grid(sticky="nsew", pady=PADY)
style = ttk.Style()
heading_size = int(self.app.guiconfig["scale"] * 10)
style.configure("Treeview.Heading", font=(None, heading_size, "bold"))
self.tree.column("nodeid", stretch=tk.YES, anchor="center")
self.tree.heading("nodeid", text="Node ID")
self.tree.column("name", stretch=tk.YES, anchor="center")
self.tree.heading("name", text="Name")
self.tree.column("location", stretch=tk.YES, anchor="center")
self.tree.heading("location", text="Location")
self.tree.column("detail", stretch=tk.YES, anchor="center")
self.tree.heading("detail", text="Detail")
self.tree.bind("<<TreeviewSelect>>", self.click_select)
yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview)
yscrollbar.grid(row=0, column=1, sticky="ns")
self.tree.configure(yscrollcommand=yscrollbar.set)
xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview)
xscrollbar.grid(row=1, sticky="ew")
self.tree.configure(xscrollcommand=xscrollbar.set)
# button frame
frame = ttk.Frame(self.top)
frame.grid(sticky="nsew")
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
button = ttk.Button(frame, text="Find", command=self.find_node)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.close_dialog)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
def clear_treeview_items(self) -> None:
"""
clear all items in the treeview
"""
for i in list(self.tree.get_children("")):
self.tree.delete(i)
def find_node(self, _event: tk.Event = None) -> None:
"""
Query nodes that have the same node name as our search key,
display results to tree view
"""
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
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)
# TODO I am not sure what to insert for Detail column, leaving in blank for now
self.tree.insert(
"",
tk.END,
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])
def close_dialog(self) -> None:
self.app.canvas.delete("find")
self.destroy()
def click_select(self, _event: tk.Event = None) -> None:
"""
find the node that matches search criteria, circle around that node
and scroll the x and y scrollbar to be able to see the node if
it is out of sight
"""
item = self.tree.selection()
if item:
self.app.canvas.delete("find")
node_id = int(self.tree.item(item, "text"))
canvas_node = self.app.core.canvas_nodes[node_id]
x0, y0, x1, y1 = self.app.canvas.bbox(canvas_node.id)
dist = 5 * self.app.guiconfig["scale"]
self.app.canvas.create_oval(
x0 - dist,
y0 - dist,
x1 + dist,
y1 + dist,
tags="find",
outline="red",
width=3.0 * self.app.guiconfig["scale"],
)
_x, _y, _, _ = self.app.canvas.bbox(canvas_node.id)
oid = self.app.canvas.find_withtag("rectangle")
x0, y0, x1, y1 = self.app.canvas.bbox(oid[0])
logging.debug("Dist to most left: %s", abs(x0 - _x))
logging.debug("White canvas width: %s", abs(x0 - x1))
# calculate the node's location
# (as fractions of white canvas's width and height)
# and instantly scroll the x and y scrollbar to that location
xscroll_fraction = abs(x0 - _x) / abs(x0 - x1)
yscroll_fraction = abs(y0 - _y) / abs(y0 - y1)
# scroll a little more to the left or a little bit up so that the node
# doesn't always fall in the most top-left corner
for i in range(2):
if xscroll_fraction > 0.05:
xscroll_fraction = xscroll_fraction - 0.05
if yscroll_fraction > 0.05:
yscroll_fraction = yscroll_fraction - 0.05
self.app.canvas.xview_moveto(xscroll_fraction)
self.app.canvas.yview_moveto(yscroll_fraction)

View file

@ -72,12 +72,15 @@ class SessionsDialog(Dialog):
show="headings", show="headings",
selectmode=tk.BROWSE, selectmode=tk.BROWSE,
) )
style = ttk.Style()
heading_size = int(self.app.guiconfig["scale"] * 10)
style.configure("Treeview.Heading", font=(None, heading_size, "bold"))
self.tree.grid(sticky="nsew") self.tree.grid(sticky="nsew")
self.tree.column("id", stretch=tk.YES) self.tree.column("id", stretch=tk.YES, anchor="center")
self.tree.heading("id", text="ID") self.tree.heading("id", text="ID")
self.tree.column("state", stretch=tk.YES) self.tree.column("state", stretch=tk.YES, anchor="center")
self.tree.heading("state", text="State") self.tree.heading("state", text="State")
self.tree.column("nodes", stretch=tk.YES) self.tree.column("nodes", stretch=tk.YES, anchor="center")
self.tree.heading("nodes", text="Node Count") self.tree.heading("nodes", text="Node Count")
for index, session in enumerate(self.sessions): for index, session in enumerate(self.sessions):
@ -213,3 +216,5 @@ class SessionsDialog(Dialog):
def on_closing(self) -> None: def on_closing(self) -> None:
if self.is_start_app and messagebox.askokcancel("Exit", "Quit?", parent=self): if self.is_start_app and messagebox.askokcancel("Exit", "Quit?", parent=self):
self.click_exit() self.click_exit()
if not self.is_start_app:
self.destroy()

View file

@ -11,6 +11,7 @@ from core.gui.dialogs.about import AboutDialog
from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog
from core.gui.dialogs.canvaswallpaper import CanvasWallpaperDialog from core.gui.dialogs.canvaswallpaper import CanvasWallpaperDialog
from core.gui.dialogs.executepython import ExecutePythonDialog from core.gui.dialogs.executepython import ExecutePythonDialog
from core.gui.dialogs.find import FindDialog
from core.gui.dialogs.hooks import HooksDialog from core.gui.dialogs.hooks import HooksDialog
from core.gui.dialogs.ipdialog import IpConfigDialog from core.gui.dialogs.ipdialog import IpConfigDialog
from core.gui.dialogs.macdialog import MacConfigDialog from core.gui.dialogs.macdialog import MacConfigDialog
@ -114,6 +115,7 @@ class Menubar(tk.Menu):
Create edit menu Create edit menu
""" """
menu = tk.Menu(self) menu = tk.Menu(self)
menu.add_command(label="Find", accelerator="Ctrl+F", command=self.click_find)
menu.add_command(label="Preferences", command=self.click_preferences) menu.add_command(label="Preferences", command=self.click_preferences)
menu.add_command(label="Undo", accelerator="Ctrl+Z", state=tk.DISABLED) menu.add_command(label="Undo", accelerator="Ctrl+Z", state=tk.DISABLED)
menu.add_command(label="Redo", accelerator="Ctrl+Y", state=tk.DISABLED) menu.add_command(label="Redo", accelerator="Ctrl+Y", state=tk.DISABLED)
@ -125,6 +127,7 @@ class Menubar(tk.Menu):
label="Delete", accelerator="Ctrl+D", command=self.click_delete label="Delete", accelerator="Ctrl+D", command=self.click_delete
) )
self.add_cascade(label="Edit", menu=menu) self.add_cascade(label="Edit", menu=menu)
self.app.master.bind_all("<Control-f>", self.click_find)
self.app.master.bind_all("<Control-x>", self.click_cut) self.app.master.bind_all("<Control-x>", self.click_cut)
self.app.master.bind_all("<Control-c>", self.click_copy) self.app.master.bind_all("<Control-c>", self.click_copy)
self.app.master.bind_all("<Control-v>", self.click_paste) self.app.master.bind_all("<Control-v>", self.click_paste)
@ -397,6 +400,10 @@ class Menubar(tk.Menu):
self.core.create_new_session() self.core.create_new_session()
self.core.xml_file = None self.core.xml_file = None
def click_find(self, _event: tk.Event = None) -> None:
dialog = FindDialog(self.app, self.app)
dialog.show()
def click_preferences(self) -> None: def click_preferences(self) -> None:
dialog = PreferencesDialog(self.app, self.app) dialog = PreferencesDialog(self.app, self.app)
dialog.show() dialog.show()

View file

@ -260,7 +260,7 @@ class QuaggaService(CoreService):
if netaddr.valid_ipv4(a): if netaddr.valid_ipv4(a):
return a return a
# raise ValueError, "no IPv4 address found for router ID" # raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0" return "0.0.0.%d" % node.id
@staticmethod @staticmethod
def rj45check(ifc): def rj45check(ifc):
@ -348,7 +348,19 @@ class Ospfv2(QuaggaService):
@classmethod @classmethod
def generatequaggaifcconfig(cls, node, ifc): def generatequaggaifcconfig(cls, node, ifc):
return cls.mtucheck(ifc) cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers
if cls.rj45check(ifc):
return cfg
cfg += cls.ptpcheck(ifc)
return (
cfg
+ """\
ip ospf hello-interval 2
ip ospf dead-interval 6
ip ospf retransmit-interval 5
"""
)
class Ospfv3(QuaggaService): class Ospfv3(QuaggaService):

View file

@ -40,10 +40,15 @@ class OvsService(SdnService):
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# auto-generated by OvsService (OvsService.py)\n" cfg += "# auto-generated by OvsService (OvsService.py)\n"
cfg += "/etc/init.d/openvswitch-switch start < /dev/null\n" cfg += "## First make sure that the ovs services are up and running\n"
cfg += "/etc/init.d/openvswitch-switch start < /dev/null\n\n"
cfg += "## create the switch itself, set the fail mode to secure, \n"
cfg += "## this stops it from routing traffic without defined flows.\n"
cfg += "## remove the -- and everything after if you want it to act as a regular switch\n"
cfg += "ovs-vsctl add-br ovsbr0 -- set Bridge ovsbr0 fail-mode=secure\n" cfg += "ovs-vsctl add-br ovsbr0 -- set Bridge ovsbr0 fail-mode=secure\n"
cfg += "ip link set dev ovsbr0 up\n"
cfg += "\n## Now add all our interfaces as ports to the switch\n"
portnum = 1
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, "control") and ifc.control is True: if hasattr(ifc, "control") and ifc.control is True:
continue continue
@ -51,9 +56,8 @@ class OvsService(SdnService):
ifnum = ifnumstr[0] ifnum = ifnumstr[0]
# create virtual interfaces # create virtual interfaces
cfg += "## Create a veth pair to send the data to\n"
cfg += "ip link add rtr%s type veth peer name sw%s\n" % (ifnum, ifnum) cfg += "ip link add rtr%s type veth peer name sw%s\n" % (ifnum, ifnum)
cfg += "ip link set dev rtr%s up\n" % ifnum
cfg += "ip link set dev sw%s up\n" % ifnum
# remove ip address of eths because quagga/zebra will assign same IPs to rtr interfaces # remove ip address of eths because quagga/zebra will assign same IPs to rtr interfaces
# or assign them manually to rtr interfaces if zebra is not running # or assign them manually to rtr interfaces if zebra is not running
@ -71,17 +75,37 @@ class OvsService(SdnService):
raise ValueError("invalid address: %s" % ifcaddr) raise ValueError("invalid address: %s" % ifcaddr)
# add interfaces to bridge # add interfaces to bridge
cfg += "ovs-vsctl add-port ovsbr0 eth%s\n" % ifnum # Make port numbers explicit so they're easier to follow in reading the script
cfg += "ovs-vsctl add-port ovsbr0 sw%s\n" % ifnum cfg += "## Add the CORE interface to the switch\n"
cfg += (
"ovs-vsctl add-port ovsbr0 eth%s -- set Interface eth%s ofport_request=%d\n"
% (ifnum, ifnum, portnum)
)
cfg += "## And then add its sibling veth interface\n"
cfg += (
"ovs-vsctl add-port ovsbr0 sw%s -- set Interface sw%s ofport_request=%d\n"
% (ifnum, ifnum, portnum + 1)
)
cfg += "## start them up so we can send/receive data\n"
cfg += "ovs-ofctl mod-port ovsbr0 eth%s up\n" % ifnum
cfg += "ovs-ofctl mod-port ovsbr0 sw%s up\n" % ifnum
cfg += "## Bring up the lower part of the veth pair\n"
cfg += "ip link set dev rtr%s up\n" % ifnum
portnum += 2
# Add rule for default controller if there is one local (even if the controller is not local, it finds it) # Add rule for default controller if there is one local (even if the controller is not local, it finds it)
cfg += "\n## We assume there will be an SDN controller on the other end of this, \n"
cfg += "## but it will still function if there's not\n"
cfg += "ovs-vsctl set-controller ovsbr0 tcp:127.0.0.1:6633\n" cfg += "ovs-vsctl set-controller ovsbr0 tcp:127.0.0.1:6633\n"
cfg += "\n## Now to create some default flows, \n"
cfg += "## if the above controller will be present then you probably want to delete them\n"
# Setup default flows # Setup default flows
portnum = 1 portnum = 1
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, "control") and ifc.control is True: if hasattr(ifc, "control") and ifc.control is True:
continue continue
cfg += "## Take the data from the CORE interface and put it on the veth and vice versa\n"
cfg += ( cfg += (
"ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n" "ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n"
% (portnum, portnum + 1) % (portnum, portnum + 1)

View file

@ -13,7 +13,7 @@ from core.errors import CoreXmlError
from core.nodes.base import CoreNetworkBase, CoreNodeBase, NodeBase from core.nodes.base import CoreNetworkBase, CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode from core.nodes.docker import DockerNode
from core.nodes.lxd import LxcNode from core.nodes.lxd import LxcNode
from core.nodes.network import CtrlNet from core.nodes.network import CtrlNet, WlanNode
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
if TYPE_CHECKING: if TYPE_CHECKING:
@ -559,7 +559,12 @@ class CoreXmlWriter:
) )
link_element.append(interface_two) link_element.append(interface_two)
# check for options # check for options, don't write for emane/wlan links
node_one = self.session.get_node(link_data.node1_id)
node_two = self.session.get_node(link_data.node2_id)
is_node_one_wireless = isinstance(node_one, (WlanNode, EmaneNet))
is_node_two_wireless = isinstance(node_two, (WlanNode, EmaneNet))
if not any([is_node_one_wireless, is_node_two_wireless]):
options = etree.Element("options") options = etree.Element("options")
add_attribute(options, "delay", link_data.delay) add_attribute(options, "delay", link_data.delay)
add_attribute(options, "bandwidth", link_data.bandwidth) add_attribute(options, "bandwidth", link_data.bandwidth)