4 MiniEdit: a simple network editor for Mininet
6 This is a simple demonstration of how one might build a
7 GUI application using Mininet as the network model.
10 Gregory Gee, July 2013
12 Controller icon from http://semlabs.co.uk/
13 OpenFlow icon from https://www.opennetworking.org/
16 # Miniedit needs some work in order to pass pylint...
17 # pylint: disable=line-too-long,too-many-branches
18 # pylint: disable=too-many-statements,attribute-defined-outside-init
19 # pylint: disable=missing-docstring
21 MINIEDIT_VERSION = '2.2.0.1'
24 from optparse import OptionParser
25 from subprocess import call
27 # pylint: disable=import-error
28 if sys.version_info[0] == 2:
29 from Tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu,
30 Checkbutton, Menu, Toplevel, Button, BitmapImage,
31 PhotoImage, Canvas, Scrollbar, Wm, TclError,
32 StringVar, IntVar, E, W, EW, NW, Y, VERTICAL, SOLID,
33 CENTER, RIGHT, LEFT, BOTH, TRUE, FALSE )
34 from ttk import Notebook
35 from tkMessageBox import showerror
40 from tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu,
41 Checkbutton, Menu, Toplevel, Button, BitmapImage,
42 PhotoImage, Canvas, Scrollbar, Wm, TclError,
43 StringVar, IntVar, E, W, EW, NW, Y, VERTICAL, SOLID,
44 CENTER, RIGHT, LEFT, BOTH, TRUE, FALSE )
45 from tkinter.ttk import Notebook
46 from tkinter.messagebox import showerror
47 from tkinter import font as tkFont
48 from tkinter import simpledialog as tkSimpleDialog
49 from tkinter import filedialog as tkFileDialog
50 # pylint: enable=import-error
54 from distutils.version import StrictVersion
56 from functools import partial
58 if 'PYTHONPATH' in os.environ:
59 sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
61 # someday: from ttk import *
63 from mininet.log import info, debug, warn, setLogLevel
64 from mininet.net import Mininet, VERSION
65 from mininet.util import netParse, ipAdd, quietRun
66 from mininet.util import buildTopo
67 from mininet.util import custom, customClass
68 from mininet.term import makeTerm, cleanUpScreens
69 from mininet.node import Controller, RemoteController, NOX, OVSController
70 from mininet.node import CPULimitedHost, Host, Node
71 from mininet.node import OVSSwitch, UserSwitch
72 from mininet.link import TCLink, Intf, Link
73 from mininet.cli import CLI
74 from mininet.moduledeps import moduleDeps
75 from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
76 from mininet.topolib import TreeTopo
78 info( 'MiniEdit running against Mininet '+VERSION, '\n' )
79 MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION)
80 if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
81 from mininet.node import IVSSwitch
84 TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
86 'reversed': SingleSwitchReversedTopo,
87 'single': SingleSwitchTopo,
91 CONTROLLERS = { 'ref': Controller,
92 'ovsc': OVSController,
94 'remote': RemoteController,
95 'none': lambda name: None }
97 LINKS = { 'default': Link,
100 HOSTS = { 'proc': Host,
101 'rt': custom( CPULimitedHost, sched='rt' ),
102 'cfs': custom( CPULimitedHost, sched='cfs' ) }
105 class InbandController( RemoteController ):
106 "RemoteController that ignores checkListening"
107 def checkListening( self ):
108 "Overridden to do nothing."
111 class CustomUserSwitch(UserSwitch):
112 "Customized UserSwitch"
113 def __init__( self, name, dpopts='--no-slicing', **kwargs ):
114 UserSwitch.__init__( self, name, **kwargs )
117 def getSwitchIP(self):
118 "Return management IP address"
121 def setSwitchIP(self, ip):
122 "Set management IP address"
125 def start( self, controllers ):
126 "Start and set management IP address"
127 # Call superclass constructor
128 UserSwitch.start( self, controllers )
129 # Set Switch IP address
130 if self.switchIP is not None:
131 if not self.inNamespace:
132 self.cmd( 'ifconfig', self, self.switchIP )
134 self.cmd( 'ifconfig lo', self.switchIP )
136 class LegacyRouter( Node ):
138 def __init__( self, name, inNamespace=True, **params ):
139 Node.__init__( self, name, inNamespace, **params )
141 def config( self, **_params ):
143 self.setParam( _params, 'setIP', ip='0.0.0.0' )
144 r = Node.config( self, **_params )
145 self.cmd('sysctl -w net.ipv4.ip_forward=1')
148 class LegacySwitch(OVSSwitch):
149 "OVS switch in standalone/bridge mode"
150 def __init__( self, name, **params ):
151 OVSSwitch.__init__( self, name, failMode='standalone', **params )
154 class customOvs(OVSSwitch):
155 "Customized OVS switch"
157 def __init__( self, name, failMode='secure', datapath='kernel', **params ):
158 OVSSwitch.__init__( self, name, failMode=failMode, datapath=datapath,**params )
161 def getSwitchIP(self):
162 "Return management IP address"
165 def setSwitchIP(self, ip):
166 "Set management IP address"
169 def start( self, controllers ):
170 "Start and set management IP address"
171 # Call superclass constructor
172 OVSSwitch.start( self, controllers )
173 # Set Switch IP address
174 if self.switchIP is not None:
175 self.cmd( 'ifconfig', self, self.switchIP )
177 class PrefsDialog(tkSimpleDialog.Dialog):
180 def __init__(self, parent, title, prefDefaults):
182 self.prefValues = prefDefaults
184 tkSimpleDialog.Dialog.__init__(self, parent, title)
186 def body(self, master):
188 self.rootFrame = master
189 self.leftfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
190 self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2)
191 self.rightfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
192 self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2)
195 Label(self.leftfieldFrame, text="IP Base:").grid(row=0, sticky=E)
196 self.ipEntry = Entry(self.leftfieldFrame)
197 self.ipEntry.grid(row=0, column=1)
198 ipBase = self.prefValues['ipBase']
199 self.ipEntry.insert(0, ipBase)
201 # Selection of terminal type
202 Label(self.leftfieldFrame, text="Default Terminal:").grid(row=1, sticky=E)
203 self.terminalVar = StringVar(self.leftfieldFrame)
204 self.terminalOption = OptionMenu(self.leftfieldFrame, self.terminalVar, "xterm", "gterm")
205 self.terminalOption.grid(row=1, column=1, sticky=W)
206 terminalType = self.prefValues['terminalType']
207 self.terminalVar.set(terminalType)
210 Label(self.leftfieldFrame, text="Start CLI:").grid(row=2, sticky=E)
211 self.cliStart = IntVar()
212 self.cliButton = Checkbutton(self.leftfieldFrame, variable=self.cliStart)
213 self.cliButton.grid(row=2, column=1, sticky=W)
214 if self.prefValues['startCLI'] == '0':
215 self.cliButton.deselect()
217 self.cliButton.select()
219 # Selection of switch type
220 Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E)
221 self.switchType = StringVar(self.leftfieldFrame)
222 self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace")
223 self.switchTypeMenu.grid(row=3, column=1, sticky=W)
224 switchTypePref = self.prefValues['switchType']
225 if switchTypePref == 'ivs':
226 self.switchType.set("Indigo Virtual Switch")
227 elif switchTypePref == 'userns':
228 self.switchType.set("Userspace Switch inNamespace")
229 elif switchTypePref == 'user':
230 self.switchType.set("Userspace Switch")
232 self.switchType.set("Open vSwitch Kernel Mode")
235 # Fields for OVS OpenFlow version
236 ovsFrame= LabelFrame(self.leftfieldFrame, text='Open vSwitch', padx=5, pady=5)
237 ovsFrame.grid(row=4, column=0, columnspan=2, sticky=EW)
238 Label(ovsFrame, text="OpenFlow 1.0:").grid(row=0, sticky=E)
239 Label(ovsFrame, text="OpenFlow 1.1:").grid(row=1, sticky=E)
240 Label(ovsFrame, text="OpenFlow 1.2:").grid(row=2, sticky=E)
241 Label(ovsFrame, text="OpenFlow 1.3:").grid(row=3, sticky=E)
243 self.ovsOf10 = IntVar()
244 self.covsOf10 = Checkbutton(ovsFrame, variable=self.ovsOf10)
245 self.covsOf10.grid(row=0, column=1, sticky=W)
246 if self.prefValues['openFlowVersions']['ovsOf10'] == '0':
247 self.covsOf10.deselect()
249 self.covsOf10.select()
251 self.ovsOf11 = IntVar()
252 self.covsOf11 = Checkbutton(ovsFrame, variable=self.ovsOf11)
253 self.covsOf11.grid(row=1, column=1, sticky=W)
254 if self.prefValues['openFlowVersions']['ovsOf11'] == '0':
255 self.covsOf11.deselect()
257 self.covsOf11.select()
259 self.ovsOf12 = IntVar()
260 self.covsOf12 = Checkbutton(ovsFrame, variable=self.ovsOf12)
261 self.covsOf12.grid(row=2, column=1, sticky=W)
262 if self.prefValues['openFlowVersions']['ovsOf12'] == '0':
263 self.covsOf12.deselect()
265 self.covsOf12.select()
267 self.ovsOf13 = IntVar()
268 self.covsOf13 = Checkbutton(ovsFrame, variable=self.ovsOf13)
269 self.covsOf13.grid(row=3, column=1, sticky=W)
270 if self.prefValues['openFlowVersions']['ovsOf13'] == '0':
271 self.covsOf13.deselect()
273 self.covsOf13.select()
275 # Field for DPCTL listen port
276 Label(self.leftfieldFrame, text="dpctl port:").grid(row=5, sticky=E)
277 self.dpctlEntry = Entry(self.leftfieldFrame)
278 self.dpctlEntry.grid(row=5, column=1)
279 if 'dpctl' in self.prefValues:
280 self.dpctlEntry.insert(0, self.prefValues['dpctl'])
283 sflowValues = self.prefValues['sflow']
284 self.sflowFrame= LabelFrame(self.rightfieldFrame, text='sFlow Profile for Open vSwitch', padx=5, pady=5)
285 self.sflowFrame.grid(row=0, column=0, columnspan=2, sticky=EW)
287 Label(self.sflowFrame, text="Target:").grid(row=0, sticky=E)
288 self.sflowTarget = Entry(self.sflowFrame)
289 self.sflowTarget.grid(row=0, column=1)
290 self.sflowTarget.insert(0, sflowValues['sflowTarget'])
292 Label(self.sflowFrame, text="Sampling:").grid(row=1, sticky=E)
293 self.sflowSampling = Entry(self.sflowFrame)
294 self.sflowSampling.grid(row=1, column=1)
295 self.sflowSampling.insert(0, sflowValues['sflowSampling'])
297 Label(self.sflowFrame, text="Header:").grid(row=2, sticky=E)
298 self.sflowHeader = Entry(self.sflowFrame)
299 self.sflowHeader.grid(row=2, column=1)
300 self.sflowHeader.insert(0, sflowValues['sflowHeader'])
302 Label(self.sflowFrame, text="Polling:").grid(row=3, sticky=E)
303 self.sflowPolling = Entry(self.sflowFrame)
304 self.sflowPolling.grid(row=3, column=1)
305 self.sflowPolling.insert(0, sflowValues['sflowPolling'])
308 nflowValues = self.prefValues['netflow']
309 self.nFrame= LabelFrame(self.rightfieldFrame, text='NetFlow Profile for Open vSwitch', padx=5, pady=5)
310 self.nFrame.grid(row=1, column=0, columnspan=2, sticky=EW)
312 Label(self.nFrame, text="Target:").grid(row=0, sticky=E)
313 self.nflowTarget = Entry(self.nFrame)
314 self.nflowTarget.grid(row=0, column=1)
315 self.nflowTarget.insert(0, nflowValues['nflowTarget'])
317 Label(self.nFrame, text="Active Timeout:").grid(row=1, sticky=E)
318 self.nflowTimeout = Entry(self.nFrame)
319 self.nflowTimeout.grid(row=1, column=1)
320 self.nflowTimeout.insert(0, nflowValues['nflowTimeout'])
322 Label(self.nFrame, text="Add ID to Interface:").grid(row=2, sticky=E)
323 self.nflowAddId = IntVar()
324 self.nflowAddIdButton = Checkbutton(self.nFrame, variable=self.nflowAddId)
325 self.nflowAddIdButton.grid(row=2, column=1, sticky=W)
326 if nflowValues['nflowAddId'] == '0':
327 self.nflowAddIdButton.deselect()
329 self.nflowAddIdButton.select()
335 ipBase = self.ipEntry.get()
336 terminalType = self.terminalVar.get()
337 startCLI = str(self.cliStart.get())
338 sw = self.switchType.get()
339 dpctl = self.dpctlEntry.get()
341 ovsOf10 = str(self.ovsOf10.get())
342 ovsOf11 = str(self.ovsOf11.get())
343 ovsOf12 = str(self.ovsOf12.get())
344 ovsOf13 = str(self.ovsOf13.get())
346 sflowValues = {'sflowTarget':self.sflowTarget.get(),
347 'sflowSampling':self.sflowSampling.get(),
348 'sflowHeader':self.sflowHeader.get(),
349 'sflowPolling':self.sflowPolling.get()}
350 nflowvalues = {'nflowTarget':self.nflowTarget.get(),
351 'nflowTimeout':self.nflowTimeout.get(),
352 'nflowAddId':str(self.nflowAddId.get())}
353 self.result = {'ipBase':ipBase,
354 'terminalType':terminalType,
357 'netflow':nflowvalues,
359 if sw == 'Indigo Virtual Switch':
360 self.result['switchType'] = 'ivs'
361 if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
363 showerror(title="Error",
364 message='MiniNet version 2.1+ required. You have '+VERSION+'.')
365 elif sw == 'Userspace Switch':
366 self.result['switchType'] = 'user'
367 elif sw == 'Userspace Switch inNamespace':
368 self.result['switchType'] = 'userns'
370 self.result['switchType'] = 'ovs'
374 ovsVer = self.getOvsVersion()
375 if StrictVersion(ovsVer) < StrictVersion('2.0'):
377 showerror(title="Error",
378 message='Open vSwitch version 2.0+ required. You have '+ovsVer+'.')
379 if ovsOf12 == "1" or ovsOf13 == "1":
380 ovsVer = self.getOvsVersion()
381 if StrictVersion(ovsVer) < StrictVersion('1.10'):
383 showerror(title="Error",
384 message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.')
387 self.result['openFlowVersions']={'ovsOf10':ovsOf10,
397 outp = quietRun("ovs-vsctl --version")
398 r = r'ovs-vsctl \(Open vSwitch\) (.*)'
399 m = re.search(r, outp)
401 warn( 'Version check failed' )
404 info( 'Open vSwitch version is '+m.group(1), '\n' )
408 class CustomDialog(object):
410 # TODO: Fix button placement and Title and window focus lock
411 def __init__(self, master, _title):
412 self.top=Toplevel(master)
414 self.bodyFrame = Frame(self.top)
415 self.bodyFrame.grid(row=0, column=0, sticky='nswe')
416 self.body(self.bodyFrame)
418 #return self.b # initial focus
419 buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey')
420 buttonFrame.grid(row=1 , column=0, sticky='nswe')
422 okButton = Button(buttonFrame, width=8, text='OK', relief='groove',
423 bd=4, command=self.okAction)
424 okButton.grid(row=0, column=0, sticky=E)
426 canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove',
427 bd=4, command=self.cancelAction)
428 canlceButton.grid(row=0, column=1, sticky=W)
430 def body(self, master):
431 self.rootFrame = master
436 def cancelAction(self):
443 class HostDialog(CustomDialog):
445 def __init__(self, master, title, prefDefaults):
447 self.prefValues = prefDefaults
450 CustomDialog.__init__(self, master, title)
452 def body(self, master):
453 self.rootFrame = master
454 n = Notebook(self.rootFrame)
455 self.propFrame = Frame(n)
456 self.vlanFrame = Frame(n)
457 self.interfaceFrame = Frame(n)
458 self.mountFrame = Frame(n)
459 n.add(self.propFrame, text='Properties')
460 n.add(self.vlanFrame, text='VLAN Interfaces')
461 n.add(self.interfaceFrame, text='External Interfaces')
462 n.add(self.mountFrame, text='Private Directories')
467 Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E)
468 self.hostnameEntry = Entry(self.propFrame)
469 self.hostnameEntry.grid(row=0, column=1)
470 if 'hostname' in self.prefValues:
471 self.hostnameEntry.insert(0, self.prefValues['hostname'])
473 # Field for Switch IP
474 Label(self.propFrame, text="IP Address:").grid(row=1, sticky=E)
475 self.ipEntry = Entry(self.propFrame)
476 self.ipEntry.grid(row=1, column=1)
477 if 'ip' in self.prefValues:
478 self.ipEntry.insert(0, self.prefValues['ip'])
480 # Field for default route
481 Label(self.propFrame, text="Default Route:").grid(row=2, sticky=E)
482 self.routeEntry = Entry(self.propFrame)
483 self.routeEntry.grid(row=2, column=1)
484 if 'defaultRoute' in self.prefValues:
485 self.routeEntry.insert(0, self.prefValues['defaultRoute'])
488 Label(self.propFrame, text="Amount CPU:").grid(row=3, sticky=E)
489 self.cpuEntry = Entry(self.propFrame)
490 self.cpuEntry.grid(row=3, column=1)
491 if 'cpu' in self.prefValues:
492 self.cpuEntry.insert(0, str(self.prefValues['cpu']))
493 # Selection of Scheduler
494 if 'sched' in self.prefValues:
495 sched = self.prefValues['sched']
498 self.schedVar = StringVar(self.propFrame)
499 self.schedOption = OptionMenu(self.propFrame, self.schedVar, "host", "cfs", "rt")
500 self.schedOption.grid(row=3, column=2, sticky=W)
501 self.schedVar.set(sched)
504 Label(self.propFrame, text="Cores:").grid(row=4, sticky=E)
505 self.coreEntry = Entry(self.propFrame)
506 self.coreEntry.grid(row=4, column=1)
507 if 'cores' in self.prefValues:
508 self.coreEntry.insert(1, self.prefValues['cores'])
511 Label(self.propFrame, text="Start Command:").grid(row=5, sticky=E)
512 self.startEntry = Entry(self.propFrame)
513 self.startEntry.grid(row=5, column=1, sticky='nswe', columnspan=3)
514 if 'startCommand' in self.prefValues:
515 self.startEntry.insert(0, str(self.prefValues['startCommand']))
517 Label(self.propFrame, text="Stop Command:").grid(row=6, sticky=E)
518 self.stopEntry = Entry(self.propFrame)
519 self.stopEntry.grid(row=6, column=1, sticky='nswe', columnspan=3)
520 if 'stopCommand' in self.prefValues:
521 self.stopEntry.insert(0, str(self.prefValues['stopCommand']))
524 # External Interfaces
525 self.externalInterfaces = 0
526 Label(self.interfaceFrame, text="External Interface:").grid(row=0, column=0, sticky=E)
527 self.b = Button( self.interfaceFrame, text='Add', command=self.addInterface)
528 self.b.grid(row=0, column=1)
530 self.interfaceFrame = VerticalScrolledTable(self.interfaceFrame, rows=0, columns=1, title='External Interfaces')
531 self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
532 self.tableFrame = self.interfaceFrame.interior
533 self.tableFrame.addRow(value=['Interface Name'], readonly=True)
535 # Add defined interfaces
536 externalInterfaces = []
537 if 'externalInterfaces' in self.prefValues:
538 externalInterfaces = self.prefValues['externalInterfaces']
540 for externalInterface in externalInterfaces:
541 self.tableFrame.addRow(value=[externalInterface])
545 self.vlanInterfaces = 0
546 Label(self.vlanFrame, text="VLAN Interface:").grid(row=0, column=0, sticky=E)
547 self.vlanButton = Button( self.vlanFrame, text='Add', command=self.addVlanInterface)
548 self.vlanButton.grid(row=0, column=1)
550 self.vlanFrame = VerticalScrolledTable(self.vlanFrame, rows=0, columns=2, title='VLAN Interfaces')
551 self.vlanFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
552 self.vlanTableFrame = self.vlanFrame.interior
553 self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True)
556 if 'vlanInterfaces' in self.prefValues:
557 vlanInterfaces = self.prefValues['vlanInterfaces']
558 for vlanInterface in vlanInterfaces:
559 self.vlanTableFrame.addRow(value=vlanInterface)
562 # Private Directories
563 self.privateDirectories = 0
564 Label(self.mountFrame, text="Private Directory:").grid(row=0, column=0, sticky=E)
565 self.mountButton = Button( self.mountFrame, text='Add', command=self.addDirectory)
566 self.mountButton.grid(row=0, column=1)
568 self.mountFrame = VerticalScrolledTable(self.mountFrame, rows=0, columns=2, title='Directories')
569 self.mountFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
570 self.mountTableFrame = self.mountFrame.interior
571 self.mountTableFrame.addRow(value=['Mount','Persistent Directory'], readonly=True)
574 if 'privateDirectory' in self.prefValues:
575 directoryList = self.prefValues['privateDirectory']
576 for privateDir in directoryList:
577 if isinstance( privateDir, tuple ):
578 self.mountTableFrame.addRow(value=privateDir)
580 self.mountTableFrame.addRow(value=[privateDir,''])
583 def addDirectory( self ):
584 self.mountTableFrame.addRow()
586 def addVlanInterface( self ):
587 self.vlanTableFrame.addRow()
589 def addInterface( self ):
590 self.tableFrame.addRow()
593 externalInterfaces = []
594 for row in range(self.tableFrame.rows):
595 if (len(self.tableFrame.get(row, 0)) > 0 and
597 externalInterfaces.append(self.tableFrame.get(row, 0))
599 for row in range(self.vlanTableFrame.rows):
600 if (len(self.vlanTableFrame.get(row, 0)) > 0 and
601 len(self.vlanTableFrame.get(row, 1)) > 0 and
603 vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)])
604 privateDirectories = []
605 for row in range(self.mountTableFrame.rows):
606 if len(self.mountTableFrame.get(row, 0)) > 0 and row > 0:
607 if len(self.mountTableFrame.get(row, 1)) > 0:
608 privateDirectories.append((self.mountTableFrame.get(row, 0), self.mountTableFrame.get(row, 1)))
610 privateDirectories.append(self.mountTableFrame.get(row, 0))
612 results = {'cpu': self.cpuEntry.get(),
613 'cores':self.coreEntry.get(),
614 'sched':self.schedVar.get(),
615 'hostname':self.hostnameEntry.get(),
616 'ip':self.ipEntry.get(),
617 'defaultRoute':self.routeEntry.get(),
618 'startCommand':self.startEntry.get(),
619 'stopCommand':self.stopEntry.get(),
620 'privateDirectory':privateDirectories,
621 'externalInterfaces':externalInterfaces,
622 'vlanInterfaces':vlanInterfaces}
623 self.result = results
625 class SwitchDialog(CustomDialog):
627 def __init__(self, master, title, prefDefaults):
629 self.prefValues = prefDefaults
631 CustomDialog.__init__(self, master, title)
633 def body(self, master):
634 self.rootFrame = master
635 self.leftfieldFrame = Frame(self.rootFrame)
636 self.rightfieldFrame = Frame(self.rootFrame)
637 self.leftfieldFrame.grid(row=0, column=0, sticky='nswe')
638 self.rightfieldFrame.grid(row=0, column=1, sticky='nswe')
641 externalInterfaces = []
642 if 'externalInterfaces' in self.prefValues:
643 externalInterfaces = self.prefValues['externalInterfaces']
646 Label(self.leftfieldFrame, text="Hostname:").grid(row=rowCount, sticky=E)
647 self.hostnameEntry = Entry(self.leftfieldFrame)
648 self.hostnameEntry.grid(row=rowCount, column=1)
649 self.hostnameEntry.insert(0, self.prefValues['hostname'])
653 Label(self.leftfieldFrame, text="DPID:").grid(row=rowCount, sticky=E)
654 self.dpidEntry = Entry(self.leftfieldFrame)
655 self.dpidEntry.grid(row=rowCount, column=1)
656 if 'dpid' in self.prefValues:
657 self.dpidEntry.insert(0, self.prefValues['dpid'])
661 Label(self.leftfieldFrame, text="Enable NetFlow:").grid(row=rowCount, sticky=E)
662 self.nflow = IntVar()
663 self.nflowButton = Checkbutton(self.leftfieldFrame, variable=self.nflow)
664 self.nflowButton.grid(row=rowCount, column=1, sticky=W)
665 if 'netflow' in self.prefValues:
666 if self.prefValues['netflow'] == '0':
667 self.nflowButton.deselect()
669 self.nflowButton.select()
671 self.nflowButton.deselect()
675 Label(self.leftfieldFrame, text="Enable sFlow:").grid(row=rowCount, sticky=E)
676 self.sflow = IntVar()
677 self.sflowButton = Checkbutton(self.leftfieldFrame, variable=self.sflow)
678 self.sflowButton.grid(row=rowCount, column=1, sticky=W)
679 if 'sflow' in self.prefValues:
680 if self.prefValues['sflow'] == '0':
681 self.sflowButton.deselect()
683 self.sflowButton.select()
685 self.sflowButton.deselect()
688 # Selection of switch type
689 Label(self.leftfieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E)
690 self.switchType = StringVar(self.leftfieldFrame)
691 self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Default", "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace")
692 self.switchTypeMenu.grid(row=rowCount, column=1, sticky=W)
693 if 'switchType' in self.prefValues:
694 switchTypePref = self.prefValues['switchType']
695 if switchTypePref == 'ivs':
696 self.switchType.set("Indigo Virtual Switch")
697 elif switchTypePref == 'userns':
698 self.switchType.set("Userspace Switch inNamespace")
699 elif switchTypePref == 'user':
700 self.switchType.set("Userspace Switch")
701 elif switchTypePref == 'ovs':
702 self.switchType.set("Open vSwitch Kernel Mode")
704 self.switchType.set("Default")
706 self.switchType.set("Default")
709 # Field for Switch IP
710 Label(self.leftfieldFrame, text="IP Address:").grid(row=rowCount, sticky=E)
711 self.ipEntry = Entry(self.leftfieldFrame)
712 self.ipEntry.grid(row=rowCount, column=1)
713 if 'switchIP' in self.prefValues:
714 self.ipEntry.insert(0, self.prefValues['switchIP'])
717 # Field for DPCTL port
718 Label(self.leftfieldFrame, text="DPCTL port:").grid(row=rowCount, sticky=E)
719 self.dpctlEntry = Entry(self.leftfieldFrame)
720 self.dpctlEntry.grid(row=rowCount, column=1)
721 if 'dpctl' in self.prefValues:
722 self.dpctlEntry.insert(0, self.prefValues['dpctl'])
725 # External Interfaces
726 Label(self.rightfieldFrame, text="External Interface:").grid(row=0, sticky=E)
727 self.b = Button( self.rightfieldFrame, text='Add', command=self.addInterface)
728 self.b.grid(row=0, column=1)
730 self.interfaceFrame = VerticalScrolledTable(self.rightfieldFrame, rows=0, columns=1, title='External Interfaces')
731 self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
732 self.tableFrame = self.interfaceFrame.interior
734 # Add defined interfaces
735 for externalInterface in externalInterfaces:
736 self.tableFrame.addRow(value=[externalInterface])
738 self.commandFrame = Frame(self.rootFrame)
739 self.commandFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
740 self.commandFrame.columnconfigure(1, weight=1)
742 Label(self.commandFrame, text="Start Command:").grid(row=0, column=0, sticky=W)
743 self.startEntry = Entry(self.commandFrame)
744 self.startEntry.grid(row=0, column=1, sticky='nsew')
745 if 'startCommand' in self.prefValues:
746 self.startEntry.insert(0, str(self.prefValues['startCommand']))
748 Label(self.commandFrame, text="Stop Command:").grid(row=1, column=0, sticky=W)
749 self.stopEntry = Entry(self.commandFrame)
750 self.stopEntry.grid(row=1, column=1, sticky='nsew')
751 if 'stopCommand' in self.prefValues:
752 self.stopEntry.insert(0, str(self.prefValues['stopCommand']))
754 def addInterface( self ):
755 self.tableFrame.addRow()
757 def defaultDpid( self, name):
758 "Derive dpid from switch name, s1 -> 1"
759 assert self # satisfy pylint and allow contextual override
761 dpid = int( re.findall( r'\d+', name )[ 0 ] )
762 dpid = hex( dpid )[ 2: ]
766 #raise Exception( 'Unable to derive default datapath ID - '
767 # 'please either specify a dpid or use a '
768 # 'canonical switch name such as s23.' )
771 externalInterfaces = []
772 for row in range(self.tableFrame.rows):
773 # debug( 'Interface is ' + self.tableFrame.get(row, 0), '\n' )
774 if len(self.tableFrame.get(row, 0)) > 0:
775 externalInterfaces.append(self.tableFrame.get(row, 0))
777 dpid = self.dpidEntry.get()
778 if (self.defaultDpid(self.hostnameEntry.get()) is None
780 showerror(title="Error",
781 message= 'Unable to derive default datapath ID - '
782 'please either specify a DPID or use a '
783 'canonical switch name such as s23.' )
786 results = {'externalInterfaces':externalInterfaces,
787 'hostname':self.hostnameEntry.get(),
789 'startCommand':self.startEntry.get(),
790 'stopCommand':self.stopEntry.get(),
791 'sflow':str(self.sflow.get()),
792 'netflow':str(self.nflow.get()),
793 'dpctl':self.dpctlEntry.get(),
794 'switchIP':self.ipEntry.get()}
795 sw = self.switchType.get()
796 if sw == 'Indigo Virtual Switch':
797 results['switchType'] = 'ivs'
798 if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
800 showerror(title="Error",
801 message='MiniNet version 2.1+ required. You have '+VERSION+'.')
802 elif sw == 'Userspace Switch inNamespace':
803 results['switchType'] = 'userns'
804 elif sw == 'Userspace Switch':
805 results['switchType'] = 'user'
806 elif sw == 'Open vSwitch Kernel Mode':
807 results['switchType'] = 'ovs'
809 results['switchType'] = 'default'
810 self.result = results
813 class VerticalScrolledTable(LabelFrame):
814 """A pure Tkinter scrollable frame that actually works!
816 * Use the 'interior' attribute to place widgets inside the scrollable frame
817 * Construct and pack/place/grid normally
818 * This frame only allows vertical scrolling
821 def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw):
822 LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw)
824 # create a canvas object and a vertical scrollbar for scrolling it
825 vscrollbar = Scrollbar(self, orient=VERTICAL)
826 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
827 canvas = Canvas(self, bd=0, highlightthickness=0,
828 yscrollcommand=vscrollbar.set)
829 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
830 vscrollbar.config(command=canvas.yview)
833 canvas.xview_moveto(0)
834 canvas.yview_moveto(0)
836 # create a frame inside the canvas which will be scrolled with it
837 self.interior = interior = TableFrame(canvas, rows=rows, columns=columns)
838 interior_id = canvas.create_window(0, 0, window=interior,
841 # track changes to the canvas and frame width and sync them,
842 # also updating the scrollbar
843 def _configure_interior(_event):
844 # update the scrollbars to match the size of the inner frame
845 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
846 canvas.config(scrollregion="0 0 %s %s" % size)
847 if interior.winfo_reqwidth() != canvas.winfo_width():
848 # update the canvas's width to fit the inner frame
849 canvas.config(width=interior.winfo_reqwidth())
850 interior.bind('<Configure>', _configure_interior)
852 def _configure_canvas(_event):
853 if interior.winfo_reqwidth() != canvas.winfo_width():
854 # update the inner frame's width to fill the canvas
855 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
856 canvas.bind('<Configure>', _configure_canvas)
860 class TableFrame(Frame):
861 def __init__(self, parent, rows=2, columns=2):
863 Frame.__init__(self, parent, background="black")
866 self.columns = columns
867 for row in range(rows):
869 for column in range(columns):
870 label = Entry(self, borderwidth=0)
871 label.grid(row=row, column=column, sticky="wens", padx=1, pady=1)
872 current_row.append(label)
873 self._widgets.append(current_row)
875 def set(self, row, column, value):
876 widget = self._widgets[row][column]
877 widget.insert(0, value)
879 def get(self, row, column):
880 widget = self._widgets[row][column]
883 def addRow( self, value=None, readonly=False ):
884 # debug( "Adding row " + str(self.rows +1), '\n' )
886 for column in range(self.columns):
887 label = Entry(self, borderwidth=0)
888 label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1)
889 if value is not None:
890 label.insert(0, value[column])
892 label.configure(state='readonly')
893 current_row.append(label)
894 self._widgets.append(current_row)
895 self.update_idletasks()
898 class LinkDialog(tkSimpleDialog.Dialog):
900 def __init__(self, parent, title, linkDefaults):
902 self.linkValues = linkDefaults
904 tkSimpleDialog.Dialog.__init__(self, parent, title)
906 def body(self, master):
908 self.var = StringVar(master)
909 Label(master, text="Bandwidth:").grid(row=0, sticky=E)
910 self.e1 = Entry(master)
911 self.e1.grid(row=0, column=1)
912 Label(master, text="Mbit").grid(row=0, column=2, sticky=W)
913 if 'bw' in self.linkValues:
914 self.e1.insert(0,str(self.linkValues['bw']))
916 Label(master, text="Delay:").grid(row=1, sticky=E)
917 self.e2 = Entry(master)
918 self.e2.grid(row=1, column=1)
919 if 'delay' in self.linkValues:
920 self.e2.insert(0, self.linkValues['delay'])
922 Label(master, text="Loss:").grid(row=2, sticky=E)
923 self.e3 = Entry(master)
924 self.e3.grid(row=2, column=1)
925 Label(master, text="%").grid(row=2, column=2, sticky=W)
926 if 'loss' in self.linkValues:
927 self.e3.insert(0, str(self.linkValues['loss']))
929 Label(master, text="Max Queue size:").grid(row=3, sticky=E)
930 self.e4 = Entry(master)
931 self.e4.grid(row=3, column=1)
932 if 'max_queue_size' in self.linkValues:
933 self.e4.insert(0, str(self.linkValues['max_queue_size']))
935 Label(master, text="Jitter:").grid(row=4, sticky=E)
936 self.e5 = Entry(master)
937 self.e5.grid(row=4, column=1)
938 if 'jitter' in self.linkValues:
939 self.e5.insert(0, self.linkValues['jitter'])
941 Label(master, text="Speedup:").grid(row=5, sticky=E)
942 self.e6 = Entry(master)
943 self.e6.grid(row=5, column=1)
944 if 'speedup' in self.linkValues:
945 self.e6.insert(0, str(self.linkValues['speedup']))
947 return self.e1 # initial focus
951 if len(self.e1.get()) > 0:
952 self.result['bw'] = int(self.e1.get())
953 if len(self.e2.get()) > 0:
954 self.result['delay'] = self.e2.get()
955 if len(self.e3.get()) > 0:
956 self.result['loss'] = int(self.e3.get())
957 if len(self.e4.get()) > 0:
958 self.result['max_queue_size'] = int(self.e4.get())
959 if len(self.e5.get()) > 0:
960 self.result['jitter'] = self.e5.get()
961 if len(self.e6.get()) > 0:
962 self.result['speedup'] = int(self.e6.get())
964 class ControllerDialog(tkSimpleDialog.Dialog):
966 def __init__(self, parent, title, ctrlrDefaults=None):
969 self.ctrlrValues = ctrlrDefaults
971 tkSimpleDialog.Dialog.__init__(self, parent, title)
973 def body(self, master):
975 self.var = StringVar(master)
976 self.protcolvar = StringVar(master)
980 Label(master, text="Name:").grid(row=rowCount, sticky=E)
981 self.hostnameEntry = Entry(master)
982 self.hostnameEntry.grid(row=rowCount, column=1)
983 self.hostnameEntry.insert(0, self.ctrlrValues['hostname'])
986 # Field for Remove Controller Port
987 Label(master, text="Controller Port:").grid(row=rowCount, sticky=E)
988 self.e2 = Entry(master)
989 self.e2.grid(row=rowCount, column=1)
990 self.e2.insert(0, self.ctrlrValues['remotePort'])
993 # Field for Controller Type
994 Label(master, text="Controller Type:").grid(row=rowCount, sticky=E)
995 controllerType = self.ctrlrValues['controllerType']
996 self.o1 = OptionMenu(master, self.var, "Remote Controller", "In-Band Controller", "OpenFlow Reference", "OVS Controller")
997 self.o1.grid(row=rowCount, column=1, sticky=W)
998 if controllerType == 'ref':
999 self.var.set("OpenFlow Reference")
1000 elif controllerType == 'inband':
1001 self.var.set("In-Band Controller")
1002 elif controllerType == 'remote':
1003 self.var.set("Remote Controller")
1005 self.var.set("OVS Controller")
1008 # Field for Controller Protcol
1009 Label(master, text="Protocol:").grid(row=rowCount, sticky=E)
1010 if 'controllerProtocol' in self.ctrlrValues:
1011 controllerProtocol = self.ctrlrValues['controllerProtocol']
1013 controllerProtocol = 'tcp'
1014 self.protcol = OptionMenu(master, self.protcolvar, "TCP", "SSL")
1015 self.protcol.grid(row=rowCount, column=1, sticky=W)
1016 if controllerProtocol == 'ssl':
1017 self.protcolvar.set("SSL")
1019 self.protcolvar.set("TCP")
1022 # Field for Remove Controller IP
1023 remoteFrame= LabelFrame(master, text='Remote/In-Band Controller', padx=5, pady=5)
1024 remoteFrame.grid(row=rowCount, column=0, columnspan=2, sticky=W)
1026 Label(remoteFrame, text="IP Address:").grid(row=0, sticky=E)
1027 self.e1 = Entry(remoteFrame)
1028 self.e1.grid(row=0, column=1)
1029 self.e1.insert(0, self.ctrlrValues['remoteIP'])
1032 return self.hostnameEntry # initial focus
1035 self.result = { 'hostname': self.hostnameEntry.get(),
1036 'remoteIP': self.e1.get(),
1037 'remotePort': int(self.e2.get())}
1039 controllerType = self.var.get()
1040 if controllerType == 'Remote Controller':
1041 self.result['controllerType'] = 'remote'
1042 elif controllerType == 'In-Band Controller':
1043 self.result['controllerType'] = 'inband'
1044 elif controllerType == 'OpenFlow Reference':
1045 self.result['controllerType'] = 'ref'
1047 self.result['controllerType'] = 'ovsc'
1048 controllerProtocol = self.protcolvar.get()
1049 if controllerProtocol == 'SSL':
1050 self.result['controllerProtocol'] = 'ssl'
1052 self.result['controllerProtocol'] = 'tcp'
1054 class ToolTip(object):
1056 def __init__(self, widget):
1057 self.widget = widget
1058 self.tipwindow = None
1062 def showtip(self, text):
1063 "Display text in tooltip window"
1065 if self.tipwindow or not self.text:
1067 x, y, _cx, cy = self.widget.bbox("insert")
1068 x = x + self.widget.winfo_rootx() + 27
1069 y = y + cy + self.widget.winfo_rooty() +27
1070 self.tipwindow = tw = Toplevel(self.widget)
1071 tw.wm_overrideredirect(1)
1072 tw.wm_geometry("+%d+%d" % (x, y))
1075 # pylint: disable=protected-access
1076 tw.tk.call("::tk::unsupported::MacWindowStyle",
1078 "help", "noActivates")
1079 # pylint: enable=protected-access
1082 label = Label(tw, text=self.text, justify=LEFT,
1083 background="#ffffe0", relief=SOLID, borderwidth=1,
1084 font=("tahoma", "8", "normal"))
1089 self.tipwindow = None
1093 class MiniEdit( Frame ):
1095 "A simple network editor for Mininet."
1097 def __init__( self, parent=None, cheight=600, cwidth=1000 ):
1099 self.defaultIpBase='10.0.0.0/8'
1101 self.nflowDefaults = {'nflowTarget':'',
1102 'nflowTimeout':'600',
1104 self.sflowDefaults = {'sflowTarget':'',
1105 'sflowSampling':'400',
1106 'sflowHeader':'128',
1107 'sflowPolling':'30'}
1110 "ipBase": self.defaultIpBase,
1112 "terminalType": 'xterm',
1113 "switchType": 'ovs',
1115 'sflow':self.sflowDefaults,
1116 'netflow':self.nflowDefaults,
1117 'openFlowVersions':{'ovsOf10':'1',
1125 Frame.__init__( self, parent )
1127 self.appName = 'MiniEdit'
1128 self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" )
1131 self.font = ( 'Geneva', 9 )
1132 self.smallFont = ( 'Geneva', 7 )
1136 self.top = self.winfo_toplevel()
1137 self.top.title( self.appName )
1140 self.createMenubar()
1143 self.cheight, self.cwidth = cheight, cwidth
1144 self.cframe, self.canvas = self.createCanvas()
1147 self.controllers = {}
1150 self.images = miniEditImages()
1153 self.tools = ( 'Select', 'Host', 'Switch', 'LegacySwitch', 'LegacyRouter', 'NetLink', 'Controller' )
1154 self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
1155 self.toolbar = self.createToolbar()
1158 self.toolbar.grid( column=0, row=0, sticky='nsew')
1159 self.cframe.grid( column=1, row=0 )
1160 self.columnconfigure( 1, weight=1 )
1161 self.rowconfigure( 0, weight=1 )
1162 self.pack( expand=True, fill='both' )
1165 self.aboutBox = None
1167 # Initialize node data
1168 self.nodeBindings = self.createNodeBindings()
1169 self.nodePrefixes = { 'LegacyRouter': 'r', 'LegacySwitch': 's', 'Switch': 's', 'Host': 'h' , 'Controller': 'c'}
1170 self.widgetToItem = {}
1171 self.itemToWidget = {}
1173 # Initialize link tool
1174 self.link = self.linkWidget = None
1177 self.selection = None
1180 self.bind( '<Control-q>', lambda event: self.quit() )
1181 self.bind( '<KeyPress-Delete>', self.deleteSelection )
1182 self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
1185 self.hostPopup = Menu(self.top, tearoff=0)
1186 self.hostPopup.add_command(label='Host Options', font=self.font)
1187 self.hostPopup.add_separator()
1188 self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails )
1190 self.hostRunPopup = Menu(self.top, tearoff=0)
1191 self.hostRunPopup.add_command(label='Host Options', font=self.font)
1192 self.hostRunPopup.add_separator()
1193 self.hostRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
1195 self.legacyRouterRunPopup = Menu(self.top, tearoff=0)
1196 self.legacyRouterRunPopup.add_command(label='Router Options', font=self.font)
1197 self.legacyRouterRunPopup.add_separator()
1198 self.legacyRouterRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
1200 self.switchPopup = Menu(self.top, tearoff=0)
1201 self.switchPopup.add_command(label='Switch Options', font=self.font)
1202 self.switchPopup.add_separator()
1203 self.switchPopup.add_command(label='Properties', font=self.font, command=self.switchDetails )
1205 self.switchRunPopup = Menu(self.top, tearoff=0)
1206 self.switchRunPopup.add_command(label='Switch Options', font=self.font)
1207 self.switchRunPopup.add_separator()
1208 self.switchRunPopup.add_command(label='List bridge details', font=self.font, command=self.listBridge )
1210 self.linkPopup = Menu(self.top, tearoff=0)
1211 self.linkPopup.add_command(label='Link Options', font=self.font)
1212 self.linkPopup.add_separator()
1213 self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails )
1215 self.linkRunPopup = Menu(self.top, tearoff=0)
1216 self.linkRunPopup.add_command(label='Link Options', font=self.font)
1217 self.linkRunPopup.add_separator()
1218 self.linkRunPopup.add_command(label='Link Up', font=self.font, command=self.linkUp )
1219 self.linkRunPopup.add_command(label='Link Down', font=self.font, command=self.linkDown )
1221 self.controllerPopup = Menu(self.top, tearoff=0)
1222 self.controllerPopup.add_command(label='Controller Options', font=self.font)
1223 self.controllerPopup.add_separator()
1224 self.controllerPopup.add_command(label='Properties', font=self.font, command=self.controllerDetails )
1227 # Event handling initalization
1228 self.linkx = self.linky = self.linkItem = None
1229 self.lastSelection = None
1231 # Model initialization
1234 self.switchOpts = {}
1236 self.switchCount = 0
1237 self.controllerCount = 0
1240 # Close window gracefully
1241 Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
1244 "Stop our network, if any, then quit."
1248 def createMenubar( self ):
1249 "Create our menu bar."
1253 mbar = Menu( self.top, font=font )
1254 self.top.configure( menu=mbar )
1257 fileMenu = Menu( mbar, tearoff=False )
1258 mbar.add_cascade( label="File", font=font, menu=fileMenu )
1259 fileMenu.add_command( label="New", font=font, command=self.newTopology )
1260 fileMenu.add_command( label="Open", font=font, command=self.loadTopology )
1261 fileMenu.add_command( label="Save", font=font, command=self.saveTopology )
1262 fileMenu.add_command( label="Export Level 2 Script", font=font, command=self.exportScript )
1263 fileMenu.add_separator()
1264 fileMenu.add_command( label='Quit', command=self.quit, font=font )
1266 editMenu = Menu( mbar, tearoff=False )
1267 mbar.add_cascade( label="Edit", font=font, menu=editMenu )
1268 editMenu.add_command( label="Cut", font=font,
1269 command=lambda: self.deleteSelection( None ) )
1270 editMenu.add_command( label="Preferences", font=font, command=self.prefDetails)
1272 runMenu = Menu( mbar, tearoff=False )
1273 mbar.add_cascade( label="Run", font=font, menu=runMenu )
1274 runMenu.add_command( label="Run", font=font, command=self.doRun )
1275 runMenu.add_command( label="Stop", font=font, command=self.doStop )
1276 fileMenu.add_separator()
1277 runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow )
1278 runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal )
1281 appMenu = Menu( mbar, tearoff=False )
1282 mbar.add_cascade( label="Help", font=font, menu=appMenu )
1283 appMenu.add_command( label='About MiniEdit', command=self.about,
1287 def createCanvas( self ):
1288 "Create and return our scrolling canvas frame."
1291 canvas = Canvas( f, width=self.cwidth, height=self.cheight,
1295 xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
1296 ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
1297 canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
1300 resize = Label( f, bg='white' )
1303 canvas.grid( row=0, column=1, sticky='nsew')
1304 ybar.grid( row=0, column=2, sticky='ns')
1305 xbar.grid( row=1, column=1, sticky='ew' )
1306 resize.grid( row=1, column=2, sticky='nsew' )
1309 f.rowconfigure( 0, weight=1 )
1310 f.columnconfigure( 1, weight=1 )
1311 f.grid( row=0, column=0, sticky='nsew' )
1312 f.bind( '<Configure>', lambda event: self.updateScrollRegion() )
1315 canvas.bind( '<ButtonPress-1>', self.clickCanvas )
1316 canvas.bind( '<B1-Motion>', self.dragCanvas )
1317 canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
1321 def updateScrollRegion( self ):
1322 "Update canvas scroll region to hold everything."
1323 bbox = self.canvas.bbox( 'all' )
1324 if bbox is not None:
1325 self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ],
1328 def canvasx( self, x_root ):
1329 "Convert root x coordinate to canvas coordinate."
1331 return c.canvasx( x_root ) - c.winfo_rootx()
1333 def canvasy( self, y_root ):
1334 "Convert root y coordinate to canvas coordinate."
1336 return c.canvasy( y_root ) - c.winfo_rooty()
1340 def activate( self, toolName ):
1341 "Activate a tool and press its button."
1342 # Adjust button appearance
1344 self.buttons[ self.active ].configure( relief='raised' )
1345 self.buttons[ toolName ].configure( relief='sunken' )
1346 # Activate dynamic bindings
1347 self.active = toolName
1351 def createToolTip(widget, text):
1352 toolTip = ToolTip(widget)
1354 toolTip.showtip(text)
1357 widget.bind('<Enter>', enter)
1358 widget.bind('<Leave>', leave)
1360 def createToolbar( self ):
1361 "Create and return our toolbar frame."
1363 toolbar = Frame( self )
1366 for tool in self.tools:
1367 cmd = ( lambda t=tool: self.activate( t ) )
1368 b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
1369 if tool in self.images:
1370 b.config( height=35, image=self.images[ tool ] )
1371 self.createToolTip(b, str(tool))
1372 # b.config( compound='top' )
1374 self.buttons[ tool ] = b
1375 self.activate( self.tools[ 0 ] )
1378 Label( toolbar, text='' ).pack()
1381 for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]:
1382 doCmd = getattr( self, 'do' + cmd )
1383 b = Button( toolbar, text=cmd, font=self.smallFont,
1384 fg=color, command=doCmd )
1385 b.pack( fill='x', side='bottom' )
1391 self.activate( 'Select' )
1392 for tool in self.tools:
1393 self.buttons[ tool ].config( state='disabled' )
1399 for tool in self.tools:
1400 self.buttons[ tool ].config( state='normal' )
1402 def addNode( self, node, nodeNum, x, y, name=None):
1403 "Add a new node to our canvas."
1404 if 'Switch' == node:
1405 self.switchCount += 1
1408 if 'Controller' == node:
1409 self.controllerCount += 1
1411 name = self.nodePrefixes[ node ] + nodeNum
1412 self.addNamedNode(node, name, x, y)
1414 def addNamedNode( self, node, name, x, y):
1415 "Add a new node to our canvas."
1416 icon = self.nodeIcon( node, name )
1417 item = self.canvas.create_window( x, y, anchor='c', window=icon,
1419 self.widgetToItem[ icon ] = item
1420 self.itemToWidget[ item ] = icon
1423 def convertJsonUnicode(self, text):
1424 "Some part of Mininet don't like Unicode"
1425 if isinstance(text, dict):
1426 return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in text.items()}
1427 elif isinstance(text, list):
1428 return [self.convertJsonUnicode(element) for element in text]
1429 elif isinstance(text, unicode):
1430 return text.encode('utf-8')
1434 def loadTopology( self ):
1439 ('Mininet Topology','*.mn'),
1442 f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
1446 loadedTopology = self.convertJsonUnicode(json.load(f))
1448 # Load application preferences
1449 if 'application' in loadedTopology:
1450 self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items())
1451 if "ovsOf10" not in self.appPrefs["openFlowVersions"]:
1452 self.appPrefs["openFlowVersions"]["ovsOf10"] = '0'
1453 if "ovsOf11" not in self.appPrefs["openFlowVersions"]:
1454 self.appPrefs["openFlowVersions"]["ovsOf11"] = '0'
1455 if "ovsOf12" not in self.appPrefs["openFlowVersions"]:
1456 self.appPrefs["openFlowVersions"]["ovsOf12"] = '0'
1457 if "ovsOf13" not in self.appPrefs["openFlowVersions"]:
1458 self.appPrefs["openFlowVersions"]["ovsOf13"] = '0'
1459 if "sflow" not in self.appPrefs:
1460 self.appPrefs["sflow"] = self.sflowDefaults
1461 if "netflow" not in self.appPrefs:
1462 self.appPrefs["netflow"] = self.nflowDefaults
1465 if 'controllers' in loadedTopology:
1466 if loadedTopology['version'] == '1':
1467 # This is old location of controller info
1469 self.controllers = {}
1470 self.controllers[hostname] = loadedTopology['controllers']['c0']
1471 self.controllers[hostname]['hostname'] = hostname
1472 self.addNode('Controller', 0, float(30), float(30), name=hostname)
1473 icon = self.findWidgetByName(hostname)
1474 icon.bind('<Button-3>', self.do_controllerPopup )
1476 controllers = loadedTopology['controllers']
1477 for controller in controllers:
1478 hostname = controller['opts']['hostname']
1481 self.addNode('Controller', 0, float(x), float(y), name=hostname)
1482 self.controllers[hostname] = controller['opts']
1483 icon = self.findWidgetByName(hostname)
1484 icon.bind('<Button-3>', self.do_controllerPopup )
1488 hosts = loadedTopology['hosts']
1490 nodeNum = host['number']
1491 hostname = 'h'+nodeNum
1492 if 'hostname' in host['opts']:
1493 hostname = host['opts']['hostname']
1495 host['opts']['hostname'] = hostname
1496 if 'nodeNum' not in host['opts']:
1497 host['opts']['nodeNum'] = int(nodeNum)
1500 self.addNode('Host', nodeNum, float(x), float(y), name=hostname)
1502 # Fix JSON converting tuple to list when saving
1503 if 'privateDirectory' in host['opts']:
1505 for privateDir in host['opts']['privateDirectory']:
1506 if isinstance( privateDir, list ):
1507 newDirList.append((privateDir[0],privateDir[1]))
1509 newDirList.append(privateDir)
1510 host['opts']['privateDirectory'] = newDirList
1511 self.hostOpts[hostname] = host['opts']
1512 icon = self.findWidgetByName(hostname)
1513 icon.bind('<Button-3>', self.do_hostPopup )
1516 switches = loadedTopology['switches']
1517 for switch in switches:
1518 nodeNum = switch['number']
1519 hostname = 's'+nodeNum
1520 if 'controllers' not in switch['opts']:
1521 switch['opts']['controllers'] = []
1522 if 'switchType' not in switch['opts']:
1523 switch['opts']['switchType'] = 'default'
1524 if 'hostname' in switch['opts']:
1525 hostname = switch['opts']['hostname']
1527 switch['opts']['hostname'] = hostname
1528 if 'nodeNum' not in switch['opts']:
1529 switch['opts']['nodeNum'] = int(nodeNum)
1532 if switch['opts']['switchType'] == "legacyRouter":
1533 self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname)
1534 icon = self.findWidgetByName(hostname)
1535 icon.bind('<Button-3>', self.do_legacyRouterPopup )
1536 elif switch['opts']['switchType'] == "legacySwitch":
1537 self.addNode('LegacySwitch', nodeNum, float(x), float(y), name=hostname)
1538 icon = self.findWidgetByName(hostname)
1539 icon.bind('<Button-3>', self.do_legacySwitchPopup )
1541 self.addNode('Switch', nodeNum, float(x), float(y), name=hostname)
1542 icon = self.findWidgetByName(hostname)
1543 icon.bind('<Button-3>', self.do_switchPopup )
1544 self.switchOpts[hostname] = switch['opts']
1546 # create links to controllers
1547 if int(loadedTopology['version']) > 1:
1548 controllers = self.switchOpts[hostname]['controllers']
1549 for controller in controllers:
1550 dest = self.findWidgetByName(controller)
1551 dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1552 self.link = self.canvas.create_line(float(x),
1560 c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
1561 self.addLink( icon, dest, linktype='control' )
1562 self.createControlLinkBindings()
1563 self.link = self.linkWidget = None
1565 dest = self.findWidgetByName('c0')
1566 dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1567 self.link = self.canvas.create_line(float(x),
1575 c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
1576 self.addLink( icon, dest, linktype='control' )
1577 self.createControlLinkBindings()
1578 self.link = self.linkWidget = None
1581 links = loadedTopology['links']
1583 srcNode = link['src']
1584 src = self.findWidgetByName(srcNode)
1585 sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
1587 destNode = link['dest']
1588 dest = self.findWidgetByName(destNode)
1589 dx, dy = self.canvas.coords( self.widgetToItem[ dest] )
1591 self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
1592 fill='blue', tag='link' )
1593 c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
1594 self.addLink( src, dest, linkopts=link['opts'] )
1595 self.createDataLinkBindings()
1596 self.link = self.linkWidget = None
1600 def findWidgetByName( self, name ):
1601 for widget in self.widgetToItem:
1602 if name == widget[ 'text' ]:
1605 def newTopology( self ):
1607 for widget in self.widgetToItem.keys():
1608 self.deleteItem( self.widgetToItem[ widget ] )
1610 self.switchCount = 0
1611 self.controllerCount = 0
1614 self.switchOpts = {}
1615 self.controllers = {}
1616 self.appPrefs["ipBase"]= self.defaultIpBase
1618 def saveTopology( self ):
1621 ('Mininet Topology','*.mn'),
1625 savingDictionary = {}
1626 fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...")
1627 if len(fileName ) > 0:
1628 # Save Application preferences
1629 savingDictionary['version'] = '2'
1631 # Save Switches and Hosts
1634 controllersToSave = []
1635 for widget in self.widgetToItem:
1636 name = widget[ 'text' ]
1637 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1638 x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] )
1639 if 'Switch' in tags or 'LegacySwitch' in tags or 'LegacyRouter' in tags:
1640 nodeNum = self.switchOpts[name]['nodeNum']
1641 nodeToSave = {'number':str(nodeNum),
1644 'opts':self.switchOpts[name] }
1645 switchesToSave.append(nodeToSave)
1646 elif 'Host' in tags:
1647 nodeNum = self.hostOpts[name]['nodeNum']
1648 nodeToSave = {'number':str(nodeNum),
1651 'opts':self.hostOpts[name] }
1652 hostsToSave.append(nodeToSave)
1653 elif 'Controller' in tags:
1654 nodeToSave = {'x':str(x1),
1656 'opts':self.controllers[name] }
1657 controllersToSave.append(nodeToSave)
1659 raise Exception( "Cannot create mystery node: " + name )
1660 savingDictionary['hosts'] = hostsToSave
1661 savingDictionary['switches'] = switchesToSave
1662 savingDictionary['controllers'] = controllersToSave
1666 for link in self.links.values():
1669 linkopts = link['linkOpts']
1671 srcName, dstName = src[ 'text' ], dst[ 'text' ]
1672 linkToSave = {'src':srcName,
1675 if link['type'] == 'data':
1676 linksToSave.append(linkToSave)
1677 savingDictionary['links'] = linksToSave
1679 # Save Application preferences
1680 savingDictionary['application'] = self.appPrefs
1683 f = open(fileName, 'wb')
1684 f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': ')))
1685 # pylint: disable=broad-except
1686 except Exception as er:
1688 # pylint: enable=broad-except
1692 def exportScript( self ):
1695 ('Mininet Custom Topology','*.py'),
1699 fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...")
1700 if len(fileName ) > 0:
1701 # debug( "Now saving under %s\n" % fileName )
1702 f = open(fileName, 'wb')
1704 f.write("#!/usr/bin/python\n")
1706 f.write("from mininet.net import Mininet\n")
1707 f.write("from mininet.node import Controller, RemoteController, OVSController\n")
1708 f.write("from mininet.node import CPULimitedHost, Host, Node\n")
1709 f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n")
1710 if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
1711 f.write("from mininet.node import IVSSwitch\n")
1712 f.write("from mininet.cli import CLI\n")
1713 f.write("from mininet.log import setLogLevel, info\n")
1714 f.write("from mininet.link import TCLink, Intf\n")
1715 f.write("from subprocess import call\n")
1718 for widget in self.widgetToItem:
1719 name = widget[ 'text' ]
1720 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1722 if 'Controller' in tags:
1723 opts = self.controllers[name]
1724 controllerType = opts['controllerType']
1725 if controllerType == 'inband':
1728 if inBandCtrl == True:
1730 f.write("class InbandController( RemoteController ):\n")
1732 f.write(" def checkListening( self ):\n")
1733 f.write(" \"Overridden to do nothing.\"\n")
1734 f.write(" return\n")
1737 f.write("def myNetwork():\n")
1739 f.write(" net = Mininet( topo=None,\n")
1740 if len(self.appPrefs['dpctl']) > 0:
1741 f.write(" listenPort="+self.appPrefs['dpctl']+",\n")
1742 f.write(" build=False,\n")
1743 f.write(" ipBase='"+self.appPrefs['ipBase']+"')\n")
1745 f.write(" info( '*** Adding controller\\n' )\n")
1746 for widget in self.widgetToItem:
1747 name = widget[ 'text' ]
1748 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1750 if 'Controller' in tags:
1751 opts = self.controllers[name]
1752 controllerType = opts['controllerType']
1753 if 'controllerProtocol' in opts:
1754 controllerProtocol = opts['controllerProtocol']
1756 controllerProtocol = 'tcp'
1757 controllerIP = opts['remoteIP']
1758 controllerPort = opts['remotePort']
1761 f.write(" "+name+"=net.addController(name='"+name+"',\n")
1763 if controllerType == 'remote':
1764 f.write(" controller=RemoteController,\n")
1765 f.write(" ip='"+controllerIP+"',\n")
1766 elif controllerType == 'inband':
1767 f.write(" controller=InbandController,\n")
1768 f.write(" ip='"+controllerIP+"',\n")
1769 elif controllerType == 'ovsc':
1770 f.write(" controller=OVSController,\n")
1772 f.write(" controller=Controller,\n")
1774 f.write(" protocol='"+controllerProtocol+"',\n")
1775 f.write(" port="+str(controllerPort)+")\n")
1778 # Save Switches and Hosts
1779 f.write(" info( '*** Add switches\\n')\n")
1780 for widget in self.widgetToItem:
1781 name = widget[ 'text' ]
1782 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1783 if 'LegacyRouter' in tags:
1784 f.write(" "+name+" = net.addHost('"+name+"', cls=Node, ip='0.0.0.0')\n")
1785 f.write(" "+name+".cmd('sysctl -w net.ipv4.ip_forward=1')\n")
1786 if 'LegacySwitch' in tags:
1787 f.write(" "+name+" = net.addSwitch('"+name+"', cls=OVSKernelSwitch, failMode='standalone')\n")
1788 if 'Switch' in tags:
1789 opts = self.switchOpts[name]
1790 nodeNum = opts['nodeNum']
1791 f.write(" "+name+" = net.addSwitch('"+name+"'")
1792 if opts['switchType'] == 'default':
1793 if self.appPrefs['switchType'] == 'ivs':
1794 f.write(", cls=IVSSwitch")
1795 elif self.appPrefs['switchType'] == 'user':
1796 f.write(", cls=UserSwitch")
1797 elif self.appPrefs['switchType'] == 'userns':
1798 f.write(", cls=UserSwitch, inNamespace=True")
1800 f.write(", cls=OVSKernelSwitch")
1801 elif opts['switchType'] == 'ivs':
1802 f.write(", cls=IVSSwitch")
1803 elif opts['switchType'] == 'user':
1804 f.write(", cls=UserSwitch")
1805 elif opts['switchType'] == 'userns':
1806 f.write(", cls=UserSwitch, inNamespace=True")
1808 f.write(", cls=OVSKernelSwitch")
1810 f.write(", listenPort="+opts['dpctl'])
1812 f.write(", dpid='"+opts['dpid']+"'")
1814 if 'externalInterfaces' in opts:
1815 for extInterface in opts['externalInterfaces']:
1816 f.write(" Intf( '"+extInterface+"', node="+name+" )\n")
1819 f.write(" info( '*** Add hosts\\n')\n")
1820 for widget in self.widgetToItem:
1821 name = widget[ 'text' ]
1822 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1824 opts = self.hostOpts[name]
1827 if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
1828 defaultRoute = "'via "+opts['defaultRoute']+"'"
1830 defaultRoute = 'None'
1831 if 'ip' in opts and len(opts['ip']) > 0:
1834 nodeNum = self.hostOpts[name]['nodeNum']
1835 ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
1836 ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
1838 if 'cores' in opts or 'cpu' in opts:
1839 f.write(" "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
1841 f.write(" "+name+".setCPUs(cores='"+opts['cores']+"')\n")
1843 f.write(" "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n")
1845 f.write(" "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
1846 if 'externalInterfaces' in opts:
1847 for extInterface in opts['externalInterfaces']:
1848 f.write(" Intf( '"+extInterface+"', node="+name+" )\n")
1852 f.write(" info( '*** Add links\\n')\n")
1853 for key,linkDetail in self.links.items():
1854 tags = self.canvas.gettags(key)
1857 src = linkDetail['src']
1858 dst = linkDetail['dest']
1859 linkopts = linkDetail['linkOpts']
1860 srcName, dstName = src[ 'text' ], dst[ 'text' ]
1864 # max_queue_size = ''
1866 if 'bw' in linkopts:
1868 linkOpts = linkOpts + "'bw':"+str(bw)
1870 if 'delay' in linkopts:
1871 # delay = linkopts['delay']
1873 linkOpts = linkOpts + ","
1874 linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'"
1876 if 'loss' in linkopts:
1878 linkOpts = linkOpts + ","
1879 linkOpts = linkOpts + "'loss':"+str(linkopts['loss'])
1881 if 'max_queue_size' in linkopts:
1883 linkOpts = linkOpts + ","
1884 linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size'])
1886 if 'jitter' in linkopts:
1888 linkOpts = linkOpts + ","
1889 linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'"
1891 if 'speedup' in linkopts:
1893 linkOpts = linkOpts + ","
1894 linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup'])
1897 linkOpts = linkOpts + "}"
1899 f.write(" "+srcName+dstName+" = "+linkOpts+"\n")
1900 f.write(" net.addLink("+srcName+", "+dstName)
1902 f.write(", cls=TCLink , **"+srcName+dstName)
1906 f.write(" info( '*** Starting network\\n')\n")
1907 f.write(" net.build()\n")
1909 f.write(" info( '*** Starting controllers\\n')\n")
1910 f.write(" for controller in net.controllers:\n")
1911 f.write(" controller.start()\n")
1914 f.write(" info( '*** Starting switches\\n')\n")
1915 for widget in self.widgetToItem:
1916 name = widget[ 'text' ]
1917 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1918 if 'Switch' in tags or 'LegacySwitch' in tags:
1919 opts = self.switchOpts[name]
1920 ctrlList = ",".join(opts['controllers'])
1921 f.write(" net.get('"+name+"').start(["+ctrlList+"])\n")
1925 f.write(" info( '*** Post configure switches and hosts\\n')\n")
1926 for widget in self.widgetToItem:
1927 name = widget[ 'text' ]
1928 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1929 if 'Switch' in tags:
1930 opts = self.switchOpts[name]
1931 if opts['switchType'] == 'default':
1932 if self.appPrefs['switchType'] == 'user':
1933 if 'switchIP' in opts:
1934 if len(opts['switchIP']) > 0:
1935 f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1936 elif self.appPrefs['switchType'] == 'userns':
1937 if 'switchIP' in opts:
1938 if len(opts['switchIP']) > 0:
1939 f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n")
1940 elif self.appPrefs['switchType'] == 'ovs':
1941 if 'switchIP' in opts:
1942 if len(opts['switchIP']) > 0:
1943 f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1944 elif opts['switchType'] == 'user':
1945 if 'switchIP' in opts:
1946 if len(opts['switchIP']) > 0:
1947 f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1948 elif opts['switchType'] == 'userns':
1949 if 'switchIP' in opts:
1950 if len(opts['switchIP']) > 0:
1951 f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n")
1952 elif opts['switchType'] == 'ovs':
1953 if 'switchIP' in opts:
1954 if len(opts['switchIP']) > 0:
1955 f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1956 for widget in self.widgetToItem:
1957 name = widget[ 'text' ]
1958 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1960 opts = self.hostOpts[name]
1961 # Attach vlan interfaces
1962 if 'vlanInterfaces' in opts:
1963 for vlanInterface in opts['vlanInterfaces']:
1964 f.write(" "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n")
1965 f.write(" "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n")
1966 # Run User Defined Start Command
1967 if 'startCommand' in opts:
1968 f.write(" "+name+".cmdPrint('"+opts['startCommand']+"')\n")
1969 if 'Switch' in tags:
1970 opts = self.switchOpts[name]
1971 # Run User Defined Start Command
1972 if 'startCommand' in opts:
1973 f.write(" "+name+".cmdPrint('"+opts['startCommand']+"')\n")
1976 nflowValues = self.appPrefs['netflow']
1977 if len(nflowValues['nflowTarget']) > 0:
1978 nflowEnabled = False
1980 for widget in self.widgetToItem:
1981 name = widget[ 'text' ]
1982 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1984 if 'Switch' in tags:
1985 opts = self.switchOpts[name]
1986 if 'netflow' in opts:
1987 if opts['netflow'] == '1':
1988 nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF'
1991 nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout']
1992 if nflowValues['nflowAddId'] == '1':
1993 nflowCmd = nflowCmd + ' add_id_to_interface=true'
1995 nflowCmd = nflowCmd + ' add_id_to_interface=false'
1997 f.write(" call('"+nflowCmd+nflowSwitches+"', shell=True)\n")
2000 sflowValues = self.appPrefs['sflow']
2001 if len(sflowValues['sflowTarget']) > 0:
2002 sflowEnabled = False
2004 for widget in self.widgetToItem:
2005 name = widget[ 'text' ]
2006 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2008 if 'Switch' in tags:
2009 opts = self.switchOpts[name]
2011 if opts['sflow'] == '1':
2012 sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
2015 sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling']
2017 f.write(" call('"+sflowCmd+sflowSwitches+"', shell=True)\n")
2020 f.write(" CLI(net)\n")
2021 for widget in self.widgetToItem:
2022 name = widget[ 'text' ]
2023 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2025 opts = self.hostOpts[name]
2026 # Run User Defined Stop Command
2027 if 'stopCommand' in opts:
2028 f.write(" "+name+".cmdPrint('"+opts['stopCommand']+"')\n")
2029 if 'Switch' in tags:
2030 opts = self.switchOpts[name]
2031 # Run User Defined Stop Command
2032 if 'stopCommand' in opts:
2033 f.write(" "+name+".cmdPrint('"+opts['stopCommand']+"')\n")
2035 f.write(" net.stop()\n")
2037 f.write("if __name__ == '__main__':\n")
2038 f.write(" setLogLevel( 'info' )\n")
2039 f.write(" myNetwork()\n")
2046 # Generic canvas handler
2048 # We could have used bindtags, as in nodeIcon, but
2049 # the dynamic approach used here
2050 # may actually require less code. In any case, it's an
2051 # interesting introspection-based alternative to bindtags.
2053 def canvasHandle( self, eventName, event ):
2054 "Generic canvas event handler"
2055 if self.active is None:
2057 toolName = self.active
2058 handler = getattr( self, eventName + toolName, None )
2059 if handler is not None:
2062 def clickCanvas( self, event ):
2063 "Canvas click handler."
2064 self.canvasHandle( 'click', event )
2066 def dragCanvas( self, event ):
2067 "Canvas drag handler."
2068 self.canvasHandle( 'drag', event )
2070 def releaseCanvas( self, event ):
2071 "Canvas mouse up handler."
2072 self.canvasHandle( 'release', event )
2074 # Currently the only items we can select directly are
2075 # links. Nodes are handled by bindings in the node icon.
2077 def findItem( self, x, y ):
2078 "Find items at a location in our canvas."
2079 items = self.canvas.find_overlapping( x, y, x, y )
2080 if len( items ) == 0:
2085 # Canvas bindings for Select, Host, Switch and Link tools
2087 def clickSelect( self, event ):
2089 self.selectItem( self.findItem( event.x, event.y ) )
2091 def deleteItem( self, item ):
2093 # Don't delete while network is running
2094 if self.buttons[ 'Select' ][ 'state' ] == 'disabled':
2097 if item in self.links:
2098 self.deleteLink( item )
2099 if item in self.itemToWidget:
2100 self.deleteNode( item )
2102 self.canvas.delete( item )
2104 def deleteSelection( self, _event ):
2105 "Delete the selected item."
2106 if self.selection is not None:
2107 self.deleteItem( self.selection )
2108 self.selectItem( None )
2110 def nodeIcon( self, node, name ):
2111 "Create a new node icon."
2112 icon = Button( self.canvas, image=self.images[ node ],
2113 text=name, compound='top' )
2114 # Unfortunately bindtags wants a tuple
2115 bindtags = [ str( self.nodeBindings ) ]
2116 bindtags += list( icon.bindtags() )
2117 icon.bindtags( tuple( bindtags ) )
2120 def newNode( self, node, event ):
2121 "Add a new node to our canvas."
2123 x, y = c.canvasx( event.x ), c.canvasy( event.y )
2124 name = self.nodePrefixes[ node ]
2125 if 'Switch' == node:
2126 self.switchCount += 1
2127 name = self.nodePrefixes[ node ] + str( self.switchCount )
2128 self.switchOpts[name] = {}
2129 self.switchOpts[name]['nodeNum']=self.switchCount
2130 self.switchOpts[name]['hostname']=name
2131 self.switchOpts[name]['switchType']='default'
2132 self.switchOpts[name]['controllers']=[]
2133 if 'LegacyRouter' == node:
2134 self.switchCount += 1
2135 name = self.nodePrefixes[ node ] + str( self.switchCount )
2136 self.switchOpts[name] = {}
2137 self.switchOpts[name]['nodeNum']=self.switchCount
2138 self.switchOpts[name]['hostname']=name
2139 self.switchOpts[name]['switchType']='legacyRouter'
2140 if 'LegacySwitch' == node:
2141 self.switchCount += 1
2142 name = self.nodePrefixes[ node ] + str( self.switchCount )
2143 self.switchOpts[name] = {}
2144 self.switchOpts[name]['nodeNum']=self.switchCount
2145 self.switchOpts[name]['hostname']=name
2146 self.switchOpts[name]['switchType']='legacySwitch'
2147 self.switchOpts[name]['controllers']=[]
2150 name = self.nodePrefixes[ node ] + str( self.hostCount )
2151 self.hostOpts[name] = {'sched':'host'}
2152 self.hostOpts[name]['nodeNum']=self.hostCount
2153 self.hostOpts[name]['hostname']=name
2154 if 'Controller' == node:
2155 name = self.nodePrefixes[ node ] + str( self.controllerCount )
2156 ctrlr = { 'controllerType': 'ref',
2158 'controllerProtocol': 'tcp',
2159 'remoteIP': '127.0.0.1',
2161 self.controllers[name] = ctrlr
2162 # We want to start controller count at 0
2163 self.controllerCount += 1
2165 icon = self.nodeIcon( node, name )
2166 item = self.canvas.create_window( x, y, anchor='c', window=icon,
2168 self.widgetToItem[ icon ] = item
2169 self.itemToWidget[ item ] = icon
2170 self.selectItem( item )
2172 if 'Switch' == node:
2173 icon.bind('<Button-3>', self.do_switchPopup )
2174 if 'LegacyRouter' == node:
2175 icon.bind('<Button-3>', self.do_legacyRouterPopup )
2176 if 'LegacySwitch' == node:
2177 icon.bind('<Button-3>', self.do_legacySwitchPopup )
2179 icon.bind('<Button-3>', self.do_hostPopup )
2180 if 'Controller' == node:
2181 icon.bind('<Button-3>', self.do_controllerPopup )
2183 def clickController( self, event ):
2184 "Add a new Controller to our canvas."
2185 self.newNode( 'Controller', event )
2187 def clickHost( self, event ):
2188 "Add a new host to our canvas."
2189 self.newNode( 'Host', event )
2191 def clickLegacyRouter( self, event ):
2192 "Add a new switch to our canvas."
2193 self.newNode( 'LegacyRouter', event )
2195 def clickLegacySwitch( self, event ):
2196 "Add a new switch to our canvas."
2197 self.newNode( 'LegacySwitch', event )
2199 def clickSwitch( self, event ):
2200 "Add a new switch to our canvas."
2201 self.newNode( 'Switch', event )
2203 def dragNetLink( self, event ):
2204 "Drag a link's endpoint to another node."
2205 if self.link is None:
2207 # Since drag starts in widget, we use root coords
2208 x = self.canvasx( event.x_root )
2209 y = self.canvasy( event.y_root )
2211 c.coords( self.link, self.linkx, self.linky, x, y )
2213 def releaseNetLink( self, _event ):
2214 "Give up on the current link."
2215 if self.link is not None:
2216 self.canvas.delete( self.link )
2217 self.linkWidget = self.linkItem = self.link = None
2219 # Generic node handlers
2221 def createNodeBindings( self ):
2222 "Create a set of bindings for nodes."
2224 '<ButtonPress-1>': self.clickNode,
2225 '<B1-Motion>': self.dragNode,
2226 '<ButtonRelease-1>': self.releaseNode,
2227 '<Enter>': self.enterNode,
2228 '<Leave>': self.leaveNode
2230 l = Label() # lightweight-ish owner for bindings
2231 for event, binding in bindings.items():
2232 l.bind( event, binding )
2235 def selectItem( self, item ):
2236 "Select an item and remember old selection."
2237 self.lastSelection = self.selection
2238 self.selection = item
2240 def enterNode( self, event ):
2241 "Select node on entry."
2242 self.selectNode( event )
2244 def leaveNode( self, _event ):
2245 "Restore old selection on exit."
2246 self.selectItem( self.lastSelection )
2248 def clickNode( self, event ):
2249 "Node click handler."
2250 if self.active is 'NetLink':
2251 self.startLink( event )
2253 self.selectNode( event )
2256 def dragNode( self, event ):
2257 "Node drag handler."
2258 if self.active is 'NetLink':
2259 self.dragNetLink( event )
2261 self.dragNodeAround( event )
2263 def releaseNode( self, event ):
2264 "Node release handler."
2265 if self.active is 'NetLink':
2266 self.finishLink( event )
2268 # Specific node handlers
2270 def selectNode( self, event ):
2271 "Select the node that was clicked on."
2272 item = self.widgetToItem.get( event.widget, None )
2273 self.selectItem( item )
2275 def dragNodeAround( self, event ):
2276 "Drag a node around on the canvas."
2278 # Convert global to local coordinates;
2279 # Necessary since x, y are widget-relative
2280 x = self.canvasx( event.x_root )
2281 y = self.canvasy( event.y_root )
2283 # Adjust node position
2284 item = self.widgetToItem[ w ]
2285 c.coords( item, x, y )
2286 # Adjust link positions
2287 for dest in w.links:
2288 link = w.links[ dest ]
2289 item = self.widgetToItem[ dest ]
2290 x1, y1 = c.coords( item )
2291 c.coords( link, x, y, x1, y1 )
2292 self.updateScrollRegion()
2294 def createControlLinkBindings( self ):
2295 "Create a set of bindings for nodes."
2297 # Selection still needs a bit of work overall
2298 # Callbacks ignore event
2300 def select( _event, link=self.link ):
2301 "Select item on mouse entry."
2302 self.selectItem( link )
2304 def highlight( _event, link=self.link ):
2305 "Highlight item on mouse entry."
2306 self.selectItem( link )
2307 self.canvas.itemconfig( link, fill='green' )
2309 def unhighlight( _event, link=self.link ):
2310 "Unhighlight item on mouse exit."
2311 self.canvas.itemconfig( link, fill='red' )
2312 #self.selectItem( None )
2314 self.canvas.tag_bind( self.link, '<Enter>', highlight )
2315 self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
2316 self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
2318 def createDataLinkBindings( self ):
2319 "Create a set of bindings for nodes."
2321 # Selection still needs a bit of work overall
2322 # Callbacks ignore event
2324 def select( _event, link=self.link ):
2325 "Select item on mouse entry."
2326 self.selectItem( link )
2328 def highlight( _event, link=self.link ):
2329 "Highlight item on mouse entry."
2330 self.selectItem( link )
2331 self.canvas.itemconfig( link, fill='green' )
2333 def unhighlight( _event, link=self.link ):
2334 "Unhighlight item on mouse exit."
2335 self.canvas.itemconfig( link, fill='blue' )
2336 #self.selectItem( None )
2338 self.canvas.tag_bind( self.link, '<Enter>', highlight )
2339 self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
2340 self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
2341 self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup )
2344 def startLink( self, event ):
2346 if event.widget not in self.widgetToItem:
2347 # Didn't click on a node
2351 item = self.widgetToItem[ w ]
2352 x, y = self.canvas.coords( item )
2353 self.link = self.canvas.create_line( x, y, x, y, width=4,
2354 fill='blue', tag='link' )
2355 self.linkx, self.linky = x, y
2357 self.linkItem = item
2360 def finishLink( self, event ):
2361 "Finish creating a link"
2362 if self.link is None:
2364 source = self.linkWidget
2366 # Since we dragged from the widget, use root coords
2367 x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root )
2368 target = self.findItem( x, y )
2369 dest = self.itemToWidget.get( target, None )
2370 if ( source is None or dest is None or source == dest
2371 or dest in source.links or source in dest.links ):
2372 self.releaseNetLink( event )
2374 # For now, don't allow hosts to be directly linked
2375 stags = self.canvas.gettags( self.widgetToItem[ source ] )
2376 dtags = self.canvas.gettags( target )
2377 if (('Host' in stags and 'Host' in dtags) or
2378 ('Controller' in dtags and 'LegacyRouter' in stags) or
2379 ('Controller' in stags and 'LegacyRouter' in dtags) or
2380 ('Controller' in dtags and 'LegacySwitch' in stags) or
2381 ('Controller' in stags and 'LegacySwitch' in dtags) or
2382 ('Controller' in dtags and 'Host' in stags) or
2383 ('Controller' in stags and 'Host' in dtags) or
2384 ('Controller' in stags and 'Controller' in dtags)):
2385 self.releaseNetLink( event )
2390 if 'Controller' in stags or 'Controller' in dtags:
2392 c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red')
2393 self.createControlLinkBindings()
2396 self.createDataLinkBindings()
2397 c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,))
2399 x, y = c.coords( target )
2400 c.coords( self.link, self.linkx, self.linky, x, y )
2401 self.addLink( source, dest, linktype=linkType )
2402 if linkType == 'control':
2405 if 'Controller' in stags:
2406 controllerName = source[ 'text' ]
2407 switchName = dest[ 'text' ]
2409 controllerName = dest[ 'text' ]
2410 switchName = source[ 'text' ]
2412 self.switchOpts[switchName]['controllers'].append(controllerName)
2415 self.link = self.linkWidget = None
2420 "Display about box."
2421 about = self.aboutBox
2424 about = Toplevel( bg='white' )
2425 about.title( 'About' )
2426 desc = self.appName + ': a simple network editor for MiniNet'
2427 version = 'MiniEdit '+MINIEDIT_VERSION
2428 author = 'Originally by: Bob Lantz <rlantz@cs>, April 2010'
2429 enhancements = 'Enhancements by: Gregory Gee, Since July 2013'
2430 www = 'http://gregorygee.wordpress.com/category/miniedit/'
2431 line1 = Label( about, text=desc, font='Helvetica 10 bold', bg=bg )
2432 line2 = Label( about, text=version, font='Helvetica 9', bg=bg )
2433 line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
2434 line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg )
2435 line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER )
2436 line5.insert(0, www)
2437 line5.configure(state='readonly')
2438 line1.pack( padx=20, pady=10 )
2439 line2.pack(pady=10 )
2440 line3.pack(pady=10 )
2441 line4.pack(pady=10 )
2442 line5.pack(pady=10 )
2443 hide = ( lambda about=about: about.withdraw() )
2444 self.aboutBox = about
2445 # Hide on close rather than destroying window
2446 Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
2447 # Show (existing) window
2450 def createToolImages( self ):
2451 "Create toolbar (and icon) images."
2454 def checkIntf( intf ):
2455 "Make sure intf exists and is not configured."
2456 if ( ' %s:' % intf ) not in quietRun( 'ip link show' ):
2457 showerror(title="Error",
2458 message='External interface ' +intf + ' does not exist! Skipping.')
2460 ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) )
2462 showerror(title="Error",
2463 message= intf + ' has an IP address and is probably in use! Skipping.' )
2467 def hostDetails( self, _ignore=None ):
2468 if ( self.selection is None or
2469 self.net is not None or
2470 self.selection not in self.itemToWidget ):
2472 widget = self.itemToWidget[ self.selection ]
2473 name = widget[ 'text' ]
2474 tags = self.canvas.gettags( self.selection )
2475 if 'Host' not in tags:
2478 prefDefaults = self.hostOpts[name]
2479 hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults)
2480 self.master.wait_window(hostBox.top)
2482 newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']}
2483 newHostOpts['sched'] = hostBox.result['sched']
2484 if len(hostBox.result['startCommand']) > 0:
2485 newHostOpts['startCommand'] = hostBox.result['startCommand']
2486 if len(hostBox.result['stopCommand']) > 0:
2487 newHostOpts['stopCommand'] = hostBox.result['stopCommand']
2488 if len(hostBox.result['cpu']) > 0:
2489 newHostOpts['cpu'] = float(hostBox.result['cpu'])
2490 if len(hostBox.result['cores']) > 0:
2491 newHostOpts['cores'] = hostBox.result['cores']
2492 if len(hostBox.result['hostname']) > 0:
2493 newHostOpts['hostname'] = hostBox.result['hostname']
2494 name = hostBox.result['hostname']
2495 widget[ 'text' ] = name
2496 if len(hostBox.result['defaultRoute']) > 0:
2497 newHostOpts['defaultRoute'] = hostBox.result['defaultRoute']
2498 if len(hostBox.result['ip']) > 0:
2499 newHostOpts['ip'] = hostBox.result['ip']
2500 if len(hostBox.result['externalInterfaces']) > 0:
2501 newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces']
2502 if len(hostBox.result['vlanInterfaces']) > 0:
2503 newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces']
2504 if len(hostBox.result['privateDirectory']) > 0:
2505 newHostOpts['privateDirectory'] = hostBox.result['privateDirectory']
2506 self.hostOpts[name] = newHostOpts
2507 info( 'New host details for ' + name + ' = ' + str(newHostOpts), '\n' )
2509 def switchDetails( self, _ignore=None ):
2510 if ( self.selection is None or
2511 self.net is not None or
2512 self.selection not in self.itemToWidget ):
2514 widget = self.itemToWidget[ self.selection ]
2515 name = widget[ 'text' ]
2516 tags = self.canvas.gettags( self.selection )
2517 if 'Switch' not in tags:
2520 prefDefaults = self.switchOpts[name]
2521 switchBox = SwitchDialog(self, title='Switch Details', prefDefaults=prefDefaults)
2522 self.master.wait_window(switchBox.top)
2523 if switchBox.result:
2524 newSwitchOpts = {'nodeNum':self.switchOpts[name]['nodeNum']}
2525 newSwitchOpts['switchType'] = switchBox.result['switchType']
2526 newSwitchOpts['controllers'] = self.switchOpts[name]['controllers']
2527 if len(switchBox.result['startCommand']) > 0:
2528 newSwitchOpts['startCommand'] = switchBox.result['startCommand']
2529 if len(switchBox.result['stopCommand']) > 0:
2530 newSwitchOpts['stopCommand'] = switchBox.result['stopCommand']
2531 if len(switchBox.result['dpctl']) > 0:
2532 newSwitchOpts['dpctl'] = switchBox.result['dpctl']
2533 if len(switchBox.result['dpid']) > 0:
2534 newSwitchOpts['dpid'] = switchBox.result['dpid']
2535 if len(switchBox.result['hostname']) > 0:
2536 newSwitchOpts['hostname'] = switchBox.result['hostname']
2537 name = switchBox.result['hostname']
2538 widget[ 'text' ] = name
2539 if len(switchBox.result['externalInterfaces']) > 0:
2540 newSwitchOpts['externalInterfaces'] = switchBox.result['externalInterfaces']
2541 newSwitchOpts['switchIP'] = switchBox.result['switchIP']
2542 newSwitchOpts['sflow'] = switchBox.result['sflow']
2543 newSwitchOpts['netflow'] = switchBox.result['netflow']
2544 self.switchOpts[name] = newSwitchOpts
2545 info( 'New switch details for ' + name + ' = ' + str(newSwitchOpts), '\n' )
2548 if ( self.selection is None or
2551 link = self.selection
2552 linkDetail = self.links[link]
2553 src = linkDetail['src']
2554 dst = linkDetail['dest']
2555 srcName, dstName = src[ 'text' ], dst[ 'text' ]
2556 self.net.configLinkStatus(srcName, dstName, 'up')
2557 self.canvas.itemconfig(link, dash=())
2559 def linkDown( self ):
2560 if ( self.selection is None or
2563 link = self.selection
2564 linkDetail = self.links[link]
2565 src = linkDetail['src']
2566 dst = linkDetail['dest']
2567 srcName, dstName = src[ 'text' ], dst[ 'text' ]
2568 self.net.configLinkStatus(srcName, dstName, 'down')
2569 self.canvas.itemconfig(link, dash=(4, 4))
2571 def linkDetails( self, _ignore=None ):
2572 if ( self.selection is None or
2573 self.net is not None):
2575 link = self.selection
2577 linkDetail = self.links[link]
2578 # src = linkDetail['src']
2579 # dest = linkDetail['dest']
2580 linkopts = linkDetail['linkOpts']
2581 linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
2582 if linkBox.result is not None:
2583 linkDetail['linkOpts'] = linkBox.result
2584 info( 'New link details = ' + str(linkBox.result), '\n' )
2586 def prefDetails( self ):
2587 prefDefaults = self.appPrefs
2588 prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults)
2589 info( 'New Prefs = ' + str(prefBox.result), '\n' )
2591 self.appPrefs = prefBox.result
2594 def controllerDetails( self ):
2595 if ( self.selection is None or
2596 self.net is not None or
2597 self.selection not in self.itemToWidget ):
2599 widget = self.itemToWidget[ self.selection ]
2600 name = widget[ 'text' ]
2601 tags = self.canvas.gettags( self.selection )
2603 if 'Controller' not in tags:
2606 ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name])
2608 # debug( 'Controller is ' + ctrlrBox.result[0], '\n' )
2609 if len(ctrlrBox.result['hostname']) > 0:
2610 name = ctrlrBox.result['hostname']
2611 widget[ 'text' ] = name
2613 ctrlrBox.result['hostname'] = name
2614 self.controllers[name] = ctrlrBox.result
2615 info( 'New controller details for ' + name + ' = ' + str(self.controllers[name]), '\n' )
2616 # Find references to controller and change name
2618 for widget in self.widgetToItem:
2619 switchName = widget[ 'text' ]
2620 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2621 if 'Switch' in tags:
2622 switch = self.switchOpts[switchName]
2623 if oldName in switch['controllers']:
2624 switch['controllers'].remove(oldName)
2625 switch['controllers'].append(name)
2628 def listBridge( self, _ignore=None ):
2629 if ( self.selection is None or
2631 self.selection not in self.itemToWidget ):
2633 name = self.itemToWidget[ self.selection ][ 'text' ]
2634 tags = self.canvas.gettags( self.selection )
2636 if name not in self.net.nameToNode:
2638 if 'Switch' in tags or 'LegacySwitch' in tags:
2639 call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True)
2642 def ovsShow( _ignore=None ):
2643 call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True)
2646 def rootTerminal( _ignore=None ):
2647 call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True)
2651 # Ultimately we will either want to use a topo or
2652 # mininet object here, probably.
2654 def addLink( self, source, dest, linktype='data', linkopts=None ):
2655 "Add link to model."
2656 if linkopts is None:
2658 source.links[ dest ] = self.link
2659 dest.links[ source ] = self.link
2660 self.links[ self.link ] = {'type' :linktype,
2663 'linkOpts':linkopts}
2665 def deleteLink( self, link ):
2666 "Delete link from model."
2667 pair = self.links.get( link, None )
2668 if pair is not None:
2671 del source.links[ dest ]
2672 del dest.links[ source ]
2673 stags = self.canvas.gettags( self.widgetToItem[ source ] )
2674 # dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
2675 ltags = self.canvas.gettags( link )
2677 if 'control' in ltags:
2680 if 'Controller' in stags:
2681 controllerName = source[ 'text' ]
2682 switchName = dest[ 'text' ]
2684 controllerName = dest[ 'text' ]
2685 switchName = source[ 'text' ]
2687 if controllerName in self.switchOpts[switchName]['controllers']:
2688 self.switchOpts[switchName]['controllers'].remove(controllerName)
2691 if link is not None:
2692 del self.links[ link ]
2694 def deleteNode( self, item ):
2695 "Delete node (and its links) from model."
2697 widget = self.itemToWidget[ item ]
2698 tags = self.canvas.gettags(item)
2699 if 'Controller' in tags:
2700 # remove from switch controller lists
2701 for serachwidget in self.widgetToItem:
2702 name = serachwidget[ 'text' ]
2703 tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] )
2704 if 'Switch' in tags:
2705 if widget['text'] in self.switchOpts[name]['controllers']:
2706 self.switchOpts[name]['controllers'].remove(widget['text'])
2708 for link in widget.links.values():
2709 # Delete from view and model
2710 self.deleteItem( link )
2711 del self.itemToWidget[ item ]
2712 del self.widgetToItem[ widget ]
2714 def buildNodes( self, net):
2716 info( "Getting Hosts and Switches.\n" )
2717 for widget in self.widgetToItem:
2718 name = widget[ 'text' ]
2719 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2720 # debug( name+' has '+str(tags), '\n' )
2722 if 'Switch' in tags:
2723 opts = self.switchOpts[name]
2724 # debug( str(opts), '\n' )
2726 # Create the correct switch class
2727 switchClass = customOvs
2730 switchParms['listenPort']=int(opts['dpctl'])
2732 switchParms['dpid']=opts['dpid']
2733 if opts['switchType'] == 'default':
2734 if self.appPrefs['switchType'] == 'ivs':
2735 switchClass = IVSSwitch
2736 elif self.appPrefs['switchType'] == 'user':
2737 switchClass = CustomUserSwitch
2738 elif self.appPrefs['switchType'] == 'userns':
2739 switchParms['inNamespace'] = True
2740 switchClass = CustomUserSwitch
2742 switchClass = customOvs
2743 elif opts['switchType'] == 'user':
2744 switchClass = CustomUserSwitch
2745 elif opts['switchType'] == 'userns':
2746 switchClass = CustomUserSwitch
2747 switchParms['inNamespace'] = True
2748 elif opts['switchType'] == 'ivs':
2749 switchClass = IVSSwitch
2751 switchClass = customOvs
2753 if switchClass == customOvs:
2754 # Set OpenFlow versions
2755 self.openFlowVersions = []
2756 if self.appPrefs['openFlowVersions']['ovsOf10'] == '1':
2757 self.openFlowVersions.append('OpenFlow10')
2758 if self.appPrefs['openFlowVersions']['ovsOf11'] == '1':
2759 self.openFlowVersions.append('OpenFlow11')
2760 if self.appPrefs['openFlowVersions']['ovsOf12'] == '1':
2761 self.openFlowVersions.append('OpenFlow12')
2762 if self.appPrefs['openFlowVersions']['ovsOf13'] == '1':
2763 self.openFlowVersions.append('OpenFlow13')
2764 protoList = ",".join(self.openFlowVersions)
2765 switchParms['protocols'] = protoList
2766 newSwitch = net.addSwitch( name , cls=switchClass, **switchParms)
2768 # Some post startup config
2769 if switchClass == CustomUserSwitch:
2770 if 'switchIP' in opts:
2771 if len(opts['switchIP']) > 0:
2772 newSwitch.setSwitchIP(opts['switchIP'])
2773 if switchClass == customOvs:
2774 if 'switchIP' in opts:
2775 if len(opts['switchIP']) > 0:
2776 newSwitch.setSwitchIP(opts['switchIP'])
2778 # Attach external interfaces
2779 if 'externalInterfaces' in opts:
2780 for extInterface in opts['externalInterfaces']:
2781 if self.checkIntf(extInterface):
2782 Intf( extInterface, node=newSwitch )
2784 elif 'LegacySwitch' in tags:
2785 newSwitch = net.addSwitch( name , cls=LegacySwitch)
2786 elif 'LegacyRouter' in tags:
2787 newSwitch = net.addHost( name , cls=LegacyRouter)
2788 elif 'Host' in tags:
2789 opts = self.hostOpts[name]
2790 # debug( str(opts), '\n' )
2793 if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
2794 defaultRoute = 'via '+opts['defaultRoute']
2795 if 'ip' in opts and len(opts['ip']) > 0:
2798 nodeNum = self.hostOpts[name]['nodeNum']
2799 ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
2800 ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
2802 # Create the correct host class
2803 if 'cores' in opts or 'cpu' in opts:
2804 if 'privateDirectory' in opts:
2805 hostCls = partial( CPULimitedHost,
2806 privateDirs=opts['privateDirectory'] )
2808 hostCls=CPULimitedHost
2810 if 'privateDirectory' in opts:
2811 hostCls = partial( Host,
2812 privateDirs=opts['privateDirectory'] )
2815 debug( hostCls, '\n' )
2816 newHost = net.addHost( name,
2819 defaultRoute=defaultRoute
2822 # Set the CPULimitedHost specific options
2824 newHost.setCPUs(cores = opts['cores'])
2826 newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched'])
2828 # Attach external interfaces
2829 if 'externalInterfaces' in opts:
2830 for extInterface in opts['externalInterfaces']:
2831 if self.checkIntf(extInterface):
2832 Intf( extInterface, node=newHost )
2833 if 'vlanInterfaces' in opts:
2834 if len(opts['vlanInterfaces']) > 0:
2835 info( 'Checking that OS is VLAN prepared\n' )
2836 self.pathCheck('vconfig', moduleName='vlan package')
2837 moduleDeps( add='8021q' )
2838 elif 'Controller' in tags:
2839 opts = self.controllers[name]
2841 # Get controller info from panel
2842 controllerType = opts['controllerType']
2843 if 'controllerProtocol' in opts:
2844 controllerProtocol = opts['controllerProtocol']
2846 controllerProtocol = 'tcp'
2847 opts['controllerProtocol'] = 'tcp'
2848 controllerIP = opts['remoteIP']
2849 controllerPort = opts['remotePort']
2852 info( 'Getting controller selection:'+controllerType, '\n' )
2853 if controllerType == 'remote':
2854 net.addController(name=name,
2855 controller=RemoteController,
2857 protocol=controllerProtocol,
2858 port=controllerPort)
2859 elif controllerType == 'inband':
2860 net.addController(name=name,
2861 controller=InbandController,
2863 protocol=controllerProtocol,
2864 port=controllerPort)
2865 elif controllerType == 'ovsc':
2866 net.addController(name=name,
2867 controller=OVSController,
2868 protocol=controllerProtocol,
2869 port=controllerPort)
2871 net.addController(name=name,
2872 controller=Controller,
2873 protocol=controllerProtocol,
2874 port=controllerPort)
2877 raise Exception( "Cannot create mystery node: " + name )
2880 def pathCheck( *args, **kwargs ):
2881 "Make sure each program in *args can be found in $PATH."
2882 moduleName = kwargs.get( 'moduleName', 'it' )
2884 if not quietRun( 'which ' + arg ):
2885 showerror(title="Error",
2886 message= 'Cannot find required executable %s.\n' % arg +
2887 'Please make sure that %s is installed ' % moduleName +
2888 'and available in your $PATH.' )
2890 def buildLinks( self, net):
2892 info( "Getting Links.\n" )
2893 for key,link in self.links.items():
2894 tags = self.canvas.gettags(key)
2898 linkopts=link['linkOpts']
2899 srcName, dstName = src[ 'text' ], dst[ 'text' ]
2900 srcNode, dstNode = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
2902 net.addLink(srcNode, dstNode, cls=TCLink, **linkopts)
2904 # debug( str(srcNode) )
2905 # debug( str(dstNode), '\n' )
2906 net.addLink(srcNode, dstNode)
2907 self.canvas.itemconfig(key, dash=())
2911 "Build network based on our topology."
2914 if len(self.appPrefs['dpctl']) > 0:
2915 dpctl = int(self.appPrefs['dpctl'])
2916 net = Mininet( topo=None,
2919 ipBase=self.appPrefs['ipBase'] )
2921 self.buildNodes(net)
2922 self.buildLinks(net)
2924 # Build network (we have to do this separately at the moment )
2930 def postStartSetup( self ):
2932 # Setup host details
2933 for widget in self.widgetToItem:
2934 name = widget[ 'text' ]
2935 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2937 newHost = self.net.get(name)
2938 opts = self.hostOpts[name]
2939 # Attach vlan interfaces
2940 if 'vlanInterfaces' in opts:
2941 for vlanInterface in opts['vlanInterfaces']:
2942 info( 'adding vlan interface '+vlanInterface[1], '\n' )
2943 newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0])
2944 # Run User Defined Start Command
2945 if 'startCommand' in opts:
2946 newHost.cmdPrint(opts['startCommand'])
2947 if 'Switch' in tags:
2948 newNode = self.net.get(name)
2949 opts = self.switchOpts[name]
2950 # Run User Defined Start Command
2951 if 'startCommand' in opts:
2952 newNode.cmdPrint(opts['startCommand'])
2956 nflowValues = self.appPrefs['netflow']
2957 if len(nflowValues['nflowTarget']) > 0:
2958 nflowEnabled = False
2960 for widget in self.widgetToItem:
2961 name = widget[ 'text' ]
2962 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2964 if 'Switch' in tags:
2965 opts = self.switchOpts[name]
2966 if 'netflow' in opts:
2967 if opts['netflow'] == '1':
2968 info( name+' has Netflow enabled\n' )
2969 nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF'
2972 nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout']
2973 if nflowValues['nflowAddId'] == '1':
2974 nflowCmd = nflowCmd + ' add_id_to_interface=true'
2976 nflowCmd = nflowCmd + ' add_id_to_interface=false'
2977 info( 'cmd = '+nflowCmd+nflowSwitches, '\n' )
2978 call(nflowCmd+nflowSwitches, shell=True)
2981 info( 'No switches with Netflow\n' )
2983 info( 'No NetFlow targets specified.\n' )
2986 sflowValues = self.appPrefs['sflow']
2987 if len(sflowValues['sflowTarget']) > 0:
2988 sflowEnabled = False
2990 for widget in self.widgetToItem:
2991 name = widget[ 'text' ]
2992 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2994 if 'Switch' in tags:
2995 opts = self.switchOpts[name]
2997 if opts['sflow'] == '1':
2998 info( name+' has sflow enabled\n' )
2999 sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
3002 sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling']
3003 info( 'cmd = '+sflowCmd+sflowSwitches, '\n' )
3004 call(sflowCmd+sflowSwitches, shell=True)
3007 info( 'No switches with sflow\n' )
3009 info( 'No sFlow targets specified.\n' )
3011 ## NOTE: MAKE SURE THIS IS LAST THING CALLED
3012 # Start the CLI if enabled
3013 if self.appPrefs['startCLI'] == '1':
3014 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")
3019 if self.net is None:
3020 self.net = self.build()
3022 # Since I am going to inject per switch controllers.
3023 # I can't call net.start(). I have to replicate what it
3024 # does and add the controller options.
3026 info( '**** Starting %s controllers\n' % len( self.net.controllers ) )
3027 for controller in self.net.controllers:
3028 info( str(controller) + ' ')
3031 info( '**** Starting %s switches\n' % len( self.net.switches ) )
3032 #for switch in self.net.switches:
3033 # info( switch.name + ' ')
3034 # switch.start( self.net.controllers )
3035 for widget in self.widgetToItem:
3036 name = widget[ 'text' ]
3037 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
3038 if 'Switch' in tags:
3039 opts = self.switchOpts[name]
3040 switchControllers = []
3041 for ctrl in opts['controllers']:
3042 switchControllers.append(self.net.get(ctrl))
3044 # Figure out what controllers will manage this switch
3045 self.net.get(name).start( switchControllers )
3046 if 'LegacySwitch' in tags:
3047 self.net.get(name).start( [] )
3051 self.postStartSetup()
3055 if self.net is not None:
3057 for widget in self.widgetToItem:
3058 name = widget[ 'text' ]
3059 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
3061 newHost = self.net.get(name)
3062 opts = self.hostOpts[name]
3063 # Run User Defined Stop Command
3064 if 'stopCommand' in opts:
3065 newHost.cmdPrint(opts['stopCommand'])
3066 if 'Switch' in tags:
3067 newNode = self.net.get(name)
3068 opts = self.switchOpts[name]
3069 # Run User Defined Stop Command
3070 if 'stopCommand' in opts:
3071 newNode.cmdPrint(opts['stopCommand'])
3077 def do_linkPopup(self, event):
3078 # display the popup menu
3079 if self.net is None:
3081 self.linkPopup.tk_popup(event.x_root, event.y_root, 0)
3083 # make sure to release the grab (Tk 8.0a1 only)
3084 self.linkPopup.grab_release()
3087 self.linkRunPopup.tk_popup(event.x_root, event.y_root, 0)
3089 # make sure to release the grab (Tk 8.0a1 only)
3090 self.linkRunPopup.grab_release()
3092 def do_controllerPopup(self, event):
3093 # display the popup menu
3094 if self.net is None:
3096 self.controllerPopup.tk_popup(event.x_root, event.y_root, 0)
3098 # make sure to release the grab (Tk 8.0a1 only)
3099 self.controllerPopup.grab_release()
3101 def do_legacyRouterPopup(self, event):
3102 # display the popup menu
3103 if self.net is not None:
3105 self.legacyRouterRunPopup.tk_popup(event.x_root, event.y_root, 0)
3107 # make sure to release the grab (Tk 8.0a1 only)
3108 self.legacyRouterRunPopup.grab_release()
3110 def do_hostPopup(self, event):
3111 # display the popup menu
3112 if self.net is None:
3114 self.hostPopup.tk_popup(event.x_root, event.y_root, 0)
3116 # make sure to release the grab (Tk 8.0a1 only)
3117 self.hostPopup.grab_release()
3120 self.hostRunPopup.tk_popup(event.x_root, event.y_root, 0)
3122 # make sure to release the grab (Tk 8.0a1 only)
3123 self.hostRunPopup.grab_release()
3125 def do_legacySwitchPopup(self, event):
3126 # display the popup menu
3127 if self.net is not None:
3129 self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
3131 # make sure to release the grab (Tk 8.0a1 only)
3132 self.switchRunPopup.grab_release()
3134 def do_switchPopup(self, event):
3135 # display the popup menu
3136 if self.net is None:
3138 self.switchPopup.tk_popup(event.x_root, event.y_root, 0)
3140 # make sure to release the grab (Tk 8.0a1 only)
3141 self.switchPopup.grab_release()
3144 self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
3146 # make sure to release the grab (Tk 8.0a1 only)
3147 self.switchRunPopup.grab_release()
3149 def xterm( self, _ignore=None ):
3150 "Make an xterm when a button is pressed."
3151 if ( self.selection is None or
3153 self.selection not in self.itemToWidget ):
3155 name = self.itemToWidget[ self.selection ][ 'text' ]
3156 if name not in self.net.nameToNode:
3158 term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] )
3159 if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
3160 self.net.terms += term
3162 self.net.terms.append(term)
3164 def iperf( self, _ignore=None ):
3165 "Make an xterm when a button is pressed."
3166 if ( self.selection is None or
3168 self.selection not in self.itemToWidget ):
3170 name = self.itemToWidget[ self.selection ][ 'text' ]
3171 if name not in self.net.nameToNode:
3173 self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' )
3175 ### BELOW HERE IS THE TOPOLOGY IMPORT CODE ###
3177 def parseArgs( self ):
3178 """Parse command-line args and return options object.
3179 returns: opts parse options dict"""
3181 if '--custom' in sys.argv:
3182 index = sys.argv.index( '--custom' )
3183 if len( sys.argv ) > index + 1:
3184 filename = sys.argv[ index + 1 ]
3185 self.parseCustomFile( filename )
3187 raise Exception( 'Custom file name not found' )
3189 desc = ( "The %prog utility creates Mininet network from the\n"
3190 "command line. It can create parametrized topologies,\n"
3191 "invoke the Mininet CLI, and run tests." )
3193 usage = ( '%prog [options]\n'
3194 '(type %prog -h for details)' )
3196 opts = OptionParser( description=desc, usage=usage )
3198 addDictOption( opts, TOPOS, TOPODEF, 'topo' )
3199 addDictOption( opts, LINKS, LINKDEF, 'link' )
3201 opts.add_option( '--custom', type='string', default=None,
3202 help='read custom topo and node params from .py' +
3205 self.options, self.args = opts.parse_args()
3206 # We don't accept extra arguments after the options
3211 def setCustom( self, name, value ):
3212 "Set custom parameters for MininetRunner."
3213 if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
3214 # Update dictionaries
3215 param = name.upper()
3216 globals()[ param ].update( value )
3217 elif name == 'validate':
3218 # Add custom validate function
3219 self.validate = value
3221 # Add or modify global variable or class
3222 globals()[ name ] = value
3224 def parseCustomFile( self, fileName ):
3225 "Parse custom file and add params before parsing cmd-line options."
3227 if os.path.isfile( fileName ):
3228 execfile( fileName, customs, customs )
3229 for name, val in customs.items():
3230 self.setCustom( name, val )
3232 raise Exception( 'could not find custom file: %s' % fileName )
3234 def importTopo( self ):
3235 info( 'topo='+self.options.topo, '\n' )
3236 if self.options.topo == 'none':
3239 topo = buildTopo( TOPOS, self.options.topo )
3240 link = customClass( LINKS, self.options.link )
3241 importNet = Mininet(topo=topo, build=False, link=link)
3249 info( 'controllers:'+str(len(importNet.controllers)), '\n' )
3250 for controller in importNet.controllers:
3251 name = controller.name
3252 x = self.controllerCount*100+100
3253 self.addNode('Controller', self.controllerCount,
3254 float(x), float(currentY), name=name)
3255 icon = self.findWidgetByName(name)
3256 icon.bind('<Button-3>', self.do_controllerPopup )
3257 ctrlr = { 'controllerType': 'ref',
3259 'controllerProtocol': controller.protocol,
3260 'remoteIP': controller.ip,
3261 'remotePort': controller.port}
3262 self.controllers[name] = ctrlr
3266 currentY = currentY + rowIncrement
3269 info( 'switches:'+str(len(importNet.switches)), '\n' )
3271 for switch in importNet.switches:
3273 self.switchOpts[name] = {}
3274 self.switchOpts[name]['nodeNum']=self.switchCount
3275 self.switchOpts[name]['hostname']=name
3276 self.switchOpts[name]['switchType']='default'
3277 self.switchOpts[name]['controllers']=[]
3279 x = columnCount*100+100
3280 self.addNode('Switch', self.switchCount,
3281 float(x), float(currentY), name=name)
3282 icon = self.findWidgetByName(name)
3283 icon.bind('<Button-3>', self.do_switchPopup )
3284 # Now link to controllers
3285 for controller in importNet.controllers:
3286 self.switchOpts[name]['controllers'].append(controller.name)
3287 dest = self.findWidgetByName(controller.name)
3288 dx, dy = c.coords( self.widgetToItem[ dest ] )
3289 self.link = c.create_line(float(x),
3297 c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
3298 self.addLink( icon, dest, linktype='control' )
3299 self.createControlLinkBindings()
3300 self.link = self.linkWidget = None
3301 if columnCount == 9:
3303 currentY = currentY + rowIncrement
3305 columnCount =columnCount+1
3308 currentY = currentY + rowIncrement
3310 info( 'hosts:'+str(len(importNet.hosts)), '\n' )
3312 for host in importNet.hosts:
3314 self.hostOpts[name] = {'sched':'host'}
3315 self.hostOpts[name]['nodeNum']=self.hostCount
3316 self.hostOpts[name]['hostname']=name
3317 self.hostOpts[name]['ip']=host.IP()
3319 x = columnCount*100+100
3320 self.addNode('Host', self.hostCount,
3321 float(x), float(currentY), name=name)
3322 icon = self.findWidgetByName(name)
3323 icon.bind('<Button-3>', self.do_hostPopup )
3324 if columnCount == 9:
3326 currentY = currentY + rowIncrement
3328 columnCount =columnCount+1
3330 info( 'links:'+str(len(topo.links())), '\n' )
3331 #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')]
3332 for link in topo.links():
3333 info( str(link), '\n' )
3335 src = self.findWidgetByName(srcNode)
3336 sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
3339 dest = self.findWidgetByName(destNode)
3340 dx, dy = self.canvas.coords( self.widgetToItem[ dest] )
3342 params = topo.linkInfo( srcNode, destNode )
3343 info( 'Link Parameters='+str(params), '\n' )
3345 self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
3346 fill='blue', tag='link' )
3347 c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
3348 self.addLink( src, dest, linkopts=params )
3349 self.createDataLinkBindings()
3350 self.link = self.linkWidget = None
3354 def miniEditImages():
3355 "Create and return images for MiniEdit."
3357 # Image data. Git will be unhappy. However, the alternative
3358 # is to keep track of separate binary files, which is also
3362 'Select': BitmapImage(
3363 file='/usr/include/X11/bitmaps/left_ptr' ),
3365 'Switch': PhotoImage( data=r"""
3366 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
3399 'LegacySwitch': PhotoImage( data=r"""
3400 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
3431 4BE2eIRYeHAEIBwBP0Y4Qn41YWRSCQgAOw==
3434 'LegacyRouter': PhotoImage( data=r"""
3435 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
3464 gGPLHwLwcMIo12Qxu0ABAQA7
3467 'Controller': PhotoImage( data=r"""
3468 R0lGODlhMAAwAPcAAAEBAWfNAYWFhcfHx+3t6/f390lJUaWlpfPz8/Hx72lpaZGRke/v77m5uc0B
3469 AeHh4e/v7WNjY3t7e5eXlyMjI4mJidPT0+3t7f///09PT7Ozs/X19fHx8ZWTk8HBwX9/fwAAAAAA
3470 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3471 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3472 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3473 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3474 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3475 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3476 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3477 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3478 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3479 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3480 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3481 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAwADAA
3482 Bwj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAEIeMCxo8ePHwVkBGABg8mTKFOmtDByAIYN
3483 MGPCRCCzQIENNzEMGOkBAwIKQIMKpYCgKAIHCDB4GNkAA4OnUJ9++CDhQ1QGFzA0GKkBA4GvYMOK
3484 BYtBA1cNaNOqXcuWq8q3b81m7Cqzbk2bMMu6/Tl0qFEEAZLKxdj1KlSqVA3rnet1rOOwiwmznUzZ
3485 LdzLJgdfpIv3pmebN2Pm1GyRbocNp1PLNMDaAM3Im1/alQk4gO28pCt2RdCBt+/eRg8IP1AUdmmf
3486 f5MrL56bYlcOvaP7Xo6Ag3HdGDho3869u/YE1507t+3AgLz58ujPMwg/sTBUCAzgy49PH0LW5u0x
3487 XFiwvz////5dcJ9bjxVIAHsSdUXAAgs2yOCDDn6FYEQaFGDgYxNCpEFfHHKIX4IDhCjiiCSS+CGF
3488 FlCmogYpcnVABTDGKGOMAlRQYwUHnKjhAjX2aOOPN8LImgAL6PiQBhLMqCSNAThQgQRGOqRBBD1W
3489 aaOVAggnQARRNqRBBxmEKeaYZIrZQZcMKbDiigqM5OabcMYp55x01ilnQAA7
3492 'Host': PhotoImage( data=r"""
3493 R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
3494 mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
3495 Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
3496 M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
3497 AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
3498 /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
3499 zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
3500 mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
3501 ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
3502 M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
3503 AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
3504 /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
3505 zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
3506 mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
3507 ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
3508 MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
3509 AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
3510 ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
3511 AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
3512 RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
3513 ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw
3514 BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2
3515 HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO
3516 p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/
3520 'OldSwitch': PhotoImage( data=r"""
3521 R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
3522 mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
3523 Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
3524 M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
3525 AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
3526 /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
3527 zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
3528 mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
3529 ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
3530 M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
3531 AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
3532 /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
3533 zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
3534 mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
3535 ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
3536 MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
3537 AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
3538 ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
3539 AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
3540 RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
3541 ACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGDCBMqXMiwocOH
3542 ECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5
3543 /AeTpsyYI1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui1
3544 6saLWLNq3cq1q9evYB0GBAA7
3547 'NetLink': PhotoImage( data=r"""
3548 R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
3549 mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
3550 Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
3551 M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
3552 AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
3553 /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
3554 zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
3555 mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
3556 ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
3557 M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
3558 AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
3559 /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
3560 zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
3561 mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
3562 ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
3563 MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
3564 AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
3565 ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
3566 AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
3567 RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
3568 ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG
3569 Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy
3570 lBmxI8mSNknm1Dnx5sCAADs=
3574 def addDictOption( opts, choicesDict, default, name, helpStr=None ):
3575 """Convenience function to add choices dicts to OptionParser.
3576 opts: OptionParser instance
3577 choicesDict: dictionary of valid choices, must include default
3578 default: default choice key
3579 name: long option name
3581 if default not in choicesDict:
3582 raise Exception( 'Invalid default %s for choices dict: %s' %
3585 helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
3586 '[,param=value...]' )
3587 opts.add_option( '--' + name,
3592 if __name__ == '__main__':
3593 setLogLevel( 'info' )
3595 ### import topology if specified ###