| #!/usr/bin/python |
| |
| """ |
| MiniCCNxEdit: a simple network editor for MiniCCNx |
| |
| Based on miniedit by: |
| Bob Lantz, April 2010 |
| Gregory Gee, July 2013 |
| |
| Carlos Cabral, Jan 2013 |
| Caio Elias, Nov 2014 |
| |
| """ |
| |
| MINIEDIT_VERSION = '2.2.0.1' |
| |
| from optparse import OptionParser |
| from Tkinter import * |
| from ttk import Notebook |
| from tkMessageBox import showinfo, showerror, showwarning |
| from subprocess import call |
| import tkFont |
| import csv |
| import tkFileDialog |
| import tkSimpleDialog |
| import re |
| import json |
| from distutils.version import StrictVersion |
| import os |
| import sys |
| from functools import partial |
| |
| if 'PYTHONPATH' in os.environ: |
| sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path |
| |
| # someday: from ttk import * |
| |
| from mininet.log import info, error, debug, output, setLogLevel |
| from mininet.net import Mininet, VERSION |
| from mininet.util import ipStr, netParse, ipAdd, quietRun |
| from mininet.util import buildTopo |
| from mininet.util import custom, customConstructor |
| from mininet.term import makeTerm, cleanUpScreens |
| from mininet.node import Controller, RemoteController, NOX, OVSController |
| from mininet.node import CPULimitedHost, Host, Node |
| from mininet.node import OVSKernelSwitch, OVSSwitch, UserSwitch |
| from mininet.link import TCLink, Intf, Link |
| from mininet.cli import CLI |
| from mininet.moduledeps import moduleDeps, pathCheck |
| from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo |
| from mininet.topolib import TreeTopo |
| |
| print 'MiniCCNxEdit running...' #+VERSION |
| MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION) |
| if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): |
| from mininet.node import IVSSwitch |
| |
| TOPODEF = 'none' |
| TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), |
| 'linear': LinearTopo, |
| 'reversed': SingleSwitchReversedTopo, |
| 'single': SingleSwitchTopo, |
| 'none': None, |
| 'tree': TreeTopo } |
| LINKDEF = 'default' |
| LINKS = { 'default': Link, |
| 'tc': TCLink } |
| HOSTDEF = 'proc' |
| HOSTS = { 'proc': Host, |
| 'rt': custom( CPULimitedHost, sched='rt' ), |
| 'cfs': custom( CPULimitedHost, sched='cfs' ) } |
| |
| class LegacyRouter( Node ): |
| |
| def __init__( self, name, inNamespace=True, **params ): |
| Node.__init__( self, name, inNamespace, **params ) |
| |
| def config( self, **_params ): |
| if self.intfs: |
| self.setParam( _params, 'setIP', ip='0.0.0.0' ) |
| r = Node.config( self, **_params ) |
| self.cmd('sysctl -w net.ipv4.ip_forward=1') |
| return r |
| |
| class CustomDialog(object): |
| |
| # TODO: Fix button placement and Title and window focus lock |
| def __init__(self, master, title): |
| self.top=Toplevel(master) |
| |
| self.bodyFrame = Frame(self.top) |
| self.bodyFrame.grid(row=0, column=0, sticky='nswe') |
| self.body(self.bodyFrame) |
| |
| #return self.b # initial focus |
| buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey') |
| buttonFrame.grid(row=1 , column=0, sticky='nswe') |
| |
| okButton = Button(buttonFrame, width=8, text='OK', relief='groove', |
| bd=4, command=self.okAction) |
| okButton.grid(row=0, column=0, sticky=E) |
| |
| canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove', |
| bd=4, command=self.cancelAction) |
| canlceButton.grid(row=0, column=1, sticky=W) |
| |
| def body(self, master): |
| self.rootFrame = master |
| |
| def apply(self): |
| self.top.destroy() |
| |
| def cancelAction(self): |
| self.top.destroy() |
| |
| def okAction(self): |
| self.apply() |
| self.top.destroy() |
| |
| class HostDialog(CustomDialog): |
| |
| def __init__(self, master, title, prefDefaults): |
| |
| self.prefValues = prefDefaults |
| self.result = None |
| |
| CustomDialog.__init__(self, master, title) |
| |
| def body(self, master): |
| self.rootFrame = master |
| n = Notebook(self.rootFrame) |
| self.propFrame = Frame(n) |
| self.vlanFrame = Frame(n) |
| self.interfaceFrame = Frame(n) |
| self.mountFrame = Frame(n) |
| n.add(self.propFrame, text='Properties') |
| n.add(self.vlanFrame, text='VLAN Interfaces') |
| n.add(self.interfaceFrame, text='External Interfaces') |
| n.add(self.mountFrame, text='Private Directories') |
| n.pack() |
| |
| ### TAB 1 |
| # Field for Hostname |
| Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E) |
| self.hostnameEntry = Entry(self.propFrame) |
| self.hostnameEntry.grid(row=0, column=1) |
| if 'hostname' in self.prefValues: |
| self.hostnameEntry.insert(0, self.prefValues['hostname']) |
| |
| # Field for Switch IP |
| Label(self.propFrame, text="IP Address:").grid(row=1, sticky=E) |
| self.ipEntry = Entry(self.propFrame) |
| self.ipEntry.grid(row=1, column=1) |
| if 'ip' in self.prefValues: |
| self.ipEntry.insert(0, self.prefValues['ip']) |
| |
| # Field for default route |
| Label(self.propFrame, text="Default Route:").grid(row=2, sticky=E) |
| self.routeEntry = Entry(self.propFrame) |
| self.routeEntry.grid(row=2, column=1) |
| if 'defaultRoute' in self.prefValues: |
| self.routeEntry.insert(0, self.prefValues['defaultRoute']) |
| |
| # Field for CPU |
| Label(self.propFrame, text="Amount CPU:").grid(row=3, sticky=E) |
| self.cpuEntry = Entry(self.propFrame) |
| self.cpuEntry.grid(row=3, column=1) |
| if 'cpu' in self.prefValues: |
| self.cpuEntry.insert(0, str(self.prefValues['cpu'])) |
| # Selection of Scheduler |
| if 'sched' in self.prefValues: |
| sched = self.prefValues['sched'] |
| else: |
| sched = 'host' |
| self.schedVar = StringVar(self.propFrame) |
| self.schedOption = OptionMenu(self.propFrame, self.schedVar, "host", "cfs", "rt") |
| self.schedOption.grid(row=3, column=2, sticky=W) |
| self.schedVar.set(sched) |
| |
| # Selection of Cores |
| Label(self.propFrame, text="Cores:").grid(row=4, sticky=E) |
| self.coreEntry = Entry(self.propFrame) |
| self.coreEntry.grid(row=4, column=1) |
| if 'cores' in self.prefValues: |
| self.coreEntry.insert(1, self.prefValues['cores']) |
| |
| # Start command |
| Label(self.propFrame, text="Start Command:").grid(row=5, sticky=E) |
| self.startEntry = Entry(self.propFrame) |
| self.startEntry.grid(row=5, column=1, sticky='nswe', columnspan=3) |
| if 'startCommand' in self.prefValues: |
| self.startEntry.insert(0, str(self.prefValues['startCommand'])) |
| # Stop command |
| Label(self.propFrame, text="Stop Command:").grid(row=6, sticky=E) |
| self.stopEntry = Entry(self.propFrame) |
| self.stopEntry.grid(row=6, column=1, sticky='nswe', columnspan=3) |
| if 'stopCommand' in self.prefValues: |
| self.stopEntry.insert(0, str(self.prefValues['stopCommand'])) |
| |
| ### TAB 2 |
| # External Interfaces |
| self.externalInterfaces = 0 |
| Label(self.interfaceFrame, text="External Interface:").grid(row=0, column=0, sticky=E) |
| self.b = Button( self.interfaceFrame, text='Add', command=self.addInterface) |
| self.b.grid(row=0, column=1) |
| |
| self.interfaceFrame = VerticalScrolledTable(self.interfaceFrame, rows=0, columns=1, title='External Interfaces') |
| self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) |
| self.tableFrame = self.interfaceFrame.interior |
| self.tableFrame.addRow(value=['Interface Name'], readonly=True) |
| |
| # Add defined interfaces |
| externalInterfaces = [] |
| if 'externalInterfaces' in self.prefValues: |
| externalInterfaces = self.prefValues['externalInterfaces'] |
| |
| for externalInterface in externalInterfaces: |
| self.tableFrame.addRow(value=[externalInterface]) |
| |
| ### TAB 3 |
| # VLAN Interfaces |
| self.vlanInterfaces = 0 |
| Label(self.vlanFrame, text="VLAN Interface:").grid(row=0, column=0, sticky=E) |
| self.vlanButton = Button( self.vlanFrame, text='Add', command=self.addVlanInterface) |
| self.vlanButton.grid(row=0, column=1) |
| |
| self.vlanFrame = VerticalScrolledTable(self.vlanFrame, rows=0, columns=2, title='VLAN Interfaces') |
| self.vlanFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) |
| self.vlanTableFrame = self.vlanFrame.interior |
| self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True) |
| |
| vlanInterfaces = [] |
| if 'vlanInterfaces' in self.prefValues: |
| vlanInterfaces = self.prefValues['vlanInterfaces'] |
| for vlanInterface in vlanInterfaces: |
| self.vlanTableFrame.addRow(value=vlanInterface) |
| |
| ### TAB 4 |
| # Private Directories |
| self.privateDirectories = 0 |
| Label(self.mountFrame, text="Private Directory:").grid(row=0, column=0, sticky=E) |
| self.mountButton = Button( self.mountFrame, text='Add', command=self.addDirectory) |
| self.mountButton.grid(row=0, column=1) |
| |
| self.mountFrame = VerticalScrolledTable(self.mountFrame, rows=0, columns=2, title='Directories') |
| self.mountFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) |
| self.mountTableFrame = self.mountFrame.interior |
| self.mountTableFrame.addRow(value=['Mount','Persistent Directory'], readonly=True) |
| |
| directoryList = [] |
| if 'privateDirectory' in self.prefValues: |
| directoryList = self.prefValues['privateDirectory'] |
| for privateDir in directoryList: |
| if isinstance( privateDir, tuple ): |
| self.mountTableFrame.addRow(value=privateDir) |
| else: |
| self.mountTableFrame.addRow(value=[privateDir,'']) |
| |
| |
| def addDirectory( self ): |
| self.mountTableFrame.addRow() |
| |
| def addVlanInterface( self ): |
| self.vlanTableFrame.addRow() |
| |
| def addInterface( self ): |
| self.tableFrame.addRow() |
| |
| def apply(self): |
| externalInterfaces = [] |
| for row in range(self.tableFrame.rows): |
| if (len(self.tableFrame.get(row, 0)) > 0 and |
| row > 0): |
| externalInterfaces.append(self.tableFrame.get(row, 0)) |
| vlanInterfaces = [] |
| for row in range(self.vlanTableFrame.rows): |
| if (len(self.vlanTableFrame.get(row, 0)) > 0 and |
| len(self.vlanTableFrame.get(row, 1)) > 0 and |
| row > 0): |
| vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)]) |
| privateDirectories = [] |
| for row in range(self.mountTableFrame.rows): |
| if (len(self.mountTableFrame.get(row, 0)) > 0 and row > 0): |
| if(len(self.mountTableFrame.get(row, 1)) > 0): |
| privateDirectories.append((self.mountTableFrame.get(row, 0), self.mountTableFrame.get(row, 1))) |
| else: |
| privateDirectories.append(self.mountTableFrame.get(row, 0)) |
| |
| results = {'cpu': self.cpuEntry.get(), |
| 'cores':self.coreEntry.get(), |
| 'sched':self.schedVar.get(), |
| 'hostname':self.hostnameEntry.get(), |
| 'ip':self.ipEntry.get(), |
| 'defaultRoute':self.routeEntry.get(), |
| 'startCommand':self.startEntry.get(), |
| 'stopCommand':self.stopEntry.get(), |
| 'privateDirectory':privateDirectories, |
| 'externalInterfaces':externalInterfaces, |
| 'vlanInterfaces':vlanInterfaces} |
| self.result = results |
| |
| class VerticalScrolledTable(LabelFrame): |
| """A pure Tkinter scrollable frame that actually works! |
| |
| * Use the 'interior' attribute to place widgets inside the scrollable frame |
| * Construct and pack/place/grid normally |
| * This frame only allows vertical scrolling |
| |
| """ |
| def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw): |
| LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw) |
| |
| # create a canvas object and a vertical scrollbar for scrolling it |
| vscrollbar = Scrollbar(self, orient=VERTICAL) |
| vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) |
| canvas = Canvas(self, bd=0, highlightthickness=0, |
| yscrollcommand=vscrollbar.set) |
| canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) |
| vscrollbar.config(command=canvas.yview) |
| |
| # reset the view |
| canvas.xview_moveto(0) |
| canvas.yview_moveto(0) |
| |
| # create a frame inside the canvas which will be scrolled with it |
| self.interior = interior = TableFrame(canvas, rows=rows, columns=columns) |
| interior_id = canvas.create_window(0, 0, window=interior, |
| anchor=NW) |
| |
| # track changes to the canvas and frame width and sync them, |
| # also updating the scrollbar |
| def _configure_interior(event): |
| # update the scrollbars to match the size of the inner frame |
| size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) |
| canvas.config(scrollregion="0 0 %s %s" % size) |
| if interior.winfo_reqwidth() != canvas.winfo_width(): |
| # update the canvas's width to fit the inner frame |
| canvas.config(width=interior.winfo_reqwidth()) |
| interior.bind('<Configure>', _configure_interior) |
| |
| def _configure_canvas(event): |
| if interior.winfo_reqwidth() != canvas.winfo_width(): |
| # update the inner frame's width to fill the canvas |
| canvas.itemconfigure(interior_id, width=canvas.winfo_width()) |
| canvas.bind('<Configure>', _configure_canvas) |
| |
| return |
| |
| class TableFrame(Frame): |
| def __init__(self, parent, rows=2, columns=2): |
| |
| Frame.__init__(self, parent, background="black") |
| self._widgets = [] |
| self.rows = rows |
| self.columns = columns |
| for row in range(rows): |
| current_row = [] |
| for column in range(columns): |
| label = Entry(self, borderwidth=0) |
| label.grid(row=row, column=column, sticky="wens", padx=1, pady=1) |
| current_row.append(label) |
| self._widgets.append(current_row) |
| |
| def set(self, row, column, value): |
| widget = self._widgets[row][column] |
| widget.insert(0, value) |
| |
| def get(self, row, column): |
| widget = self._widgets[row][column] |
| return widget.get() |
| |
| def addRow( self, value=None, readonly=False ): |
| #print "Adding row " + str(self.rows +1) |
| current_row = [] |
| for column in range(self.columns): |
| label = Entry(self, borderwidth=0) |
| label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1) |
| if value is not None: |
| label.insert(0, value[column]) |
| if (readonly == True): |
| label.configure(state='readonly') |
| current_row.append(label) |
| self._widgets.append(current_row) |
| self.update_idletasks() |
| self.rows += 1 |
| |
| class LinkDialog(tkSimpleDialog.Dialog): |
| |
| def __init__(self, parent, title, linkDefaults): |
| |
| self.linkValues = linkDefaults |
| |
| tkSimpleDialog.Dialog.__init__(self, parent, title) |
| |
| def body(self, master): |
| |
| self.var = StringVar(master) |
| Label(master, text="Bandwidth:").grid(row=0, sticky=E) |
| self.e1 = Entry(master) |
| self.e1.grid(row=0, column=1) |
| Label(master, text="Mbit").grid(row=0, column=2, sticky=W) |
| if 'bw' in self.linkValues: |
| self.e1.insert(0,str(self.linkValues['bw'])) |
| |
| Label(master, text="Delay:").grid(row=1, sticky=E) |
| self.e2 = Entry(master) |
| self.e2.grid(row=1, column=1) |
| if 'delay' in self.linkValues: |
| self.e2.insert(0, self.linkValues['delay']) |
| |
| Label(master, text="Loss:").grid(row=2, sticky=E) |
| self.e3 = Entry(master) |
| self.e3.grid(row=2, column=1) |
| Label(master, text="%").grid(row=2, column=2, sticky=W) |
| if 'loss' in self.linkValues: |
| self.e3.insert(0, str(self.linkValues['loss'])) |
| |
| Label(master, text="Max Queue size:").grid(row=3, sticky=E) |
| self.e4 = Entry(master) |
| self.e4.grid(row=3, column=1) |
| if 'max_queue_size' in self.linkValues: |
| self.e4.insert(0, str(self.linkValues['max_queue_size'])) |
| |
| Label(master, text="Jitter:").grid(row=4, sticky=E) |
| self.e5 = Entry(master) |
| self.e5.grid(row=4, column=1) |
| if 'jitter' in self.linkValues: |
| self.e5.insert(0, self.linkValues['jitter']) |
| |
| Label(master, text="Speedup:").grid(row=5, sticky=E) |
| self.e6 = Entry(master) |
| self.e6.grid(row=5, column=1) |
| if 'speedup' in self.linkValues: |
| self.e6.insert(0, str(self.linkValues['speedup'])) |
| |
| return self.e1 # initial focus |
| |
| def apply(self): |
| self.result = {} |
| if (len(self.e1.get()) > 0): |
| self.result['bw'] = int(self.e1.get()) |
| if (len(self.e2.get()) > 0): |
| self.result['delay'] = self.e2.get() |
| if (len(self.e3.get()) > 0): |
| self.result['loss'] = int(self.e3.get()) |
| if (len(self.e4.get()) > 0): |
| self.result['max_queue_size'] = int(self.e4.get()) |
| if (len(self.e5.get()) > 0): |
| self.result['jitter'] = self.e5.get() |
| if (len(self.e6.get()) > 0): |
| self.result['speedup'] = int(self.e6.get()) |
| |
| class ToolTip(object): |
| |
| def __init__(self, widget): |
| self.widget = widget |
| self.tipwindow = None |
| self.id = None |
| self.x = self.y = 0 |
| |
| def showtip(self, text): |
| "Display text in tooltip window" |
| self.text = text |
| if self.tipwindow or not self.text: |
| return |
| x, y, cx, cy = self.widget.bbox("insert") |
| x = x + self.widget.winfo_rootx() + 27 |
| y = y + cy + self.widget.winfo_rooty() +27 |
| self.tipwindow = tw = Toplevel(self.widget) |
| tw.wm_overrideredirect(1) |
| tw.wm_geometry("+%d+%d" % (x, y)) |
| try: |
| # For Mac OS |
| tw.tk.call("::tk::unsupported::MacWindowStyle", |
| "style", tw._w, |
| "help", "noActivates") |
| except TclError: |
| pass |
| label = Label(tw, text=self.text, justify=LEFT, |
| background="#ffffe0", relief=SOLID, borderwidth=1, |
| font=("tahoma", "8", "normal")) |
| label.pack(ipadx=1) |
| |
| def hidetip(self): |
| tw = self.tipwindow |
| self.tipwindow = None |
| if tw: |
| tw.destroy() |
| |
| class MiniEdit( Frame ): |
| |
| "A simple network editor for MiniCCNx." |
| |
| def __init__( self, parent=None, cheight=600, cwidth=1000, template_file='miniccnx.conf' ): |
| |
| #self.defaultIpBase='10.0.0.0/8' |
| |
| self.nflowDefaults = {'nflowTarget':'', |
| 'nflowTimeout':'600', |
| 'nflowAddId':'0'} |
| self.sflowDefaults = {'sflowTarget':'', |
| 'sflowSampling':'400', |
| 'sflowHeader':'128', |
| 'sflowPolling':'30'} |
| |
| self.appPrefs={ |
| #"ipBase": self.defaultIpBase, |
| "startCLI": "0", |
| "terminalType": 'xterm', |
| "switchType": 'ovs', |
| "dpctl": '', |
| 'sflow':self.sflowDefaults, |
| 'netflow':self.nflowDefaults, |
| 'openFlowVersions':{'ovsOf10':'1', |
| 'ovsOf11':'0', |
| 'ovsOf12':'0', |
| 'ovsOf13':'0'} |
| |
| } |
| |
| self.template_file = template_file |
| |
| Frame.__init__( self, parent ) |
| self.action = None |
| self.appName = 'MiniccnxEdit' |
| self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" ) |
| |
| # Style |
| self.font = ( 'Geneva', 9 ) |
| self.smallFont = ( 'Geneva', 7 ) |
| self.bg = 'white' |
| |
| # Title |
| self.top = self.winfo_toplevel() |
| self.top.title( self.appName ) |
| |
| # Menu bar |
| self.createMenubar() |
| |
| # Editing canvas |
| self.cheight, self.cwidth = cheight, cwidth |
| self.cframe, self.canvas = self.createCanvas() |
| |
| # Toolbar |
| self.controllers = {} |
| |
| # Toolbar |
| self.images = miniEditImages() |
| self.buttons = {} |
| self.active = None |
| self.tools = ( 'Select', 'Host', 'LegacyRouter', 'NetLink' ) |
| self.customColors = { 'LegacyRouter': 'darkGreen', 'Host': 'blue' } |
| self.toolbar = self.createToolbar() |
| |
| # Layout |
| self.toolbar.grid( column=0, row=0, sticky='nsew') |
| self.cframe.grid( column=1, row=0 ) |
| self.columnconfigure( 1, weight=1 ) |
| self.rowconfigure( 0, weight=1 ) |
| self.pack( expand=True, fill='both' ) |
| |
| # About box |
| self.aboutBox = None |
| |
| # Initialize node data |
| self.nodeBindings = self.createNodeBindings() |
| self.nodePrefixes = { 'LegacyRouter': 'r', 'Host': 'h'} |
| self.widgetToItem = {} |
| self.itemToWidget = {} |
| |
| # Initialize link tool |
| self.link = self.linkWidget = None |
| |
| # Selection support |
| self.selection = None |
| |
| # Keyboard bindings |
| self.bind( '<Control-q>', lambda event: self.quit() ) |
| self.bind( '<KeyPress-Delete>', self.deleteSelection ) |
| self.bind( '<KeyPress-BackSpace>', self.deleteSelection ) |
| self.focus() |
| |
| #Mouse bindings |
| self.bind( '<Button-1>', lambda event: self.clearPopups ) |
| |
| self.hostPopup = Menu(self.top, tearoff=0) |
| self.hostPopup.add_command(label='Host Options', font=self.font) |
| self.hostPopup.add_separator() |
| self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails ) |
| |
| self.legacyRouterPopup = Menu(self.top, tearoff=0) |
| self.legacyRouterPopup.add_command(label='Router Options', font=self.font) |
| |
| self.linkPopup = Menu(self.top, tearoff=0) |
| self.linkPopup.add_command(label='Link Options', font=self.font) |
| #self.linkPopup.add_separator() |
| #self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails ) |
| |
| # Event handling initalization |
| self.linkx = self.linky = self.linkItem = None |
| self.lastSelection = None |
| |
| # Model initialization |
| self.links = {} |
| self.hostOpts = {} |
| self.switchOpts = {} |
| self.hostCount = 0 |
| self.switchCount = 0 |
| self.controllerCount = 0 |
| self.net = None |
| |
| # Close window gracefully |
| Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit ) |
| |
| def quit( self ): |
| "Stop our network, if any, then quit." |
| self.stop() |
| Frame.quit( self ) |
| |
| def createMenubar( self ): # MODIFICADO - OK |
| "Create our menu bar." |
| |
| font = self.font |
| |
| mbar = Menu( self.top, font=font ) |
| self.top.configure( menu=mbar ) |
| |
| |
| fileMenu = Menu( mbar, tearoff=False ) |
| mbar.add_cascade( label="File", font=font, menu=fileMenu ) |
| fileMenu.add_command( label="New", font=font, command=self.newTopology ) |
| fileMenu.add_command( label="Open", font=font, command=self.loadTopology ) |
| fileMenu.add_command( label="Save", font=font, command=self.saveTopology ) |
| #fileMenu.add_command( label="Export Level 2 Script", font=font, command=self.exportScript ) |
| fileMenu.add_separator() |
| fileMenu.add_command( label='Quit', command=self.quit, font=font ) |
| |
| #editMenu.add_command( label="Preferences", font=font, command=self.prefDetails) |
| |
| #runMenu = Menu( mbar, tearoff=False ) |
| #mbar.add_cascade( label="Run", font=font, menu=runMenu ) |
| #runMenu.add_command( label="Run", font=font, command=self.doRun ) |
| #runMenu.add_command( label="Stop", font=font, command=self.doStop ) |
| #fileMenu.add_separator() |
| #runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow ) |
| #runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal ) |
| |
| editMenu = Menu( mbar, tearoff=False ) |
| mbar.add_cascade( label="Edit", font=font, menu=editMenu ) |
| editMenu.add_command( label="Cut", font=font, |
| command=lambda: self.deleteSelection( None ) ) |
| |
| # Application menu |
| appMenu = Menu( mbar, tearoff=False ) |
| mbar.add_cascade( label=self.appName, font=font, menu=appMenu ) |
| appMenu.add_command( label='About Mini-CCNx', command=self.about, |
| font=font) |
| #appMenu.add_separator() |
| #appMenu.add_command( label='Quit', command=self.quit, font=font ) |
| |
| # Canvas - TUDO IGUAL - OK |
| |
| def createCanvas( self ): |
| "Create and return our scrolling canvas frame." |
| f = Frame( self ) |
| |
| canvas = Canvas( f, width=self.cwidth, height=self.cheight, |
| bg=self.bg ) |
| |
| # Scroll bars |
| xbar = Scrollbar( f, orient='horizontal', command=canvas.xview ) |
| ybar = Scrollbar( f, orient='vertical', command=canvas.yview ) |
| canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set ) |
| |
| # Resize box |
| resize = Label( f, bg='white' ) |
| |
| # Layout |
| canvas.grid( row=0, column=1, sticky='nsew') |
| ybar.grid( row=0, column=2, sticky='ns') |
| xbar.grid( row=1, column=1, sticky='ew' ) |
| resize.grid( row=1, column=2, sticky='nsew' ) |
| |
| # Resize behavior |
| f.rowconfigure( 0, weight=1 ) |
| f.columnconfigure( 1, weight=1 ) |
| f.grid( row=0, column=0, sticky='nsew' ) |
| f.bind( '<Configure>', lambda event: self.updateScrollRegion() ) |
| |
| # Mouse bindings |
| canvas.bind( '<ButtonPress-1>', self.clickCanvas ) |
| canvas.bind( '<B1-Motion>', self.dragCanvas ) |
| canvas.bind( '<ButtonRelease-1>', self.releaseCanvas ) |
| |
| return f, canvas |
| |
| def updateScrollRegion( self ): |
| "Update canvas scroll region to hold everything." |
| bbox = self.canvas.bbox( 'all' ) |
| if bbox is not None: |
| self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], |
| bbox[ 3 ] ) ) |
| |
| def canvasx( self, x_root ): |
| "Convert root x coordinate to canvas coordinate." |
| c = self.canvas |
| return c.canvasx( x_root ) - c.winfo_rootx() |
| |
| def canvasy( self, y_root ): |
| "Convert root y coordinate to canvas coordinate." |
| c = self.canvas |
| return c.canvasy( y_root ) - c.winfo_rooty() |
| |
| # Toolbar |
| |
| def activate( self, toolName ): #IGUAL - OK |
| "Activate a tool and press its button." |
| # Adjust button appearance |
| if self.active: |
| self.buttons[ self.active ].configure( relief='raised' ) |
| self.buttons[ toolName ].configure( relief='sunken' ) |
| # Activate dynamic bindings |
| self.active = toolName |
| |
| |
| def createToolTip(self, widget, text): #NOVA - CRIA HINTS E TIPS |
| toolTip = ToolTip(widget) |
| def enter(event): |
| toolTip.showtip(text) |
| def leave(event): |
| toolTip.hidetip() |
| widget.bind('<Enter>', enter) |
| widget.bind('<Leave>', leave) |
| |
| def createToolbar( self ): #MODIFICADO - OK |
| "Create and return our toolbar frame." |
| |
| toolbar = Frame( self ) |
| |
| # Tools |
| for tool in self.tools: |
| cmd = ( lambda t=tool: self.activate( t ) ) |
| b = Button( toolbar, text=tool, font=self.smallFont, command=cmd) |
| if tool in self.images: |
| b.config( height=35, image=self.images[ tool ] ) |
| self.createToolTip(b, str(tool)) |
| # b.config( compound='top' ) |
| b.pack( fill='x' ) |
| self.buttons[ tool ] = b |
| self.activate( self.tools[ 0 ] ) |
| |
| # Spacer |
| Label( toolbar, text='' ).pack() |
| |
| # Commands |
| #for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]: |
| # doCmd = getattr( self, 'do' + cmd ) |
| # b = Button( toolbar, text=cmd, font=self.smallFont, |
| # fg=color, command=doCmd ) |
| # b.pack( fill='x', side='bottom' ) |
| |
| # abaixo copiado Mini-CCNx para criar botao Generate |
| |
| for cmd, color in [ ( 'Generate', 'darkGreen' ) ]: |
| doCmd = getattr( self, 'do' + cmd ) |
| b = Button( toolbar, text=cmd, font=self.smallFont, |
| fg=color, command=doCmd ) |
| b.pack( fill='x', side='bottom' ) |
| |
| return toolbar |
| |
| def doGenerate( self ): #COPIA Mini-CCNx - GERA TEMPLATE |
| "Generate template." |
| self.activate( 'Select' ) |
| for tool in self.tools: |
| self.buttons[ tool ].config( state='disabled' ) |
| |
| self.buildTemplate() |
| |
| for tool in self.tools: |
| self.buttons[ tool ].config( state='normal' ) |
| |
| #def doRun( self ): |
| # "Run command." |
| # self.activate( 'Select' ) |
| # for tool in self.tools: |
| # self.buttons[ tool ].config( state='disabled' ) |
| # self.start() |
| |
| def doStop( self ): |
| "Stop command." |
| self.stop() |
| for tool in self.tools: |
| self.buttons[ tool ].config( state='normal' ) |
| |
| def buildTemplate( self ): #COPIA Mini-CCNx para criar Template |
| "Generate template" |
| |
| template = open(self.template_file, 'w') |
| |
| # hosts |
| template.write('[hosts]\n') |
| for widget in self.widgetToItem: |
| name = widget[ 'text' ] |
| tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| if 'Host' in tags: |
| template.write(name + ':\n') |
| |
| # switches/routers |
| template.write('[routers]\n') |
| for router in self.switchOpts.values(): |
| #print router |
| hostname=router['hostname'] |
| nodetype=router['switchType'] |
| nodenum=router['nodeNum'] |
| |
| template.write(hostname + ':\n') |
| |
| #name = widget[ 'text' ] |
| #tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| #if 'Switch' in tags: |
| #template.write(name + ':\n') |
| |
| # Make links |
| template.write('[links]\n') |
| for link in self.links.values(): |
| dst=link['dest'] |
| src=link['src'] |
| linkopts=link['linkOpts'] |
| linktype=link['type'] |
| |
| #print linkopts |
| #print linktype |
| |
| srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| template.write(srcName + ':' + dstName + '\n') |
| |
| template.close() |
| |
| def addNode( self, node, nodeNum, x, y, name=None): |
| "Add a new node to our canvas." |
| if 'Switch' == node: |
| self.switchCount += 1 |
| if 'Host' == node: |
| self.hostCount += 1 |
| if 'Controller' == node: |
| self.controllerCount += 1 |
| if name is None: |
| name = self.nodePrefixes[ node ] + nodeNum |
| self.addNamedNode(node, name, x, y) |
| |
| def addNamedNode( self, node, name, x, y): |
| "Add a new node to our canvas." |
| c = self.canvas |
| icon = self.nodeIcon( node, name ) |
| item = self.canvas.create_window( x, y, anchor='c', window=icon, |
| tags=node ) |
| self.widgetToItem[ icon ] = item |
| self.itemToWidget[ item ] = icon |
| icon.links = {} |
| |
| def convertJsonUnicode(self, input): |
| "Some part of Mininet don't like Unicode" |
| if isinstance(input, dict): |
| return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in input.iteritems()} |
| elif isinstance(input, list): |
| return [self.convertJsonUnicode(element) for element in input] |
| elif isinstance(input, unicode): |
| return input.encode('utf-8') |
| else: |
| return input |
| |
| def loadTopology( self ): |
| "Load command." |
| c = self.canvas |
| |
| myFormats = [ |
| ('Miniccnx Topology','*.mnccnx'), |
| ('All Files','*'), |
| ] |
| f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb') |
| if f == None: |
| return |
| self.newTopology() |
| loadedTopology = self.convertJsonUnicode(json.load(f)) |
| |
| # Load application preferences |
| if 'application' in loadedTopology: |
| self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items()) |
| if "ovsOf10" not in self.appPrefs["openFlowVersions"]: |
| self.appPrefs["openFlowVersions"]["ovsOf10"] = '0' |
| if "ovsOf11" not in self.appPrefs["openFlowVersions"]: |
| self.appPrefs["openFlowVersions"]["ovsOf11"] = '0' |
| if "ovsOf12" not in self.appPrefs["openFlowVersions"]: |
| self.appPrefs["openFlowVersions"]["ovsOf12"] = '0' |
| if "ovsOf13" not in self.appPrefs["openFlowVersions"]: |
| self.appPrefs["openFlowVersions"]["ovsOf13"] = '0' |
| if "sflow" not in self.appPrefs: |
| self.appPrefs["sflow"] = self.sflowDefaults |
| if "netflow" not in self.appPrefs: |
| self.appPrefs["netflow"] = self.nflowDefaults |
| |
| # Load hosts |
| hosts = loadedTopology['hosts'] |
| for host in hosts: |
| nodeNum = host['number'] |
| hostname = 'h'+nodeNum |
| if 'hostname' in host['opts']: |
| hostname = host['opts']['hostname'] |
| else: |
| host['opts']['hostname'] = hostname |
| if 'nodeNum' not in host['opts']: |
| host['opts']['nodeNum'] = int(nodeNum) |
| x = host['x'] |
| y = host['y'] |
| self.addNode('Host', nodeNum, float(x), float(y), name=hostname) |
| |
| # Fix JSON converting tuple to list when saving |
| if 'privateDirectory' in host['opts']: |
| newDirList = [] |
| for privateDir in host['opts']['privateDirectory']: |
| if isinstance( privateDir, list ): |
| newDirList.append((privateDir[0],privateDir[1])) |
| else: |
| newDirList.append(privateDir) |
| host['opts']['privateDirectory'] = newDirList |
| self.hostOpts[hostname] = host['opts'] |
| icon = self.findWidgetByName(hostname) |
| icon.bind('<Button-3>', self.do_hostPopup ) |
| |
| # Load switches |
| switches = loadedTopology['switches'] |
| for switch in switches: |
| nodeNum = switch['number'] |
| hostname = 's'+nodeNum |
| if 'switchType' not in switch['opts']: |
| switch['opts']['switchType'] = 'default' |
| if 'hostname' in switch['opts']: |
| hostname = switch['opts']['hostname'] |
| else: |
| switch['opts']['hostname'] = hostname |
| if 'nodeNum' not in switch['opts']: |
| switch['opts']['nodeNum'] = int(nodeNum) |
| x = switch['x'] |
| y = switch['y'] |
| if switch['opts']['switchType'] == "legacyRouter": |
| self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname) |
| icon = self.findWidgetByName(hostname) |
| icon.bind('<Button-3>', self.do_legacyRouterPopup ) |
| self.switchOpts[hostname] = switch['opts'] |
| |
| # Load links |
| links = loadedTopology['links'] |
| for link in links: |
| srcNode = link['src'] |
| src = self.findWidgetByName(srcNode) |
| sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) |
| |
| destNode = link['dest'] |
| dest = self.findWidgetByName(destNode) |
| dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) |
| |
| self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, |
| fill='blue', tag='link' ) |
| c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) |
| self.addLink( src, dest, linkopts=link['opts'] ) |
| self.createDataLinkBindings() |
| self.link = self.linkWidget = None |
| |
| f.close |
| |
| def findWidgetByName( self, name ): |
| for widget in self.widgetToItem: |
| if name == widget[ 'text' ]: |
| return widget |
| |
| def newTopology( self ): |
| "New command." |
| for widget in self.widgetToItem.keys(): |
| self.deleteItem( self.widgetToItem[ widget ] ) |
| self.hostCount = 0 |
| self.switchCount = 0 |
| self.links = {} |
| self.hostOpts = {} |
| self.switchOpts = {} |
| #self.appPrefs["ipBase"]= self.defaultIpBase |
| |
| def saveTopology( self ): |
| "Save command." |
| myFormats = [ |
| ('Miniccnx Topology','*.mnccnx'), |
| ('All Files','*'), |
| ] |
| |
| savingDictionary = {} |
| fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...") |
| if len(fileName ) > 0: |
| # Save Application preferences |
| savingDictionary['version'] = '2' |
| |
| # Save Switches and Hosts |
| hostsToSave = [] |
| switchesToSave = [] |
| controllersToSave = [] |
| for widget in self.widgetToItem: |
| name = widget[ 'text' ] |
| tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) |
| if 'LegacyRouter' in tags: |
| nodeNum = self.switchOpts[name]['nodeNum'] |
| nodeToSave = {'number':str(nodeNum), |
| 'x':str(x1), |
| 'y':str(y1), |
| 'opts':self.switchOpts[name] } |
| switchesToSave.append(nodeToSave) |
| elif 'Host' in tags: |
| nodeNum = self.hostOpts[name]['nodeNum'] |
| nodeToSave = {'number':str(nodeNum), |
| 'x':str(x1), |
| 'y':str(y1), |
| 'opts':self.hostOpts[name] } |
| hostsToSave.append(nodeToSave) |
| else: |
| raise Exception( "Cannot create mystery node: " + name ) |
| savingDictionary['hosts'] = hostsToSave |
| savingDictionary['switches'] = switchesToSave |
| savingDictionary['controllers'] = controllersToSave |
| |
| # Save Links |
| linksToSave = [] |
| for link in self.links.values(): |
| src = link['src'] |
| dst = link['dest'] |
| linkopts = link['linkOpts'] |
| |
| srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| linkToSave = {'src':srcName, |
| 'dest':dstName, |
| 'opts':linkopts} |
| if link['type'] == 'data': |
| linksToSave.append(linkToSave) |
| savingDictionary['links'] = linksToSave |
| |
| # Save Application preferences |
| savingDictionary['application'] = self.appPrefs |
| |
| try: |
| f = open(fileName, 'wb') |
| f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': '))) |
| except Exception as er: |
| print er |
| finally: |
| f.close() |
| |
| # Generic canvas handler |
| # |
| # We could have used bindtags, as in nodeIcon, but |
| # the dynamic approach used here |
| # may actually require less code. In any case, it's an |
| # interesting introspection-based alternative to bindtags. |
| |
| def canvasHandle( self, eventName, event ): |
| "Generic canvas event handler" |
| if self.active is None: |
| return |
| toolName = self.active |
| handler = getattr( self, eventName + toolName, None ) |
| if handler is not None: |
| handler( event ) |
| |
| def clickCanvas( self, event ): |
| "Canvas click handler." |
| self.canvasHandle( 'click', event ) |
| |
| def dragCanvas( self, event ): |
| "Canvas drag handler." |
| self.canvasHandle( 'drag', event ) |
| |
| def releaseCanvas( self, event ): |
| "Canvas mouse up handler." |
| self.canvasHandle( 'release', event ) |
| |
| # Currently the only items we can select directly are |
| # links. Nodes are handled by bindings in the node icon. |
| |
| def findItem( self, x, y ): |
| "Find items at a location in our canvas." |
| items = self.canvas.find_overlapping( x, y, x, y ) |
| if len( items ) == 0: |
| return None |
| else: |
| return items[ 0 ] |
| |
| # Canvas bindings for Select, Host, Switch and Link tools |
| |
| def clickSelect( self, event ): |
| "Select an item." |
| self.selectItem( self.findItem( event.x, event.y ) ) |
| |
| def deleteItem( self, item ): |
| "Delete an item." |
| # Don't delete while network is running |
| if self.buttons[ 'Select' ][ 'state' ] == 'disabled': |
| return |
| # Delete from model |
| if item in self.links: |
| self.deleteLink( item ) |
| if item in self.itemToWidget: |
| self.deleteNode( item ) |
| # Delete from view |
| self.canvas.delete( item ) |
| |
| def deleteSelection( self, _event ): |
| "Delete the selected item." |
| if self.selection is not None: |
| self.deleteItem( self.selection ) |
| self.selectItem( None ) |
| |
| def clearPopups(self): |
| print 'Entrou funcao clear_popups' |
| |
| if isHostPopup == True: |
| print 'Hostpopup = true' |
| self.hostPopup.unpost |
| isHostPopup = False |
| #if isRouterPopup == True |
| #if isLinkPopup == True |
| |
| def nodeIcon( self, node, name ): |
| "Create a new node icon." |
| icon = Button( self.canvas, image=self.images[ node ], |
| text=name, compound='top' ) |
| # Unfortunately bindtags wants a tuple |
| bindtags = [ str( self.nodeBindings ) ] |
| bindtags += list( icon.bindtags() ) |
| icon.bindtags( tuple( bindtags ) ) |
| return icon |
| |
| def newNode( self, node, event ): |
| "Add a new node to our canvas." |
| c = self.canvas |
| x, y = c.canvasx( event.x ), c.canvasy( event.y ) |
| name = self.nodePrefixes[ node ] |
| if 'Switch' == node: |
| self.switchCount += 1 |
| name = self.nodePrefixes[ node ] + str( self.switchCount ) |
| self.switchOpts[name] = {} |
| self.switchOpts[name]['nodeNum']=self.switchCount |
| self.switchOpts[name]['hostname']=name |
| self.switchOpts[name]['switchType']='default' |
| self.switchOpts[name]['controllers']=[] |
| if 'LegacyRouter' == node: |
| self.switchCount += 1 |
| name = self.nodePrefixes[ node ] + str( self.switchCount ) |
| self.switchOpts[name] = {} |
| self.switchOpts[name]['nodeNum']=self.switchCount |
| self.switchOpts[name]['hostname']=name |
| self.switchOpts[name]['switchType']='legacyRouter' |
| if 'LegacySwitch' == node: |
| self.switchCount += 1 |
| name = self.nodePrefixes[ node ] + str( self.switchCount ) |
| self.switchOpts[name] = {} |
| self.switchOpts[name]['nodeNum']=self.switchCount |
| self.switchOpts[name]['hostname']=name |
| self.switchOpts[name]['switchType']='legacySwitch' |
| self.switchOpts[name]['controllers']=[] |
| if 'Host' == node: |
| self.hostCount += 1 |
| name = self.nodePrefixes[ node ] + str( self.hostCount ) |
| self.hostOpts[name] = {'sched':'host'} |
| self.hostOpts[name]['nodeNum']=self.hostCount |
| self.hostOpts[name]['hostname']=name |
| |
| icon = self.nodeIcon( node, name ) |
| item = self.canvas.create_window( x, y, anchor='c', window=icon, |
| tags=node ) |
| self.widgetToItem[ icon ] = item |
| self.itemToWidget[ item ] = icon |
| self.selectItem( item ) |
| icon.links = {} |
| if 'LegacyRouter' == node: |
| icon.bind('<Button-3>', self.do_legacyRouterPopup ) |
| if 'Host' == node: |
| icon.bind('<Button-3>', self.do_hostPopup ) |
| |
| def clickHost( self, event ): |
| "Add a new host to our canvas." |
| self.newNode( 'Host', event ) |
| |
| def clickLegacyRouter( self, event ): |
| "Add a new switch to our canvas." |
| self.newNode( 'LegacyRouter', event ) |
| |
| def dragNetLink( self, event ): |
| "Drag a link's endpoint to another node." |
| if self.link is None: |
| return |
| # Since drag starts in widget, we use root coords |
| x = self.canvasx( event.x_root ) |
| y = self.canvasy( event.y_root ) |
| c = self.canvas |
| c.coords( self.link, self.linkx, self.linky, x, y ) |
| |
| def releaseNetLink( self, _event ): |
| "Give up on the current link." |
| if self.link is not None: |
| self.canvas.delete( self.link ) |
| self.linkWidget = self.linkItem = self.link = None |
| |
| # Generic node handlers |
| |
| def createNodeBindings( self ): |
| "Create a set of bindings for nodes." |
| bindings = { |
| '<ButtonPress-1>': self.clickNode, |
| '<B1-Motion>': self.dragNode, |
| '<ButtonRelease-1>': self.releaseNode, |
| '<Enter>': self.enterNode, |
| '<Leave>': self.leaveNode |
| } |
| l = Label() # lightweight-ish owner for bindings |
| for event, binding in bindings.items(): |
| l.bind( event, binding ) |
| return l |
| |
| def selectItem( self, item ): |
| "Select an item and remember old selection." |
| self.lastSelection = self.selection |
| self.selection = item |
| |
| def enterNode( self, event ): |
| "Select node on entry." |
| self.selectNode( event ) |
| |
| def leaveNode( self, _event ): |
| "Restore old selection on exit." |
| self.selectItem( self.lastSelection ) |
| |
| def clickNode( self, event ): |
| "Node click handler." |
| if self.active is 'NetLink': |
| self.startLink( event ) |
| else: |
| self.selectNode( event ) |
| return 'break' |
| |
| def dragNode( self, event ): |
| "Node drag handler." |
| if self.active is 'NetLink': |
| self.dragNetLink( event ) |
| else: |
| self.dragNodeAround( event ) |
| |
| def releaseNode( self, event ): |
| "Node release handler." |
| if self.active is 'NetLink': |
| self.finishLink( event ) |
| |
| # Specific node handlers |
| |
| def selectNode( self, event ): |
| "Select the node that was clicked on." |
| item = self.widgetToItem.get( event.widget, None ) |
| self.selectItem( item ) |
| |
| def dragNodeAround( self, event ): |
| "Drag a node around on the canvas." |
| c = self.canvas |
| # Convert global to local coordinates; |
| # Necessary since x, y are widget-relative |
| x = self.canvasx( event.x_root ) |
| y = self.canvasy( event.y_root ) |
| w = event.widget |
| # Adjust node position |
| item = self.widgetToItem[ w ] |
| c.coords( item, x, y ) |
| # Adjust link positions |
| for dest in w.links: |
| link = w.links[ dest ] |
| item = self.widgetToItem[ dest ] |
| x1, y1 = c.coords( item ) |
| c.coords( link, x, y, x1, y1 ) |
| self.updateScrollRegion() |
| |
| def createControlLinkBindings( self ): |
| "Create a set of bindings for nodes." |
| # Link bindings |
| # Selection still needs a bit of work overall |
| # Callbacks ignore event |
| |
| def select( _event, link=self.link ): |
| "Select item on mouse entry." |
| self.selectItem( link ) |
| |
| def highlight( _event, link=self.link ): |
| "Highlight item on mouse entry." |
| self.selectItem( link ) |
| self.canvas.itemconfig( link, fill='green' ) |
| |
| def unhighlight( _event, link=self.link ): |
| "Unhighlight item on mouse exit." |
| self.canvas.itemconfig( link, fill='red' ) |
| #self.selectItem( None ) |
| |
| self.canvas.tag_bind( self.link, '<Enter>', highlight ) |
| self.canvas.tag_bind( self.link, '<Leave>', unhighlight ) |
| self.canvas.tag_bind( self.link, '<ButtonPress-1>', select ) |
| |
| def createDataLinkBindings( self ): |
| "Create a set of bindings for nodes." |
| # Link bindings |
| # Selection still needs a bit of work overall |
| # Callbacks ignore event |
| |
| def select( _event, link=self.link ): |
| "Select item on mouse entry." |
| self.selectItem( link ) |
| |
| def highlight( _event, link=self.link ): |
| "Highlight item on mouse entry." |
| self.selectItem( link ) |
| self.canvas.itemconfig( link, fill='green' ) |
| |
| def unhighlight( _event, link=self.link ): |
| "Unhighlight item on mouse exit." |
| self.canvas.itemconfig( link, fill='blue' ) |
| #self.selectItem( None ) |
| |
| self.canvas.tag_bind( self.link, '<Enter>', highlight ) |
| self.canvas.tag_bind( self.link, '<Leave>', unhighlight ) |
| self.canvas.tag_bind( self.link, '<ButtonPress-1>', select ) |
| self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup ) |
| |
| def startLink( self, event ): |
| "Start a new link." |
| if event.widget not in self.widgetToItem: |
| # Didn't click on a node |
| return |
| |
| w = event.widget |
| item = self.widgetToItem[ w ] |
| x, y = self.canvas.coords( item ) |
| self.link = self.canvas.create_line( x, y, x, y, width=4, |
| fill='blue', tag='link' ) |
| self.linkx, self.linky = x, y |
| self.linkWidget = w |
| self.linkItem = item |
| |
| def finishLink( self, event ): |
| "Finish creating a link" |
| if self.link is None: |
| return |
| source = self.linkWidget |
| c = self.canvas |
| # Since we dragged from the widget, use root coords |
| x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root ) |
| target = self.findItem( x, y ) |
| dest = self.itemToWidget.get( target, None ) |
| if ( source is None or dest is None or source == dest |
| or dest in source.links or source in dest.links ): |
| self.releaseNetLink( event ) |
| return |
| # For now, don't allow hosts to be directly linked |
| stags = self.canvas.gettags( self.widgetToItem[ source ] ) |
| dtags = self.canvas.gettags( target ) |
| if (('Host' in stags and 'Host' in dtags)): |
| self.releaseNetLink( event ) |
| return |
| |
| # Set link type |
| linkType='data' |
| |
| self.createDataLinkBindings() |
| c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,)) |
| |
| x, y = c.coords( target ) |
| c.coords( self.link, self.linkx, self.linky, x, y ) |
| self.addLink( source, dest, linktype=linkType ) |
| |
| # We're done |
| self.link = self.linkWidget = None |
| |
| # Menu handlers |
| |
| def about( self ): |
| "Display about box." |
| about = self.aboutBox |
| if about is None: |
| bg = 'white' |
| about = Toplevel( bg='white' ) |
| about.title( 'About' ) |
| info = self.appName + ': a simple network editor for MiniCCNx - based on Miniedit' |
| warning = 'Development version - not entirely functional!' |
| #version = 'MiniEdit '+MINIEDIT_VERSION |
| author = 'Carlos Cabral, Jan 2013' |
| author2 = 'Caio Elias, Nov 2014' |
| author3 = 'Originally by: Bob Lantz <rlantz@cs>, April 2010' |
| enhancements = 'Enhancements by: Gregory Gee, Since July 2013' |
| www = 'http://gregorygee.wordpress.com/category/miniedit/' |
| line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg ) |
| line2 = Label( about, text=warning, font='Helvetica 9', bg=bg ) |
| line3 = Label( about, text=author, font='Helvetica 9', bg=bg ) |
| line4 = Label( about, text=author2, font='Helvetica 9', bg=bg ) |
| line5 = Label( about, text=author3, font='Helvetica 9', bg=bg ) |
| line6 = Label( about, text=enhancements, font='Helvetica 9', bg=bg ) |
| line7 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER ) |
| |
| |
| line7.insert(0, www) |
| line7.configure(state='readonly') |
| line1.pack( padx=20, pady=10 ) |
| line2.pack(pady=10 ) |
| line3.pack(pady=10 ) |
| line4.pack(pady=10 ) |
| line5.pack(pady=10 ) |
| line6.pack(pady=10 ) |
| line7.pack(pady=10 ) |
| hide = ( lambda about=about: about.withdraw() ) |
| self.aboutBox = about |
| # Hide on close rather than destroying window |
| Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide ) |
| # Show (existing) window |
| about.deiconify() |
| |
| def createToolImages( self ): |
| "Create toolbar (and icon) images." |
| |
| def checkIntf( self, intf ): |
| "Make sure intf exists and is not configured." |
| if ( ' %s:' % intf ) not in quietRun( 'ip link show' ): |
| showerror(title="Error", |
| message='External interface ' +intf + ' does not exist! Skipping.') |
| return False |
| ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) ) |
| if ips: |
| showerror(title="Error", |
| message= intf + ' has an IP address and is probably in use! Skipping.' ) |
| return False |
| return True |
| |
| def hostDetails( self, _ignore=None ): |
| if ( self.selection is None or |
| self.net is not None or |
| self.selection not in self.itemToWidget ): |
| return |
| widget = self.itemToWidget[ self.selection ] |
| name = widget[ 'text' ] |
| tags = self.canvas.gettags( self.selection ) |
| if 'Host' not in tags: |
| return |
| |
| prefDefaults = self.hostOpts[name] |
| hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults) |
| self.master.wait_window(hostBox.top) |
| if hostBox.result: |
| newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']} |
| newHostOpts['sched'] = hostBox.result['sched'] |
| if len(hostBox.result['startCommand']) > 0: |
| newHostOpts['startCommand'] = hostBox.result['startCommand'] |
| if len(hostBox.result['stopCommand']) > 0: |
| newHostOpts['stopCommand'] = hostBox.result['stopCommand'] |
| if len(hostBox.result['cpu']) > 0: |
| newHostOpts['cpu'] = float(hostBox.result['cpu']) |
| if len(hostBox.result['cores']) > 0: |
| newHostOpts['cores'] = hostBox.result['cores'] |
| if len(hostBox.result['hostname']) > 0: |
| newHostOpts['hostname'] = hostBox.result['hostname'] |
| name = hostBox.result['hostname'] |
| widget[ 'text' ] = name |
| if len(hostBox.result['defaultRoute']) > 0: |
| newHostOpts['defaultRoute'] = hostBox.result['defaultRoute'] |
| if len(hostBox.result['ip']) > 0: |
| newHostOpts['ip'] = hostBox.result['ip'] |
| if len(hostBox.result['externalInterfaces']) > 0: |
| newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces'] |
| if len(hostBox.result['vlanInterfaces']) > 0: |
| newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces'] |
| if len(hostBox.result['privateDirectory']) > 0: |
| newHostOpts['privateDirectory'] = hostBox.result['privateDirectory'] |
| self.hostOpts[name] = newHostOpts |
| print 'New host details for ' + name + ' = ' + str(newHostOpts) |
| |
| def linkUp( self ): |
| if ( self.selection is None or |
| self.net is None): |
| return |
| link = self.selection |
| linkDetail = self.links[link] |
| src = linkDetail['src'] |
| dst = linkDetail['dest'] |
| srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| self.net.configLinkStatus(srcName, dstName, 'up') |
| self.canvas.itemconfig(link, dash=()) |
| |
| def linkDown( self ): |
| if ( self.selection is None or |
| self.net is None): |
| return |
| link = self.selection |
| linkDetail = self.links[link] |
| src = linkDetail['src'] |
| dst = linkDetail['dest'] |
| srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| self.net.configLinkStatus(srcName, dstName, 'down') |
| self.canvas.itemconfig(link, dash=(4, 4)) |
| |
| def linkDetails( self, _ignore=None ): |
| if ( self.selection is None or |
| self.net is not None): |
| return |
| link = self.selection |
| |
| linkDetail = self.links[link] |
| src = linkDetail['src'] |
| dest = linkDetail['dest'] |
| linkopts = linkDetail['linkOpts'] |
| linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts) |
| if linkBox.result is not None: |
| linkDetail['linkOpts'] = linkBox.result |
| print 'New link details = ' + str(linkBox.result) |
| |
| def prefDetails( self ): |
| prefDefaults = self.appPrefs |
| prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults) |
| print 'New Prefs = ' + str(prefBox.result) |
| if prefBox.result: |
| self.appPrefs = prefBox.result |
| |
| def listBridge( self, _ignore=None ): |
| if ( self.selection is None or |
| self.net is None or |
| self.selection not in self.itemToWidget ): |
| return |
| name = self.itemToWidget[ self.selection ][ 'text' ] |
| tags = self.canvas.gettags( self.selection ) |
| |
| if name not in self.net.nameToNode: |
| return |
| if 'Switch' in tags or 'LegacySwitch' in tags: |
| call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True) |
| |
| def ovsShow( self, _ignore=None ): |
| call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True) |
| |
| def rootTerminal( self, _ignore=None ): |
| call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True) |
| |
| # Model interface |
| # |
| # Ultimately we will either want to use a topo or |
| # mininet object here, probably. |
| |
| def addLink( self, source, dest, linktype='data', linkopts={} ): |
| "Add link to model." |
| source.links[ dest ] = self.link |
| dest.links[ source ] = self.link |
| self.links[ self.link ] = {'type' :linktype, |
| 'src':source, |
| 'dest':dest, |
| 'linkOpts':linkopts} |
| |
| def deleteLink( self, link ): |
| "Delete link from model." |
| pair = self.links.get( link, None ) |
| if pair is not None: |
| source=pair['src'] |
| dest=pair['dest'] |
| del source.links[ dest ] |
| del dest.links[ source ] |
| stags = self.canvas.gettags( self.widgetToItem[ source ] ) |
| dtags = self.canvas.gettags( self.widgetToItem[ dest ] ) |
| ltags = self.canvas.gettags( link ) |
| |
| if 'control' in ltags: |
| controllerName = '' |
| switchName = '' |
| if 'Controller' in stags: |
| controllerName = source[ 'text' ] |
| switchName = dest[ 'text' ] |
| else: |
| controllerName = dest[ 'text' ] |
| switchName = source[ 'text' ] |
| |
| if controllerName in self.switchOpts[switchName]['controllers']: |
| self.switchOpts[switchName]['controllers'].remove(controllerName) |
| |
| |
| if link is not None: |
| del self.links[ link ] |
| |
| def deleteNode( self, item ): |
| "Delete node (and its links) from model." |
| |
| widget = self.itemToWidget[ item ] |
| tags = self.canvas.gettags(item) |
| |
| for link in widget.links.values(): |
| # Delete from view and model |
| self.deleteItem( link ) |
| del self.itemToWidget[ item ] |
| del self.widgetToItem[ widget ] |
| |
| def buildNodes( self, net): |
| # Make nodes |
| print "Getting Hosts and Switches." |
| for widget in self.widgetToItem: |
| name = widget[ 'text' ] |
| tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| #print name+' has '+str(tags) |
| |
| if 'LegacyRouter' in tags: |
| newSwitch = net.addHost( name , cls=LegacyRouter) |
| elif 'Host' in tags: |
| opts = self.hostOpts[name] |
| #print str(opts) |
| ip = None |
| defaultRoute = None |
| if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: |
| defaultRoute = 'via '+opts['defaultRoute'] |
| if 'ip' in opts and len(opts['ip']) > 0: |
| ip = opts['ip'] |
| else: |
| nodeNum = self.hostOpts[name]['nodeNum'] |
| #ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) |
| #ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) |
| |
| # Create the correct host class |
| if 'cores' in opts or 'cpu' in opts: |
| if ('privateDirectory' in opts): |
| hostCls = partial( CPULimitedHost, |
| privateDirs=opts['privateDirectory'] ) |
| else: |
| hostCls=CPULimitedHost |
| else: |
| if ('privateDirectory' in opts): |
| hostCls = partial( Host, |
| privateDirs=opts['privateDirectory'] ) |
| else: |
| hostCls=Host |
| print hostCls |
| newHost = net.addHost( name, |
| cls=hostCls, |
| ip=ip, |
| defaultRoute=defaultRoute |
| ) |
| |
| # Set the CPULimitedHost specific options |
| if 'cores' in opts: |
| newHost.setCPUs(cores = opts['cores']) |
| if 'cpu' in opts: |
| newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched']) |
| |
| # Attach external interfaces |
| if ('externalInterfaces' in opts): |
| for extInterface in opts['externalInterfaces']: |
| if self.checkIntf(extInterface): |
| Intf( extInterface, node=newHost ) |
| if ('vlanInterfaces' in opts): |
| if len(opts['vlanInterfaces']) > 0: |
| print 'Checking that OS is VLAN prepared' |
| self.pathCheck('vconfig', moduleName='vlan package') |
| moduleDeps( add='8021q' ) |
| else: |
| raise Exception( "Cannot create mystery node: " + name ) |
| |
| def pathCheck( self, *args, **kwargs ): |
| "Make sure each program in *args can be found in $PATH." |
| moduleName = kwargs.get( 'moduleName', 'it' ) |
| for arg in args: |
| if not quietRun( 'which ' + arg ): |
| showerror(title="Error", |
| message= 'Cannot find required executable %s.\n' % arg + |
| 'Please make sure that %s is installed ' % moduleName + |
| 'and available in your $PATH.' ) |
| |
| def buildLinks( self, net): |
| # Make links |
| print "Getting Links." |
| for key,link in self.links.iteritems(): |
| tags = self.canvas.gettags(key) |
| if 'data' in tags: |
| src=link['src'] |
| dst=link['dest'] |
| linkopts=link['linkOpts'] |
| srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| srcNode, dstNode = net.nameToNode[ srcName ], net.nameToNode[ dstName ] |
| if linkopts: |
| net.addLink(srcNode, dstNode, cls=TCLink, **linkopts) |
| else: |
| #print str(srcNode) |
| #print str(dstNode) |
| net.addLink(srcNode, dstNode) |
| self.canvas.itemconfig(key, dash=()) |
| |
| |
| def build( self ): |
| print "Build network based on our topology." |
| |
| dpctl = None |
| if len(self.appPrefs['dpctl']) > 0: |
| dpctl = int(self.appPrefs['dpctl']) |
| net = Mininet( topo=None, |
| listenPort=dpctl, |
| build=False) |
| #ipBase=self.appPrefs['ipBase'] ) |
| |
| self.buildNodes(net) |
| self.buildLinks(net) |
| |
| # Build network (we have to do this separately at the moment ) |
| net.build() |
| |
| return net |
| |
| |
| def postStartSetup( self ): |
| |
| # Setup host details |
| for widget in self.widgetToItem: |
| name = widget[ 'text' ] |
| tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| if 'Host' in tags: |
| newHost = self.net.get(name) |
| opts = self.hostOpts[name] |
| # Attach vlan interfaces |
| if ('vlanInterfaces' in opts): |
| for vlanInterface in opts['vlanInterfaces']: |
| print 'adding vlan interface '+vlanInterface[1] |
| newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0]) |
| # Run User Defined Start Command |
| if ('startCommand' in opts): |
| newHost.cmdPrint(opts['startCommand']) |
| |
| # Configure NetFlow |
| nflowValues = self.appPrefs['netflow'] |
| if len(nflowValues['nflowTarget']) > 0: |
| nflowEnabled = False |
| nflowSwitches = '' |
| for widget in self.widgetToItem: |
| name = widget[ 'text' ] |
| tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| |
| if 'Switch' in tags: |
| opts = self.switchOpts[name] |
| if 'netflow' in opts: |
| if opts['netflow'] == '1': |
| print name+' has Netflow enabled' |
| nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF' |
| nflowEnabled=True |
| if nflowEnabled: |
| nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout'] |
| if nflowValues['nflowAddId'] == '1': |
| nflowCmd = nflowCmd + ' add_id_to_interface=true' |
| else: |
| nflowCmd = nflowCmd + ' add_id_to_interface=false' |
| print 'cmd = '+nflowCmd+nflowSwitches |
| call(nflowCmd+nflowSwitches, shell=True) |
| |
| else: |
| print 'No switches with Netflow' |
| else: |
| print 'No NetFlow targets specified.' |
| |
| # Configure sFlow |
| sflowValues = self.appPrefs['sflow'] |
| if len(sflowValues['sflowTarget']) > 0: |
| sflowEnabled = False |
| sflowSwitches = '' |
| for widget in self.widgetToItem: |
| name = widget[ 'text' ] |
| tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| |
| if 'Switch' in tags: |
| opts = self.switchOpts[name] |
| if 'sflow' in opts: |
| if opts['sflow'] == '1': |
| print name+' has sflow enabled' |
| sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF' |
| sflowEnabled=True |
| if sflowEnabled: |
| sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling'] |
| print 'cmd = '+sflowCmd+sflowSwitches |
| call(sflowCmd+sflowSwitches, shell=True) |
| |
| else: |
| print 'No switches with sflow' |
| else: |
| print 'No sFlow targets specified.' |
| |
| ## NOTE: MAKE SURE THIS IS LAST THING CALLED |
| # Start the CLI if enabled |
| if self.appPrefs['startCLI'] == '1': |
| info( "\n\n NOTE: PLEASE REMEMBER TO EXIT THE CLI BEFORE YOU PRESS THE STOP BUTTON. Not exiting will prevent MiniEdit from quitting and will prevent you from starting the network again during this sessoin.\n\n") |
| CLI(self.net) |
| |
| def start( self ): |
| "Start network." |
| if self.net is None: |
| self.net = self.build() |
| |
| for widget in self.widgetToItem: |
| name = widget[ 'text' ] |
| tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| |
| self.postStartSetup() |
| |
| def stop( self ): |
| "Stop network." |
| if self.net is not None: |
| # Stop host details |
| for widget in self.widgetToItem: |
| name = widget[ 'text' ] |
| tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| if 'Host' in tags: |
| newHost = self.net.get(name) |
| opts = self.hostOpts[name] |
| # Run User Defined Stop Command |
| if ('stopCommand' in opts): |
| newHost.cmdPrint(opts['stopCommand']) |
| |
| self.net.stop() |
| cleanUpScreens() |
| self.net = None |
| |
| def do_linkPopup(self, event): |
| # display the popup menu |
| if ( self.net is None ): |
| try: |
| self.linkPopup.tk_popup(event.x_root, event.y_root) |
| finally: |
| # make sure to release the grab (Tk 8.0a1 only) |
| self.linkPopup.grab_release() |
| |
| def do_legacyRouterPopup(self, event): |
| # display the popup menu |
| if ( self.net is None ): |
| try: |
| self.legacyRouterPopup.tk_popup(event.x_root, event.y_root) |
| finally: |
| # make sure to release the grab (Tk 8.0a1 only) |
| self.legacyRouterPopup.grab_release() |
| |
| def do_hostPopup(self, event): |
| # display the popup menu |
| if ( self.net is None ): |
| try: |
| self.hostPopup.tk_popup(event.x_root, event.y_root) |
| isHostPopup = True |
| finally: |
| # make sure to release the grab (Tk 8.0a1 only) |
| self.hostPopup.grab_release() |
| |
| def xterm( self, _ignore=None ): |
| "Make an xterm when a button is pressed." |
| if ( self.selection is None or |
| self.net is None or |
| self.selection not in self.itemToWidget ): |
| return |
| name = self.itemToWidget[ self.selection ][ 'text' ] |
| if name not in self.net.nameToNode: |
| return |
| term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] ) |
| if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): |
| self.net.terms += term |
| else: |
| self.net.terms.append(term) |
| |
| def iperf( self, _ignore=None ): |
| "Make an xterm when a button is pressed." |
| if ( self.selection is None or |
| self.net is None or |
| self.selection not in self.itemToWidget ): |
| return |
| name = self.itemToWidget[ self.selection ][ 'text' ] |
| if name not in self.net.nameToNode: |
| return |
| self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' ) |
| |
| """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """ |
| |
| def parseArgs( self ): |
| """Parse command-line args and return options object. |
| returns: opts parse options dict""" |
| |
| if '--custom' in sys.argv: |
| index = sys.argv.index( '--custom' ) |
| if len( sys.argv ) > index + 1: |
| filename = sys.argv[ index + 1 ] |
| self.parseCustomFile( filename ) |
| else: |
| raise Exception( 'Custom file name not found' ) |
| |
| desc = ( "The %prog utility creates Miniccnx network from the\n" |
| "command line. It can create parametrized topologies,\n" |
| "invoke the Miniccnx CLI, and run tests." ) |
| |
| usage = ( '%prog [options] [template_file]\n' |
| '\nIf no template_file is given, generated template will be written to the file miniccnx.conf in the current directory.\n' |
| 'Type %prog -h for details)' ) |
| |
| opts = OptionParser( description=desc, usage=usage ) |
| |
| addDictOption( opts, TOPOS, TOPODEF, 'topo' ) |
| addDictOption( opts, LINKS, LINKDEF, 'link' ) |
| |
| opts.add_option( '--custom', type='string', default=None, |
| help='read custom topo and node params from .py' + |
| 'file' ) |
| |
| self.options, self.args = opts.parse_args() |
| # We don't accept extra arguments after the options |
| if self.args: |
| if len(self.args) > 1: |
| opts.print_help() |
| exit() |
| else: |
| self.template_file=self.args[0] |
| |
| def setCustom( self, name, value ): |
| "Set custom parameters for MininetRunner." |
| if name in ( 'topos', 'switches', 'hosts', 'controllers' ): |
| # Update dictionaries |
| param = name.upper() |
| globals()[ param ].update( value ) |
| elif name == 'validate': |
| # Add custom validate function |
| self.validate = value |
| else: |
| # Add or modify global variable or class |
| globals()[ name ] = value |
| |
| def parseCustomFile( self, fileName ): |
| "Parse custom file and add params before parsing cmd-line options." |
| customs = {} |
| if os.path.isfile( fileName ): |
| execfile( fileName, customs, customs ) |
| for name, val in customs.iteritems(): |
| self.setCustom( name, val ) |
| else: |
| raise Exception( 'could not find custom file: %s' % fileName ) |
| |
| def importTopo( self ): |
| print 'topo='+self.options.topo |
| if self.options.topo == 'none': |
| return |
| self.newTopology() |
| topo = buildTopo( TOPOS, self.options.topo ) |
| link = customConstructor( LINKS, self.options.link ) |
| importNet = Mininet(topo=topo, build=False, link=link) |
| importNet.build() |
| |
| c = self.canvas |
| rowIncrement = 100 |
| currentY = 100 |
| |
| # Add switches |
| print 'switches:'+str(len(importNet.switches)) |
| columnCount = 0 |
| for switch in importNet.switches: |
| name = switch.name |
| self.switchOpts[name] = {} |
| self.switchOpts[name]['nodeNum']=self.switchCount |
| self.switchOpts[name]['hostname']=name |
| self.switchOpts[name]['switchType']='default' |
| self.switchOpts[name]['controllers']=[] |
| |
| x = columnCount*100+100 |
| self.addNode('Switch', self.switchCount, |
| float(x), float(currentY), name=name) |
| icon = self.findWidgetByName(name) |
| icon.bind('<Button-3>', self.do_switchPopup ) |
| |
| if columnCount == 9: |
| columnCount = 0 |
| currentY = currentY + rowIncrement |
| else: |
| columnCount =columnCount+1 |
| |
| currentY = currentY + rowIncrement |
| # Add hosts |
| print 'hosts:'+str(len(importNet.hosts)) |
| columnCount = 0 |
| for host in importNet.hosts: |
| name = host.name |
| self.hostOpts[name] = {'sched':'host'} |
| self.hostOpts[name]['nodeNum']=self.hostCount |
| self.hostOpts[name]['hostname']=name |
| self.hostOpts[name]['ip']=host.IP() |
| |
| x = columnCount*100+100 |
| self.addNode('Host', self.hostCount, |
| float(x), float(currentY), name=name) |
| icon = self.findWidgetByName(name) |
| icon.bind('<Button-3>', self.do_hostPopup ) |
| if columnCount == 9: |
| columnCount = 0 |
| currentY = currentY + rowIncrement |
| else: |
| columnCount =columnCount+1 |
| |
| print 'links:'+str(len(topo.links())) |
| #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')] |
| for link in topo.links(): |
| print str(link) |
| srcNode = link[0] |
| src = self.findWidgetByName(srcNode) |
| sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) |
| |
| destNode = link[1] |
| dest = self.findWidgetByName(destNode) |
| dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) |
| |
| params = topo.linkInfo( srcNode, destNode ) |
| print 'Link Parameters='+str(params) |
| |
| self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, |
| fill='blue', tag='link' ) |
| c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) |
| self.addLink( src, dest, linkopts=params ) |
| self.createDataLinkBindings() |
| self.link = self.linkWidget = None |
| |
| importNet.stop() |
| |
| def miniEditImages(): |
| "Create and return images for MiniEdit." |
| |
| # Image data. Git will be unhappy. However, the alternative |
| # is to keep track of separate binary files, which is also |
| # unappealing. |
| |
| return { |
| 'Select': BitmapImage( |
| file='/usr/include/X11/bitmaps/left_ptr' ), |
| |
| 'LegacyRouter': PhotoImage( data=r""" |
| R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+3QFq1DmL3wJMmAMzZZW11dnZ |
| 2SFrtyNdmTSO6gIZMUKa8gJVqEOHzR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq |
| 6ymF4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9vwNgvwJZsX+69gsXJQFH |
| jTtjizF0tvHx8VOm9z2V736Dhz2N3QM2acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtg |
| tktjfQFu3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh312Gt+VGm/AQIDTmB |
| yAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i56gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia |
| 7DpeggFt2QNPm97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er/yVVhwJJktPh |
| 70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVVhQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18 |
| zKfP9wwcLAMHCwFFiS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh7cve8pG/ |
| 7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR1RMjNTF3vU2X4TZupwRSolNne4nB+T+L |
| 2YGz4zJ/zYe99YGHjRdDcT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6WlpW2t |
| 7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90uvPz8wIVKBp42SV5zbfT7wtXpStV |
| fwFWrBVvyTt3swFz5kGBv2+1/QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t |
| u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T5yH5BAEAAAAALAAAAAAyABgA |
| Bwj/AAEIHEiQYJY7Qwg9UsTplRIbENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4c |
| HeoIabJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPATqEBoRB9gVJsxRlhPwHI |
| 0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkS |
| rtwADuxCG/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmDjhTMmseoKQIFDx7R |
| oxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq |
| 9dGvv09RHFhcIUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p9HEUFhxgMSAv |
| jbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CF |
| oVggmEgCyRf01WcFCYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57AgyZckpKKP |
| GFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemMIQggeaJywSQ/wgHOAmJskQEfWqBlFBEH |
| 1P/QaGY3QOpDZXA2+A6m7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlBpZdi |
| isd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtChRmVPNWgpr+Be+Nc9icARww9TkIEu |
| DAsQ0O7DzGIQzD2QdDEJHTsIAROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB |
| xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE42Q9jtFIp8z0Dy1jQMA1AGzi |
| z9VoW7310V0znYDTGMQgwUDXLDBO2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOM |
| LQkcjvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZMDHKCTwI8EcQFHBBAAFc |
| gGPLHwLwcMIo12Qxu0ABAQA7 |
| """), |
| |
| 'Host': PhotoImage( data=r""" |
| R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M |
| mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m |
| Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A |
| M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM |
| AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz |
| /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ |
| zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ |
| mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz |
| ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ |
| M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ |
| AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA |
| /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM |
| zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm |
| mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA |
| ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM |
| MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm |
| AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A |
| ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI |
| AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA |
| RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA |
| ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw |
| BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2 |
| HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO |
| p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/ |
| C8cSBBAQADs= |
| """ ), |
| |
| 'NetLink': PhotoImage( data=r""" |
| R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M |
| mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m |
| Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A |
| M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM |
| AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz |
| /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ |
| zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ |
| mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz |
| ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ |
| M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ |
| AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA |
| /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM |
| zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm |
| mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA |
| ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM |
| MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm |
| AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A |
| ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI |
| AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA |
| RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA |
| ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG |
| Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy |
| lBmxI8mSNknm1Dnx5sCAADs= |
| """ ) |
| } |
| |
| def addDictOption( opts, choicesDict, default, name, helpStr=None ): |
| """Convenience function to add choices dicts to OptionParser. |
| opts: OptionParser instance |
| choicesDict: dictionary of valid choices, must include default |
| default: default choice key |
| name: long option name |
| help: string""" |
| if default not in choicesDict: |
| raise Exception( 'Invalid default %s for choices dict: %s' % |
| ( default, name ) ) |
| if not helpStr: |
| helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + |
| '[,param=value...]' ) |
| opts.add_option( '--' + name, |
| type='string', |
| default = default, |
| help = helpStr ) |
| |
| if __name__ == '__main__': |
| setLogLevel( 'info' ) |
| app = MiniEdit() |
| """ import topology if specified """ |
| app.parseArgs() |
| app.importTopo() |
| |
| global isHostPopup |
| global isRouterPopup |
| global isLinkPopup |
| |
| app.mainloop() |