Update and rename MantenerFIFO to MantenerFIFO.md
[vsorcdistro/.git] / mininet / examples / miniedit.py
1 #!/usr/bin/python
2
3 """
4 MiniEdit: a simple network editor for Mininet
5
6 This is a simple demonstration of how one might build a
7 GUI application using Mininet as the network model.
8
9 Bob Lantz, April 2010
10 Gregory Gee, July 2013
11
12 Controller icon from http://semlabs.co.uk/
13 OpenFlow icon from https://www.opennetworking.org/
14 """
15
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
20
21 MINIEDIT_VERSION = '2.2.0.1'
22
23 import sys
24 from optparse import OptionParser
25 from subprocess import call
26
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
36     import tkFont
37     import tkFileDialog
38     import tkSimpleDialog
39 else:
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
51
52 import re
53 import json
54 from distutils.version import StrictVersion
55 import os
56 from functools import partial
57
58 if 'PYTHONPATH' in os.environ:
59     sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
60
61 # someday: from ttk import *
62
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
77
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
82
83 TOPODEF = 'none'
84 TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
85           'linear': LinearTopo,
86           'reversed': SingleSwitchReversedTopo,
87           'single': SingleSwitchTopo,
88           'none': None,
89           'tree': TreeTopo }
90 CONTROLLERDEF = 'ref'
91 CONTROLLERS = { 'ref': Controller,
92                 'ovsc': OVSController,
93                 'nox': NOX,
94                 'remote': RemoteController,
95                 'none': lambda name: None }
96 LINKDEF = 'default'
97 LINKS = { 'default': Link,
98           'tc': TCLink }
99 HOSTDEF = 'proc'
100 HOSTS = { 'proc': Host,
101           'rt': custom( CPULimitedHost, sched='rt' ),
102           'cfs': custom( CPULimitedHost, sched='cfs' ) }
103
104
105 class InbandController( RemoteController ):
106     "RemoteController that ignores checkListening"
107     def checkListening( self ):
108         "Overridden to do nothing."
109         return
110
111 class CustomUserSwitch(UserSwitch):
112     "Customized UserSwitch"
113     def __init__( self, name, dpopts='--no-slicing', **kwargs ):
114         UserSwitch.__init__( self, name, **kwargs )
115         self.switchIP = None
116
117     def getSwitchIP(self):
118         "Return management IP address"
119         return self.switchIP
120
121     def setSwitchIP(self, ip):
122         "Set management IP address"
123         self.switchIP = ip
124
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 )
133             else:
134                 self.cmd( 'ifconfig lo', self.switchIP )
135
136 class LegacyRouter( Node ):
137     "Simple IP router"
138     def __init__( self, name, inNamespace=True, **params ):
139         Node.__init__( self, name, inNamespace, **params )
140
141     def config( self, **_params ):
142         if self.intfs:
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')
146         return r
147
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 )
152         self.switchIP = None
153
154 class customOvs(OVSSwitch):
155     "Customized OVS switch"
156
157     def __init__( self, name, failMode='secure', datapath='kernel', **params ):
158         OVSSwitch.__init__( self, name, failMode=failMode, datapath=datapath,**params )
159         self.switchIP = None
160
161     def getSwitchIP(self):
162         "Return management IP address"
163         return self.switchIP
164
165     def setSwitchIP(self, ip):
166         "Set management IP address"
167         self.switchIP = ip
168
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 )
176
177 class PrefsDialog(tkSimpleDialog.Dialog):
178     "Preferences dialog"
179
180     def __init__(self, parent, title, prefDefaults):
181
182         self.prefValues = prefDefaults
183
184         tkSimpleDialog.Dialog.__init__(self, parent, title)
185
186     def body(self, master):
187         "Create dialog body"
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)
193
194         # Field for Base IP
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)
200
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)
208
209         # Field for CLI
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()
216         else:
217             self.cliButton.select()
218
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")
231         else:
232             self.switchType.set("Open vSwitch Kernel Mode")
233
234
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)
242
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()
248         else:
249             self.covsOf10.select()
250
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()
256         else:
257             self.covsOf11.select()
258
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()
264         else:
265             self.covsOf12.select()
266
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()
272         else:
273             self.covsOf13.select()
274
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'])
281
282         # sFlow
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)
286
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'])
291
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'])
296
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'])
301
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'])
306
307         # NetFlow
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)
311
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'])
316
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'])
321
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()
328         else:
329             self.nflowAddIdButton.select()
330
331         # initial focus
332         return self.ipEntry
333
334     def apply(self):
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()
340
341         ovsOf10 = str(self.ovsOf10.get())
342         ovsOf11 = str(self.ovsOf11.get())
343         ovsOf12 = str(self.ovsOf12.get())
344         ovsOf13 = str(self.ovsOf13.get())
345
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,
355                        'dpctl':dpctl,
356                        'sflow':sflowValues,
357                        'netflow':nflowvalues,
358                        'startCLI':startCLI}
359         if sw == 'Indigo Virtual Switch':
360             self.result['switchType'] = 'ivs'
361             if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
362                 self.ovsOk = False
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'
369         else:
370             self.result['switchType'] = 'ovs'
371
372         self.ovsOk = True
373         if ovsOf11 == "1":
374             ovsVer = self.getOvsVersion()
375             if StrictVersion(ovsVer) < StrictVersion('2.0'):
376                 self.ovsOk = False
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'):
382                 self.ovsOk = False
383                 showerror(title="Error",
384                           message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.')
385
386         if self.ovsOk:
387             self.result['openFlowVersions']={'ovsOf10':ovsOf10,
388                                              'ovsOf11':ovsOf11,
389                                              'ovsOf12':ovsOf12,
390                                              'ovsOf13':ovsOf13}
391         else:
392             self.result = None
393
394     @staticmethod
395     def getOvsVersion():
396         "Return OVS version"
397         outp = quietRun("ovs-vsctl --version")
398         r = r'ovs-vsctl \(Open vSwitch\) (.*)'
399         m = re.search(r, outp)
400         if m is None:
401             warn( 'Version check failed' )
402             return None
403         else:
404             info( 'Open vSwitch version is '+m.group(1), '\n' )
405             return m.group(1)
406
407
408 class CustomDialog(object):
409
410     # TODO: Fix button placement and Title and window focus lock
411     def __init__(self, master, _title):
412         self.top=Toplevel(master)
413
414         self.bodyFrame = Frame(self.top)
415         self.bodyFrame.grid(row=0, column=0, sticky='nswe')
416         self.body(self.bodyFrame)
417
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')
421
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)
425
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)
429
430     def body(self, master):
431         self.rootFrame = master
432
433     def apply(self):
434         self.top.destroy()
435
436     def cancelAction(self):
437         self.top.destroy()
438
439     def okAction(self):
440         self.apply()
441         self.top.destroy()
442
443 class HostDialog(CustomDialog):
444
445     def __init__(self, master, title, prefDefaults):
446
447         self.prefValues = prefDefaults
448         self.result = None
449
450         CustomDialog.__init__(self, master, title)
451
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')
463         n.pack()
464
465         ### TAB 1
466         # Field for Hostname
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'])
472
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'])
479
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'])
486
487         # Field for CPU
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']
496         else:
497             sched = 'host'
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)
502
503         # Selection of Cores
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'])
509
510         # Start command
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']))
516         # Stop command
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']))
522
523         ### TAB 2
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)
529
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)
534
535         # Add defined interfaces
536         externalInterfaces = []
537         if 'externalInterfaces' in self.prefValues:
538             externalInterfaces = self.prefValues['externalInterfaces']
539
540         for externalInterface in externalInterfaces:
541             self.tableFrame.addRow(value=[externalInterface])
542
543         ### TAB 3
544         # VLAN Interfaces
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)
549
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)
554
555         vlanInterfaces = []
556         if 'vlanInterfaces' in self.prefValues:
557             vlanInterfaces = self.prefValues['vlanInterfaces']
558         for vlanInterface in vlanInterfaces:
559             self.vlanTableFrame.addRow(value=vlanInterface)
560
561         ### TAB 4
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)
567
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)
572
573         directoryList = []
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)
579             else:
580                 self.mountTableFrame.addRow(value=[privateDir,''])
581
582
583     def addDirectory( self ):
584         self.mountTableFrame.addRow()
585
586     def addVlanInterface( self ):
587         self.vlanTableFrame.addRow()
588
589     def addInterface( self ):
590         self.tableFrame.addRow()
591
592     def apply(self):
593         externalInterfaces = []
594         for row in range(self.tableFrame.rows):
595             if (len(self.tableFrame.get(row, 0)) > 0 and
596                 row > 0):
597                 externalInterfaces.append(self.tableFrame.get(row, 0))
598         vlanInterfaces = []
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
602                 row > 0):
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)))
609                 else:
610                     privateDirectories.append(self.mountTableFrame.get(row, 0))
611
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
624
625 class SwitchDialog(CustomDialog):
626
627     def __init__(self, master, title, prefDefaults):
628
629         self.prefValues = prefDefaults
630         self.result = None
631         CustomDialog.__init__(self, master, title)
632
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')
639
640         rowCount = 0
641         externalInterfaces = []
642         if 'externalInterfaces' in self.prefValues:
643             externalInterfaces = self.prefValues['externalInterfaces']
644
645         # Field for Hostname
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'])
650         rowCount+=1
651
652         # Field for DPID
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'])
658         rowCount+=1
659
660         # Field for Netflow
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()
668             else:
669                 self.nflowButton.select()
670         else:
671             self.nflowButton.deselect()
672         rowCount+=1
673
674         # Field for sflow
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()
682             else:
683                 self.sflowButton.select()
684         else:
685             self.sflowButton.deselect()
686         rowCount+=1
687
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")
703             else:
704                 self.switchType.set("Default")
705         else:
706             self.switchType.set("Default")
707         rowCount+=1
708
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'])
715         rowCount+=1
716
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'])
723         rowCount+=1
724
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)
729
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
733
734         # Add defined interfaces
735         for externalInterface in externalInterfaces:
736             self.tableFrame.addRow(value=[externalInterface])
737
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)
741         # Start command
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']))
747         # Stop command
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']))
753
754     def addInterface( self ):
755         self.tableFrame.addRow()
756
757     def defaultDpid( self, name):
758         "Derive dpid from switch name, s1 -> 1"
759         assert self  # satisfy pylint and allow contextual override
760         try:
761             dpid = int( re.findall( r'\d+', name )[ 0 ] )
762             dpid = hex( dpid )[ 2: ]
763             return dpid
764         except IndexError:
765             return None
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.' )
769
770     def apply(self):
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))
776
777         dpid = self.dpidEntry.get()
778         if (self.defaultDpid(self.hostnameEntry.get()) is None
779            and len(dpid) == 0):
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.' )
784
785
786         results = {'externalInterfaces':externalInterfaces,
787                    'hostname':self.hostnameEntry.get(),
788                    'dpid':dpid,
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'):
799                 self.ovsOk = False
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'
808         else:
809             results['switchType'] = 'default'
810         self.result = results
811
812
813 class VerticalScrolledTable(LabelFrame):
814     """A pure Tkinter scrollable frame that actually works!
815
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
819
820     """
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)
823
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)
831
832         # reset the view
833         canvas.xview_moveto(0)
834         canvas.yview_moveto(0)
835
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,
839                                            anchor=NW)
840
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)
851
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)
857
858         return
859
860 class TableFrame(Frame):
861     def __init__(self, parent, rows=2, columns=2):
862
863         Frame.__init__(self, parent, background="black")
864         self._widgets = []
865         self.rows = rows
866         self.columns = columns
867         for row in range(rows):
868             current_row = []
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)
874
875     def set(self, row, column, value):
876         widget = self._widgets[row][column]
877         widget.insert(0, value)
878
879     def get(self, row, column):
880         widget = self._widgets[row][column]
881         return widget.get()
882
883     def addRow( self, value=None, readonly=False ):
884         # debug( "Adding row " + str(self.rows +1), '\n' )
885         current_row = []
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])
891             if readonly == True:
892                 label.configure(state='readonly')
893             current_row.append(label)
894         self._widgets.append(current_row)
895         self.update_idletasks()
896         self.rows += 1
897
898 class LinkDialog(tkSimpleDialog.Dialog):
899
900     def __init__(self, parent, title, linkDefaults):
901
902         self.linkValues = linkDefaults
903
904         tkSimpleDialog.Dialog.__init__(self, parent, title)
905
906     def body(self, master):
907
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']))
915
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'])
921
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']))
928
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']))
934
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'])
940
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']))
946
947         return self.e1 # initial focus
948
949     def apply(self):
950         self.result = {}
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())
963
964 class ControllerDialog(tkSimpleDialog.Dialog):
965
966     def __init__(self, parent, title, ctrlrDefaults=None):
967
968         if ctrlrDefaults:
969             self.ctrlrValues = ctrlrDefaults
970
971         tkSimpleDialog.Dialog.__init__(self, parent, title)
972
973     def body(self, master):
974
975         self.var = StringVar(master)
976         self.protcolvar = StringVar(master)
977
978         rowCount=0
979         # Field for Hostname
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'])
984         rowCount+=1
985
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'])
991         rowCount+=1
992
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")
1004         else:
1005             self.var.set("OVS Controller")
1006         rowCount+=1
1007
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']
1012         else:
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")
1018         else:
1019             self.protcolvar.set("TCP")
1020         rowCount+=1
1021
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)
1025
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'])
1030         rowCount+=1
1031
1032         return self.hostnameEntry # initial focus
1033
1034     def apply(self):
1035         self.result = { 'hostname': self.hostnameEntry.get(),
1036                         'remoteIP': self.e1.get(),
1037                         'remotePort': int(self.e2.get())}
1038
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'
1046         else:
1047             self.result['controllerType'] = 'ovsc'
1048         controllerProtocol = self.protcolvar.get()
1049         if controllerProtocol == 'SSL':
1050             self.result['controllerProtocol'] = 'ssl'
1051         else:
1052             self.result['controllerProtocol'] = 'tcp'
1053
1054 class ToolTip(object):
1055
1056     def __init__(self, widget):
1057         self.widget = widget
1058         self.tipwindow = None
1059         self.id = None
1060         self.x = self.y = 0
1061
1062     def showtip(self, text):
1063         "Display text in tooltip window"
1064         self.text = text
1065         if self.tipwindow or not self.text:
1066             return
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))
1073         try:
1074             # For Mac OS
1075             # pylint: disable=protected-access
1076             tw.tk.call("::tk::unsupported::MacWindowStyle",
1077                        "style", tw._w,
1078                        "help", "noActivates")
1079             # pylint: enable=protected-access
1080         except TclError:
1081             pass
1082         label = Label(tw, text=self.text, justify=LEFT,
1083                       background="#ffffe0", relief=SOLID, borderwidth=1,
1084                       font=("tahoma", "8", "normal"))
1085         label.pack(ipadx=1)
1086
1087     def hidetip(self):
1088         tw = self.tipwindow
1089         self.tipwindow = None
1090         if tw:
1091             tw.destroy()
1092
1093 class MiniEdit( Frame ):
1094
1095     "A simple network editor for Mininet."
1096
1097     def __init__( self, parent=None, cheight=600, cwidth=1000 ):
1098
1099         self.defaultIpBase='10.0.0.0/8'
1100
1101         self.nflowDefaults = {'nflowTarget':'',
1102                               'nflowTimeout':'600',
1103                               'nflowAddId':'0'}
1104         self.sflowDefaults = {'sflowTarget':'',
1105                               'sflowSampling':'400',
1106                               'sflowHeader':'128',
1107                               'sflowPolling':'30'}
1108
1109         self.appPrefs={
1110             "ipBase": self.defaultIpBase,
1111             "startCLI": "0",
1112             "terminalType": 'xterm',
1113             "switchType": 'ovs',
1114             "dpctl": '',
1115             'sflow':self.sflowDefaults,
1116             'netflow':self.nflowDefaults,
1117             'openFlowVersions':{'ovsOf10':'1',
1118                                 'ovsOf11':'0',
1119                                 'ovsOf12':'0',
1120                                 'ovsOf13':'0'}
1121
1122         }
1123
1124
1125         Frame.__init__( self, parent )
1126         self.action = None
1127         self.appName = 'MiniEdit'
1128         self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" )
1129
1130         # Style
1131         self.font = ( 'Geneva', 9 )
1132         self.smallFont = ( 'Geneva', 7 )
1133         self.bg = 'white'
1134
1135         # Title
1136         self.top = self.winfo_toplevel()
1137         self.top.title( self.appName )
1138
1139         # Menu bar
1140         self.createMenubar()
1141
1142         # Editing canvas
1143         self.cheight, self.cwidth = cheight, cwidth
1144         self.cframe, self.canvas = self.createCanvas()
1145
1146         # Toolbar
1147         self.controllers = {}
1148
1149         # Toolbar
1150         self.images = miniEditImages()
1151         self.buttons = {}
1152         self.active = None
1153         self.tools = ( 'Select', 'Host', 'Switch', 'LegacySwitch', 'LegacyRouter', 'NetLink', 'Controller' )
1154         self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
1155         self.toolbar = self.createToolbar()
1156
1157         # Layout
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' )
1163
1164         # About box
1165         self.aboutBox = None
1166
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 = {}
1172
1173         # Initialize link tool
1174         self.link = self.linkWidget = None
1175
1176         # Selection support
1177         self.selection = None
1178
1179         # Keyboard bindings
1180         self.bind( '<Control-q>', lambda event: self.quit() )
1181         self.bind( '<KeyPress-Delete>', self.deleteSelection )
1182         self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
1183         self.focus()
1184
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 )
1189
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 )
1194
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 )
1199
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 )
1204
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 )
1209
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 )
1214
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 )
1220
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 )
1225
1226
1227         # Event handling initalization
1228         self.linkx = self.linky = self.linkItem = None
1229         self.lastSelection = None
1230
1231         # Model initialization
1232         self.links = {}
1233         self.hostOpts = {}
1234         self.switchOpts = {}
1235         self.hostCount = 0
1236         self.switchCount = 0
1237         self.controllerCount = 0
1238         self.net = None
1239
1240         # Close window gracefully
1241         Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
1242
1243     def quit( self ):
1244         "Stop our network, if any, then quit."
1245         self.stop()
1246         Frame.quit( self )
1247
1248     def createMenubar( self ):
1249         "Create our menu bar."
1250
1251         font = self.font
1252
1253         mbar = Menu( self.top, font=font )
1254         self.top.configure( menu=mbar )
1255
1256
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 )
1265
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)
1271
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 )
1279
1280         # Application menu
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,
1284                              font=font)
1285     # Canvas
1286
1287     def createCanvas( self ):
1288         "Create and return our scrolling canvas frame."
1289         f = Frame( self )
1290
1291         canvas = Canvas( f, width=self.cwidth, height=self.cheight,
1292                          bg=self.bg )
1293
1294         # Scroll bars
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 )
1298
1299         # Resize box
1300         resize = Label( f, bg='white' )
1301
1302         # Layout
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' )
1307
1308         # Resize behavior
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() )
1313
1314         # Mouse bindings
1315         canvas.bind( '<ButtonPress-1>', self.clickCanvas )
1316         canvas.bind( '<B1-Motion>', self.dragCanvas )
1317         canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
1318
1319         return f, canvas
1320
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 ],
1326                                    bbox[ 3 ] ) )
1327
1328     def canvasx( self, x_root ):
1329         "Convert root x coordinate to canvas coordinate."
1330         c = self.canvas
1331         return c.canvasx( x_root ) - c.winfo_rootx()
1332
1333     def canvasy( self, y_root ):
1334         "Convert root y coordinate to canvas coordinate."
1335         c = self.canvas
1336         return c.canvasy( y_root ) - c.winfo_rooty()
1337
1338     # Toolbar
1339
1340     def activate( self, toolName ):
1341         "Activate a tool and press its button."
1342         # Adjust button appearance
1343         if self.active:
1344             self.buttons[ self.active ].configure( relief='raised' )
1345         self.buttons[ toolName ].configure( relief='sunken' )
1346         # Activate dynamic bindings
1347         self.active = toolName
1348
1349
1350     @staticmethod
1351     def createToolTip(widget, text):
1352         toolTip = ToolTip(widget)
1353         def enter(_event):
1354             toolTip.showtip(text)
1355         def leave(_event):
1356             toolTip.hidetip()
1357         widget.bind('<Enter>', enter)
1358         widget.bind('<Leave>', leave)
1359
1360     def createToolbar( self ):
1361         "Create and return our toolbar frame."
1362
1363         toolbar = Frame( self )
1364
1365         # Tools
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' )
1373             b.pack( fill='x' )
1374             self.buttons[ tool ] = b
1375         self.activate( self.tools[ 0 ] )
1376
1377         # Spacer
1378         Label( toolbar, text='' ).pack()
1379
1380         # Commands
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' )
1386
1387         return toolbar
1388
1389     def doRun( self ):
1390         "Run command."
1391         self.activate( 'Select' )
1392         for tool in self.tools:
1393             self.buttons[ tool ].config( state='disabled' )
1394         self.start()
1395
1396     def doStop( self ):
1397         "Stop command."
1398         self.stop()
1399         for tool in self.tools:
1400             self.buttons[ tool ].config( state='normal' )
1401
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
1406         if 'Host' == node:
1407             self.hostCount += 1
1408         if 'Controller' == node:
1409             self.controllerCount += 1
1410         if name is None:
1411             name = self.nodePrefixes[ node ] + nodeNum
1412         self.addNamedNode(node, name, x, y)
1413
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,
1418                                           tags=node )
1419         self.widgetToItem[ icon ] = item
1420         self.itemToWidget[ item ] = icon
1421         icon.links = {}
1422
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')
1431         else:
1432             return text
1433
1434     def loadTopology( self ):
1435         "Load command."
1436         c = self.canvas
1437
1438         myFormats = [
1439             ('Mininet Topology','*.mn'),
1440             ('All Files','*'),
1441         ]
1442         f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
1443         if f == None:
1444             return
1445         self.newTopology()
1446         loadedTopology = self.convertJsonUnicode(json.load(f))
1447
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
1463
1464         # Load controllers
1465         if 'controllers' in loadedTopology:
1466             if loadedTopology['version'] == '1':
1467                 # This is old location of controller info
1468                 hostname = 'c0'
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 )
1475             else:
1476                 controllers = loadedTopology['controllers']
1477                 for controller in controllers:
1478                     hostname = controller['opts']['hostname']
1479                     x = controller['x']
1480                     y = controller['y']
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 )
1485
1486
1487         # Load hosts
1488         hosts = loadedTopology['hosts']
1489         for host in hosts:
1490             nodeNum = host['number']
1491             hostname = 'h'+nodeNum
1492             if 'hostname' in host['opts']:
1493                 hostname = host['opts']['hostname']
1494             else:
1495                 host['opts']['hostname'] = hostname
1496             if 'nodeNum' not in host['opts']:
1497                 host['opts']['nodeNum'] = int(nodeNum)
1498             x = host['x']
1499             y = host['y']
1500             self.addNode('Host', nodeNum, float(x), float(y), name=hostname)
1501
1502             # Fix JSON converting tuple to list when saving
1503             if 'privateDirectory' in host['opts']:
1504                 newDirList = []
1505                 for privateDir in host['opts']['privateDirectory']:
1506                     if isinstance( privateDir, list ):
1507                         newDirList.append((privateDir[0],privateDir[1]))
1508                     else:
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 )
1514
1515         # Load switches
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']
1526             else:
1527                 switch['opts']['hostname'] = hostname
1528             if 'nodeNum' not in switch['opts']:
1529                 switch['opts']['nodeNum'] = int(nodeNum)
1530             x = switch['x']
1531             y = switch['y']
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 )
1540             else:
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']
1545
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),
1553                                                         float(y),
1554                                                         dx,
1555                                                         dy,
1556                                                         width=4,
1557                                                         fill='red',
1558                                                         dash=(6, 4, 2, 4),
1559                                                         tag='link' )
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
1564             else:
1565                 dest = self.findWidgetByName('c0')
1566                 dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1567                 self.link = self.canvas.create_line(float(x),
1568                                                     float(y),
1569                                                     dx,
1570                                                     dy,
1571                                                     width=4,
1572                                                     fill='red',
1573                                                     dash=(6, 4, 2, 4),
1574                                                     tag='link' )
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
1579
1580         # Load links
1581         links = loadedTopology['links']
1582         for link in links:
1583             srcNode = link['src']
1584             src = self.findWidgetByName(srcNode)
1585             sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
1586
1587             destNode = link['dest']
1588             dest = self.findWidgetByName(destNode)
1589             dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
1590
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
1597
1598         f.close()
1599
1600     def findWidgetByName( self, name ):
1601         for widget in self.widgetToItem:
1602             if name ==  widget[ 'text' ]:
1603                 return widget
1604
1605     def newTopology( self ):
1606         "New command."
1607         for widget in self.widgetToItem.keys():
1608             self.deleteItem( self.widgetToItem[ widget ] )
1609         self.hostCount = 0
1610         self.switchCount = 0
1611         self.controllerCount = 0
1612         self.links = {}
1613         self.hostOpts = {}
1614         self.switchOpts = {}
1615         self.controllers = {}
1616         self.appPrefs["ipBase"]= self.defaultIpBase
1617
1618     def saveTopology( self ):
1619         "Save command."
1620         myFormats = [
1621             ('Mininet Topology','*.mn'),
1622             ('All Files','*'),
1623         ]
1624
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'
1630
1631             # Save Switches and Hosts
1632             hostsToSave = []
1633             switchesToSave = []
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),
1642                                   'x':str(x1),
1643                                   'y':str(y1),
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),
1649                                   'x':str(x1),
1650                                   'y':str(y1),
1651                                   'opts':self.hostOpts[name] }
1652                     hostsToSave.append(nodeToSave)
1653                 elif 'Controller' in tags:
1654                     nodeToSave = {'x':str(x1),
1655                                   'y':str(y1),
1656                                   'opts':self.controllers[name] }
1657                     controllersToSave.append(nodeToSave)
1658                 else:
1659                     raise Exception( "Cannot create mystery node: " + name )
1660             savingDictionary['hosts'] = hostsToSave
1661             savingDictionary['switches'] = switchesToSave
1662             savingDictionary['controllers'] = controllersToSave
1663
1664             # Save Links
1665             linksToSave = []
1666             for link in self.links.values():
1667                 src = link['src']
1668                 dst = link['dest']
1669                 linkopts = link['linkOpts']
1670
1671                 srcName, dstName = src[ 'text' ], dst[ 'text' ]
1672                 linkToSave = {'src':srcName,
1673                               'dest':dstName,
1674                               'opts':linkopts}
1675                 if link['type'] == 'data':
1676                     linksToSave.append(linkToSave)
1677             savingDictionary['links'] = linksToSave
1678
1679             # Save Application preferences
1680             savingDictionary['application'] = self.appPrefs
1681
1682             try:
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:
1687                 warn( er, '\n' )
1688             # pylint: enable=broad-except
1689             finally:
1690                 f.close()
1691
1692     def exportScript( self ):
1693         "Export command."
1694         myFormats = [
1695             ('Mininet Custom Topology','*.py'),
1696             ('All Files','*'),
1697         ]
1698
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')
1703
1704             f.write("#!/usr/bin/python\n")
1705             f.write("\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")
1716
1717             inBandCtrl = False
1718             for widget in self.widgetToItem:
1719                 name = widget[ 'text' ]
1720                 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1721
1722                 if 'Controller' in tags:
1723                     opts = self.controllers[name]
1724                     controllerType = opts['controllerType']
1725                     if controllerType == 'inband':
1726                         inBandCtrl = True
1727
1728             if inBandCtrl == True:
1729                 f.write("\n")
1730                 f.write("class InbandController( RemoteController ):\n")
1731                 f.write("\n")
1732                 f.write("    def checkListening( self ):\n")
1733                 f.write("        \"Overridden to do nothing.\"\n")
1734                 f.write("        return\n")
1735
1736             f.write("\n")
1737             f.write("def myNetwork():\n")
1738             f.write("\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")
1744             f.write("\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 ] )
1749
1750                 if 'Controller' in tags:
1751                     opts = self.controllers[name]
1752                     controllerType = opts['controllerType']
1753                     if 'controllerProtocol' in opts:
1754                         controllerProtocol = opts['controllerProtocol']
1755                     else:
1756                         controllerProtocol = 'tcp'
1757                     controllerIP = opts['remoteIP']
1758                     controllerPort = opts['remotePort']
1759
1760
1761                     f.write("    "+name+"=net.addController(name='"+name+"',\n")
1762
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")
1771                     else:
1772                         f.write("                      controller=Controller,\n")
1773
1774                     f.write("                      protocol='"+controllerProtocol+"',\n")
1775                     f.write("                      port="+str(controllerPort)+")\n")
1776                     f.write("\n")
1777
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")
1799                         else:
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")
1807                     else:
1808                         f.write(", cls=OVSKernelSwitch")
1809                     if 'dpctl' in opts:
1810                         f.write(", listenPort="+opts['dpctl'])
1811                     if 'dpid' in opts:
1812                         f.write(", dpid='"+opts['dpid']+"'")
1813                     f.write(")\n")
1814                     if 'externalInterfaces' in opts:
1815                         for extInterface in opts['externalInterfaces']:
1816                             f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
1817
1818             f.write("\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 ] )
1823                 if 'Host' in tags:
1824                     opts = self.hostOpts[name]
1825                     ip = None
1826                     defaultRoute = None
1827                     if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
1828                         defaultRoute = "'via "+opts['defaultRoute']+"'"
1829                     else:
1830                         defaultRoute = 'None'
1831                     if 'ip' in opts and len(opts['ip']) > 0:
1832                         ip = opts['ip']
1833                     else:
1834                         nodeNum = self.hostOpts[name]['nodeNum']
1835                         ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
1836                         ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
1837
1838                     if 'cores' in opts or 'cpu' in opts:
1839                         f.write("    "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
1840                         if 'cores' in opts:
1841                             f.write("    "+name+".setCPUs(cores='"+opts['cores']+"')\n")
1842                         if 'cpu' in opts:
1843                             f.write("    "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n")
1844                     else:
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")
1849             f.write("\n")
1850
1851             # Save Links
1852             f.write("    info( '*** Add links\\n')\n")
1853             for key,linkDetail in self.links.items():
1854                 tags = self.canvas.gettags(key)
1855                 if 'data' in tags:
1856                     optsExist = False
1857                     src = linkDetail['src']
1858                     dst = linkDetail['dest']
1859                     linkopts = linkDetail['linkOpts']
1860                     srcName, dstName = src[ 'text' ], dst[ 'text' ]
1861                     bw = ''
1862                     # delay = ''
1863                     # loss = ''
1864                     # max_queue_size = ''
1865                     linkOpts = "{"
1866                     if 'bw' in linkopts:
1867                         bw =  linkopts['bw']
1868                         linkOpts = linkOpts + "'bw':"+str(bw)
1869                         optsExist = True
1870                     if 'delay' in linkopts:
1871                         # delay =  linkopts['delay']
1872                         if optsExist:
1873                             linkOpts = linkOpts + ","
1874                         linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'"
1875                         optsExist = True
1876                     if 'loss' in linkopts:
1877                         if optsExist:
1878                             linkOpts = linkOpts + ","
1879                         linkOpts = linkOpts + "'loss':"+str(linkopts['loss'])
1880                         optsExist = True
1881                     if 'max_queue_size' in linkopts:
1882                         if optsExist:
1883                             linkOpts = linkOpts + ","
1884                         linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size'])
1885                         optsExist = True
1886                     if 'jitter' in linkopts:
1887                         if optsExist:
1888                             linkOpts = linkOpts + ","
1889                         linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'"
1890                         optsExist = True
1891                     if 'speedup' in linkopts:
1892                         if optsExist:
1893                             linkOpts = linkOpts + ","
1894                         linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup'])
1895                         optsExist = True
1896
1897                     linkOpts = linkOpts + "}"
1898                     if optsExist:
1899                         f.write("    "+srcName+dstName+" = "+linkOpts+"\n")
1900                     f.write("    net.addLink("+srcName+", "+dstName)
1901                     if optsExist:
1902                         f.write(", cls=TCLink , **"+srcName+dstName)
1903                     f.write(")\n")
1904
1905             f.write("\n")
1906             f.write("    info( '*** Starting network\\n')\n")
1907             f.write("    net.build()\n")
1908
1909             f.write("    info( '*** Starting controllers\\n')\n")
1910             f.write("    for controller in net.controllers:\n")
1911             f.write("        controller.start()\n")
1912             f.write("\n")
1913
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")
1922
1923             f.write("\n")
1924
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 ] )
1959                 if 'Host' in tags:
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")
1974
1975             # Configure NetFlow
1976             nflowValues = self.appPrefs['netflow']
1977             if len(nflowValues['nflowTarget']) > 0:
1978                 nflowEnabled = False
1979                 nflowSwitches = ''
1980                 for widget in self.widgetToItem:
1981                     name = widget[ 'text' ]
1982                     tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1983
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'
1989                                 nflowEnabled=True
1990                 if nflowEnabled:
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'
1994                     else:
1995                         nflowCmd = nflowCmd + ' add_id_to_interface=false'
1996                     f.write("    \n")
1997                     f.write("    call('"+nflowCmd+nflowSwitches+"', shell=True)\n")
1998
1999             # Configure sFlow
2000             sflowValues = self.appPrefs['sflow']
2001             if len(sflowValues['sflowTarget']) > 0:
2002                 sflowEnabled = False
2003                 sflowSwitches = ''
2004                 for widget in self.widgetToItem:
2005                     name = widget[ 'text' ]
2006                     tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2007
2008                     if 'Switch' in tags:
2009                         opts = self.switchOpts[name]
2010                         if 'sflow' in opts:
2011                             if opts['sflow'] == '1':
2012                                 sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
2013                                 sflowEnabled=True
2014                 if sflowEnabled:
2015                     sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling']
2016                     f.write("    \n")
2017                     f.write("    call('"+sflowCmd+sflowSwitches+"', shell=True)\n")
2018
2019             f.write("\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 ] )
2024                 if 'Host' in tags:
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")
2034
2035             f.write("    net.stop()\n")
2036             f.write("\n")
2037             f.write("if __name__ == '__main__':\n")
2038             f.write("    setLogLevel( 'info' )\n")
2039             f.write("    myNetwork()\n")
2040             f.write("\n")
2041
2042
2043             f.close()
2044
2045
2046     # Generic canvas handler
2047     #
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.
2052
2053     def canvasHandle( self, eventName, event ):
2054         "Generic canvas event handler"
2055         if self.active is None:
2056             return
2057         toolName = self.active
2058         handler = getattr( self, eventName + toolName, None )
2059         if handler is not None:
2060             handler( event )
2061
2062     def clickCanvas( self, event ):
2063         "Canvas click handler."
2064         self.canvasHandle( 'click', event )
2065
2066     def dragCanvas( self, event ):
2067         "Canvas drag handler."
2068         self.canvasHandle( 'drag', event )
2069
2070     def releaseCanvas( self, event ):
2071         "Canvas mouse up handler."
2072         self.canvasHandle( 'release', event )
2073
2074     # Currently the only items we can select directly are
2075     # links. Nodes are handled by bindings in the node icon.
2076
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:
2081             return None
2082         else:
2083             return items[ 0 ]
2084
2085     # Canvas bindings for Select, Host, Switch and Link tools
2086
2087     def clickSelect( self, event ):
2088         "Select an item."
2089         self.selectItem( self.findItem( event.x, event.y ) )
2090
2091     def deleteItem( self, item ):
2092         "Delete an item."
2093         # Don't delete while network is running
2094         if self.buttons[ 'Select' ][ 'state' ] == 'disabled':
2095             return
2096         # Delete from model
2097         if item in self.links:
2098             self.deleteLink( item )
2099         if item in self.itemToWidget:
2100             self.deleteNode( item )
2101         # Delete from view
2102         self.canvas.delete( item )
2103
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 )
2109
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 ) )
2118         return icon
2119
2120     def newNode( self, node, event ):
2121         "Add a new node to our canvas."
2122         c = self.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']=[]
2148         if 'Host' == node:
2149             self.hostCount += 1
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',
2157                       'hostname': name,
2158                       'controllerProtocol': 'tcp',
2159                       'remoteIP': '127.0.0.1',
2160                       'remotePort': 6633}
2161             self.controllers[name] = ctrlr
2162             # We want to start controller count at 0
2163             self.controllerCount += 1
2164
2165         icon = self.nodeIcon( node, name )
2166         item = self.canvas.create_window( x, y, anchor='c', window=icon,
2167                                           tags=node )
2168         self.widgetToItem[ icon ] = item
2169         self.itemToWidget[ item ] = icon
2170         self.selectItem( item )
2171         icon.links = {}
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 )
2178         if 'Host' == node:
2179             icon.bind('<Button-3>', self.do_hostPopup )
2180         if 'Controller' == node:
2181             icon.bind('<Button-3>', self.do_controllerPopup )
2182
2183     def clickController( self, event ):
2184         "Add a new Controller to our canvas."
2185         self.newNode( 'Controller', event )
2186
2187     def clickHost( self, event ):
2188         "Add a new host to our canvas."
2189         self.newNode( 'Host', event )
2190
2191     def clickLegacyRouter( self, event ):
2192         "Add a new switch to our canvas."
2193         self.newNode( 'LegacyRouter', event )
2194
2195     def clickLegacySwitch( self, event ):
2196         "Add a new switch to our canvas."
2197         self.newNode( 'LegacySwitch', event )
2198
2199     def clickSwitch( self, event ):
2200         "Add a new switch to our canvas."
2201         self.newNode( 'Switch', event )
2202
2203     def dragNetLink( self, event ):
2204         "Drag a link's endpoint to another node."
2205         if self.link is None:
2206             return
2207         # Since drag starts in widget, we use root coords
2208         x = self.canvasx( event.x_root )
2209         y = self.canvasy( event.y_root )
2210         c = self.canvas
2211         c.coords( self.link, self.linkx, self.linky, x, y )
2212
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
2218
2219     # Generic node handlers
2220
2221     def createNodeBindings( self ):
2222         "Create a set of bindings for nodes."
2223         bindings = {
2224             '<ButtonPress-1>': self.clickNode,
2225             '<B1-Motion>': self.dragNode,
2226             '<ButtonRelease-1>': self.releaseNode,
2227             '<Enter>': self.enterNode,
2228             '<Leave>': self.leaveNode
2229         }
2230         l = Label()  # lightweight-ish owner for bindings
2231         for event, binding in bindings.items():
2232             l.bind( event, binding )
2233         return l
2234
2235     def selectItem( self, item ):
2236         "Select an item and remember old selection."
2237         self.lastSelection = self.selection
2238         self.selection = item
2239
2240     def enterNode( self, event ):
2241         "Select node on entry."
2242         self.selectNode( event )
2243
2244     def leaveNode( self, _event ):
2245         "Restore old selection on exit."
2246         self.selectItem( self.lastSelection )
2247
2248     def clickNode( self, event ):
2249         "Node click handler."
2250         if self.active is 'NetLink':
2251             self.startLink( event )
2252         else:
2253             self.selectNode( event )
2254         return 'break'
2255
2256     def dragNode( self, event ):
2257         "Node drag handler."
2258         if self.active is 'NetLink':
2259             self.dragNetLink( event )
2260         else:
2261             self.dragNodeAround( event )
2262
2263     def releaseNode( self, event ):
2264         "Node release handler."
2265         if self.active is 'NetLink':
2266             self.finishLink( event )
2267
2268     # Specific node handlers
2269
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 )
2274
2275     def dragNodeAround( self, event ):
2276         "Drag a node around on the canvas."
2277         c = self.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 )
2282         w = event.widget
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()
2293
2294     def createControlLinkBindings( self ):
2295         "Create a set of bindings for nodes."
2296         # Link bindings
2297         # Selection still needs a bit of work overall
2298         # Callbacks ignore event
2299
2300         def select( _event, link=self.link ):
2301             "Select item on mouse entry."
2302             self.selectItem( link )
2303
2304         def highlight( _event, link=self.link ):
2305             "Highlight item on mouse entry."
2306             self.selectItem( link )
2307             self.canvas.itemconfig( link, fill='green' )
2308
2309         def unhighlight( _event, link=self.link ):
2310             "Unhighlight item on mouse exit."
2311             self.canvas.itemconfig( link, fill='red' )
2312             #self.selectItem( None )
2313
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 )
2317
2318     def createDataLinkBindings( self ):
2319         "Create a set of bindings for nodes."
2320         # Link bindings
2321         # Selection still needs a bit of work overall
2322         # Callbacks ignore event
2323
2324         def select( _event, link=self.link ):
2325             "Select item on mouse entry."
2326             self.selectItem( link )
2327
2328         def highlight( _event, link=self.link ):
2329             "Highlight item on mouse entry."
2330             self.selectItem( link )
2331             self.canvas.itemconfig( link, fill='green' )
2332
2333         def unhighlight( _event, link=self.link ):
2334             "Unhighlight item on mouse exit."
2335             self.canvas.itemconfig( link, fill='blue' )
2336             #self.selectItem( None )
2337
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 )
2342
2343
2344     def startLink( self, event ):
2345         "Start a new link."
2346         if event.widget not in self.widgetToItem:
2347             # Didn't click on a node
2348             return
2349
2350         w = event.widget
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
2356         self.linkWidget = w
2357         self.linkItem = item
2358
2359
2360     def finishLink( self, event ):
2361         "Finish creating a link"
2362         if self.link is None:
2363             return
2364         source = self.linkWidget
2365         c = self.canvas
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 )
2373             return
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 )
2386             return
2387
2388         # Set link type
2389         linkType='data'
2390         if 'Controller' in stags or 'Controller' in dtags:
2391             linkType='control'
2392             c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red')
2393             self.createControlLinkBindings()
2394         else:
2395             linkType='data'
2396             self.createDataLinkBindings()
2397         c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,))
2398
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':
2403             controllerName = ''
2404             switchName = ''
2405             if 'Controller' in stags:
2406                 controllerName = source[ 'text' ]
2407                 switchName = dest[ 'text' ]
2408             else:
2409                 controllerName = dest[ 'text' ]
2410                 switchName = source[ 'text' ]
2411
2412             self.switchOpts[switchName]['controllers'].append(controllerName)
2413
2414         # We're done
2415         self.link = self.linkWidget = None
2416
2417     # Menu handlers
2418
2419     def about( self ):
2420         "Display about box."
2421         about = self.aboutBox
2422         if about is None:
2423             bg = 'white'
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
2448         about.deiconify()
2449
2450     def createToolImages( self ):
2451         "Create toolbar (and icon) images."
2452
2453     @staticmethod
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.')
2459             return False
2460         ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) )
2461         if ips:
2462             showerror(title="Error",
2463                       message= intf + ' has an IP address and is probably in use! Skipping.' )
2464             return False
2465         return True
2466
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 ):
2471             return
2472         widget = self.itemToWidget[ self.selection ]
2473         name = widget[ 'text' ]
2474         tags = self.canvas.gettags( self.selection )
2475         if 'Host' not in tags:
2476             return
2477
2478         prefDefaults = self.hostOpts[name]
2479         hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults)
2480         self.master.wait_window(hostBox.top)
2481         if hostBox.result:
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' )
2508
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 ):
2513             return
2514         widget = self.itemToWidget[ self.selection ]
2515         name = widget[ 'text' ]
2516         tags = self.canvas.gettags( self.selection )
2517         if 'Switch' not in tags:
2518             return
2519
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' )
2546
2547     def linkUp( self ):
2548         if ( self.selection is None or
2549              self.net is None):
2550             return
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=())
2558
2559     def linkDown( self ):
2560         if ( self.selection is None or
2561              self.net is None):
2562             return
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))
2570
2571     def linkDetails( self, _ignore=None ):
2572         if ( self.selection is None or
2573              self.net is not None):
2574             return
2575         link = self.selection
2576
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' )
2585
2586     def prefDetails( self ):
2587         prefDefaults = self.appPrefs
2588         prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults)
2589         info( 'New Prefs = ' + str(prefBox.result), '\n' )
2590         if prefBox.result:
2591             self.appPrefs = prefBox.result
2592
2593
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 ):
2598             return
2599         widget = self.itemToWidget[ self.selection ]
2600         name = widget[ 'text' ]
2601         tags = self.canvas.gettags( self.selection )
2602         oldName = name
2603         if 'Controller' not in tags:
2604             return
2605
2606         ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name])
2607         if ctrlrBox.result:
2608             # debug( 'Controller is ' + ctrlrBox.result[0], '\n' )
2609             if len(ctrlrBox.result['hostname']) > 0:
2610                 name = ctrlrBox.result['hostname']
2611                 widget[ 'text' ] = name
2612             else:
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
2617             if oldName != 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)
2626
2627
2628     def listBridge( self, _ignore=None ):
2629         if ( self.selection is None or
2630              self.net is None or
2631              self.selection not in self.itemToWidget ):
2632             return
2633         name = self.itemToWidget[ self.selection ][ 'text' ]
2634         tags = self.canvas.gettags( self.selection )
2635
2636         if name not in self.net.nameToNode:
2637             return
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)
2640
2641     @staticmethod
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)
2644
2645     @staticmethod
2646     def rootTerminal( _ignore=None ):
2647         call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True)
2648
2649     # Model interface
2650     #
2651     # Ultimately we will either want to use a topo or
2652     # mininet object here, probably.
2653
2654     def addLink( self, source, dest, linktype='data', linkopts=None ):
2655         "Add link to model."
2656         if linkopts is None:
2657             linkopts = {}
2658         source.links[ dest ] = self.link
2659         dest.links[ source ] = self.link
2660         self.links[ self.link ] = {'type' :linktype,
2661                                    'src':source,
2662                                    'dest':dest,
2663                                    'linkOpts':linkopts}
2664
2665     def deleteLink( self, link ):
2666         "Delete link from model."
2667         pair = self.links.get( link, None )
2668         if pair is not None:
2669             source=pair['src']
2670             dest=pair['dest']
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 )
2676
2677             if 'control' in ltags:
2678                 controllerName = ''
2679                 switchName = ''
2680                 if 'Controller' in stags:
2681                     controllerName = source[ 'text' ]
2682                     switchName = dest[ 'text' ]
2683                 else:
2684                     controllerName = dest[ 'text' ]
2685                     switchName = source[ 'text' ]
2686
2687                 if controllerName in self.switchOpts[switchName]['controllers']:
2688                     self.switchOpts[switchName]['controllers'].remove(controllerName)
2689
2690
2691         if link is not None:
2692             del self.links[ link ]
2693
2694     def deleteNode( self, item ):
2695         "Delete node (and its links) from model."
2696
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'])
2707
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 ]
2713
2714     def buildNodes( self, net):
2715         # Make nodes
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' )
2721
2722             if 'Switch' in tags:
2723                 opts = self.switchOpts[name]
2724                 # debug( str(opts), '\n' )
2725
2726                 # Create the correct switch class
2727                 switchClass = customOvs
2728                 switchParms={}
2729                 if 'dpctl' in opts:
2730                     switchParms['listenPort']=int(opts['dpctl'])
2731                 if 'dpid' in opts:
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
2741                     else:
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
2750                 else:
2751                     switchClass = customOvs
2752
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)
2767
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'])
2777
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 )
2783
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' )
2791                 ip = None
2792                 defaultRoute = None
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:
2796                     ip = opts['ip']
2797                 else:
2798                     nodeNum = self.hostOpts[name]['nodeNum']
2799                     ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
2800                     ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
2801
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'] )
2807                     else:
2808                         hostCls=CPULimitedHost
2809                 else:
2810                     if 'privateDirectory' in opts:
2811                         hostCls = partial( Host,
2812                                            privateDirs=opts['privateDirectory'] )
2813                     else:
2814                         hostCls=Host
2815                 debug( hostCls, '\n' )
2816                 newHost = net.addHost( name,
2817                                        cls=hostCls,
2818                                        ip=ip,
2819                                        defaultRoute=defaultRoute
2820                                       )
2821
2822                 # Set the CPULimitedHost specific options
2823                 if 'cores' in opts:
2824                     newHost.setCPUs(cores = opts['cores'])
2825                 if 'cpu' in opts:
2826                     newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched'])
2827
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]
2840
2841                 # Get controller info from panel
2842                 controllerType = opts['controllerType']
2843                 if 'controllerProtocol' in opts:
2844                     controllerProtocol = opts['controllerProtocol']
2845                 else:
2846                     controllerProtocol = 'tcp'
2847                     opts['controllerProtocol'] = 'tcp'
2848                 controllerIP = opts['remoteIP']
2849                 controllerPort = opts['remotePort']
2850
2851                 # Make controller
2852                 info( 'Getting controller selection:'+controllerType, '\n' )
2853                 if controllerType == 'remote':
2854                     net.addController(name=name,
2855                                       controller=RemoteController,
2856                                       ip=controllerIP,
2857                                       protocol=controllerProtocol,
2858                                       port=controllerPort)
2859                 elif controllerType == 'inband':
2860                     net.addController(name=name,
2861                                       controller=InbandController,
2862                                       ip=controllerIP,
2863                                       protocol=controllerProtocol,
2864                                       port=controllerPort)
2865                 elif controllerType == 'ovsc':
2866                     net.addController(name=name,
2867                                       controller=OVSController,
2868                                       protocol=controllerProtocol,
2869                                       port=controllerPort)
2870                 else:
2871                     net.addController(name=name,
2872                                       controller=Controller,
2873                                       protocol=controllerProtocol,
2874                                       port=controllerPort)
2875
2876             else:
2877                 raise Exception( "Cannot create mystery node: " + name )
2878
2879     @staticmethod
2880     def pathCheck( *args, **kwargs ):
2881         "Make sure each program in *args can be found in $PATH."
2882         moduleName = kwargs.get( 'moduleName', 'it' )
2883         for arg in args:
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.' )
2889
2890     def buildLinks( self, net):
2891         # Make links
2892         info( "Getting Links.\n" )
2893         for key,link in self.links.items():
2894             tags = self.canvas.gettags(key)
2895             if 'data' in tags:
2896                 src=link['src']
2897                 dst=link['dest']
2898                 linkopts=link['linkOpts']
2899                 srcName, dstName = src[ 'text' ], dst[ 'text' ]
2900                 srcNode, dstNode = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
2901                 if linkopts:
2902                     net.addLink(srcNode, dstNode, cls=TCLink, **linkopts)
2903                 else:
2904                     # debug( str(srcNode) )
2905                     # debug( str(dstNode), '\n' )
2906                     net.addLink(srcNode, dstNode)
2907                 self.canvas.itemconfig(key, dash=())
2908
2909
2910     def build( self ):
2911         "Build network based on our topology."
2912
2913         dpctl = None
2914         if len(self.appPrefs['dpctl']) > 0:
2915             dpctl = int(self.appPrefs['dpctl'])
2916         net = Mininet( topo=None,
2917                        listenPort=dpctl,
2918                        build=False,
2919                        ipBase=self.appPrefs['ipBase'] )
2920
2921         self.buildNodes(net)
2922         self.buildLinks(net)
2923
2924         # Build network (we have to do this separately at the moment )
2925         net.build()
2926
2927         return net
2928
2929
2930     def postStartSetup( self ):
2931
2932         # Setup host details
2933         for widget in self.widgetToItem:
2934             name = widget[ 'text' ]
2935             tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2936             if 'Host' in tags:
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'])
2953
2954
2955         # Configure NetFlow
2956         nflowValues = self.appPrefs['netflow']
2957         if len(nflowValues['nflowTarget']) > 0:
2958             nflowEnabled = False
2959             nflowSwitches = ''
2960             for widget in self.widgetToItem:
2961                 name = widget[ 'text' ]
2962                 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2963
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'
2970                             nflowEnabled=True
2971             if nflowEnabled:
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'
2975                 else:
2976                     nflowCmd = nflowCmd + ' add_id_to_interface=false'
2977                 info( 'cmd = '+nflowCmd+nflowSwitches, '\n' )
2978                 call(nflowCmd+nflowSwitches, shell=True)
2979
2980             else:
2981                 info( 'No switches with Netflow\n' )
2982         else:
2983             info( 'No NetFlow targets specified.\n' )
2984
2985         # Configure sFlow
2986         sflowValues = self.appPrefs['sflow']
2987         if len(sflowValues['sflowTarget']) > 0:
2988             sflowEnabled = False
2989             sflowSwitches = ''
2990             for widget in self.widgetToItem:
2991                 name = widget[ 'text' ]
2992                 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2993
2994                 if 'Switch' in tags:
2995                     opts = self.switchOpts[name]
2996                     if 'sflow' in opts:
2997                         if opts['sflow'] == '1':
2998                             info( name+' has sflow enabled\n' )
2999                             sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
3000                             sflowEnabled=True
3001             if sflowEnabled:
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)
3005
3006             else:
3007                 info( 'No switches with sflow\n' )
3008         else:
3009             info( 'No sFlow targets specified.\n' )
3010
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")
3015             CLI(self.net)
3016
3017     def start( self ):
3018         "Start network."
3019         if self.net is None:
3020             self.net = self.build()
3021
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.
3025             #self.net.start()
3026             info( '**** Starting %s controllers\n' % len( self.net.controllers ) )
3027             for controller in self.net.controllers:
3028                 info( str(controller) + ' ')
3029                 controller.start()
3030             info('\n')
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))
3043                     info( name + ' ')
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( [] )
3048                     info( name + ' ')
3049             info('\n')
3050
3051             self.postStartSetup()
3052
3053     def stop( self ):
3054         "Stop network."
3055         if self.net is not None:
3056             # Stop host details
3057             for widget in self.widgetToItem:
3058                 name = widget[ 'text' ]
3059                 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
3060                 if 'Host' in tags:
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'])
3072
3073             self.net.stop()
3074         cleanUpScreens()
3075         self.net = None
3076
3077     def do_linkPopup(self, event):
3078         # display the popup menu
3079         if self.net is None:
3080             try:
3081                 self.linkPopup.tk_popup(event.x_root, event.y_root, 0)
3082             finally:
3083                 # make sure to release the grab (Tk 8.0a1 only)
3084                 self.linkPopup.grab_release()
3085         else:
3086             try:
3087                 self.linkRunPopup.tk_popup(event.x_root, event.y_root, 0)
3088             finally:
3089                 # make sure to release the grab (Tk 8.0a1 only)
3090                 self.linkRunPopup.grab_release()
3091
3092     def do_controllerPopup(self, event):
3093         # display the popup menu
3094         if self.net is None:
3095             try:
3096                 self.controllerPopup.tk_popup(event.x_root, event.y_root, 0)
3097             finally:
3098                 # make sure to release the grab (Tk 8.0a1 only)
3099                 self.controllerPopup.grab_release()
3100
3101     def do_legacyRouterPopup(self, event):
3102         # display the popup menu
3103         if self.net is not None:
3104             try:
3105                 self.legacyRouterRunPopup.tk_popup(event.x_root, event.y_root, 0)
3106             finally:
3107                 # make sure to release the grab (Tk 8.0a1 only)
3108                 self.legacyRouterRunPopup.grab_release()
3109
3110     def do_hostPopup(self, event):
3111         # display the popup menu
3112         if self.net is None:
3113             try:
3114                 self.hostPopup.tk_popup(event.x_root, event.y_root, 0)
3115             finally:
3116                 # make sure to release the grab (Tk 8.0a1 only)
3117                 self.hostPopup.grab_release()
3118         else:
3119             try:
3120                 self.hostRunPopup.tk_popup(event.x_root, event.y_root, 0)
3121             finally:
3122                 # make sure to release the grab (Tk 8.0a1 only)
3123                 self.hostRunPopup.grab_release()
3124
3125     def do_legacySwitchPopup(self, event):
3126         # display the popup menu
3127         if self.net is not None:
3128             try:
3129                 self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
3130             finally:
3131                 # make sure to release the grab (Tk 8.0a1 only)
3132                 self.switchRunPopup.grab_release()
3133
3134     def do_switchPopup(self, event):
3135         # display the popup menu
3136         if self.net is None:
3137             try:
3138                 self.switchPopup.tk_popup(event.x_root, event.y_root, 0)
3139             finally:
3140                 # make sure to release the grab (Tk 8.0a1 only)
3141                 self.switchPopup.grab_release()
3142         else:
3143             try:
3144                 self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
3145             finally:
3146                 # make sure to release the grab (Tk 8.0a1 only)
3147                 self.switchRunPopup.grab_release()
3148
3149     def xterm( self, _ignore=None ):
3150         "Make an xterm when a button is pressed."
3151         if ( self.selection is None or
3152              self.net is None or
3153              self.selection not in self.itemToWidget ):
3154             return
3155         name = self.itemToWidget[ self.selection ][ 'text' ]
3156         if name not in self.net.nameToNode:
3157             return
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
3161         else:
3162             self.net.terms.append(term)
3163
3164     def iperf( self, _ignore=None ):
3165         "Make an xterm when a button is pressed."
3166         if ( self.selection is None or
3167              self.net is None or
3168              self.selection not in self.itemToWidget ):
3169             return
3170         name = self.itemToWidget[ self.selection ][ 'text' ]
3171         if name not in self.net.nameToNode:
3172             return
3173         self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' )
3174
3175     ### BELOW HERE IS THE TOPOLOGY IMPORT CODE ###
3176
3177     def parseArgs( self ):
3178         """Parse command-line args and return options object.
3179            returns: opts parse options dict"""
3180
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 )
3186             else:
3187                 raise Exception( 'Custom file name not found' )
3188
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." )
3192
3193         usage = ( '%prog [options]\n'
3194                   '(type %prog -h for details)' )
3195
3196         opts = OptionParser( description=desc, usage=usage )
3197
3198         addDictOption( opts, TOPOS, TOPODEF, 'topo' )
3199         addDictOption( opts, LINKS, LINKDEF, 'link' )
3200
3201         opts.add_option( '--custom', type='string', default=None,
3202                          help='read custom topo and node params from .py' +
3203                          'file' )
3204
3205         self.options, self.args = opts.parse_args()
3206         # We don't accept extra arguments after the options
3207         if self.args:
3208             opts.print_help()
3209             exit()
3210
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
3220         else:
3221             # Add or modify global variable or class
3222             globals()[ name ] = value
3223
3224     def parseCustomFile( self, fileName ):
3225         "Parse custom file and add params before parsing cmd-line options."
3226         customs = {}
3227         if os.path.isfile( fileName ):
3228             execfile( fileName, customs, customs )
3229             for name, val in customs.items():
3230                 self.setCustom( name, val )
3231         else:
3232             raise Exception( 'could not find custom file: %s' % fileName )
3233
3234     def importTopo( self ):
3235         info( 'topo='+self.options.topo, '\n' )
3236         if self.options.topo == 'none':
3237             return
3238         self.newTopology()
3239         topo = buildTopo( TOPOS, self.options.topo )
3240         link = customClass( LINKS, self.options.link )
3241         importNet = Mininet(topo=topo, build=False, link=link)
3242         importNet.build()
3243
3244         c = self.canvas
3245         rowIncrement = 100
3246         currentY = 100
3247
3248         # Add Controllers
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',
3258                       'hostname': name,
3259                       'controllerProtocol': controller.protocol,
3260                       'remoteIP': controller.ip,
3261                       'remotePort': controller.port}
3262             self.controllers[name] = ctrlr
3263
3264
3265
3266         currentY = currentY + rowIncrement
3267
3268         # Add switches
3269         info( 'switches:'+str(len(importNet.switches)), '\n' )
3270         columnCount = 0
3271         for switch in importNet.switches:
3272             name = switch.name
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']=[]
3278
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),
3290                                           float(currentY),
3291                                           dx,
3292                                           dy,
3293                                           width=4,
3294                                           fill='red',
3295                                           dash=(6, 4, 2, 4),
3296                                           tag='link' )
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:
3302                 columnCount = 0
3303                 currentY = currentY + rowIncrement
3304             else:
3305                 columnCount =columnCount+1
3306
3307
3308         currentY = currentY + rowIncrement
3309         # Add hosts
3310         info( 'hosts:'+str(len(importNet.hosts)), '\n' )
3311         columnCount = 0
3312         for host in importNet.hosts:
3313             name = host.name
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()
3318
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:
3325                 columnCount = 0
3326                 currentY = currentY + rowIncrement
3327             else:
3328                 columnCount =columnCount+1
3329
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' )
3334             srcNode = link[0]
3335             src = self.findWidgetByName(srcNode)
3336             sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
3337
3338             destNode = link[1]
3339             dest = self.findWidgetByName(destNode)
3340             dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
3341
3342             params = topo.linkInfo( srcNode, destNode )
3343             info( 'Link Parameters='+str(params), '\n' )
3344
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
3351
3352         importNet.stop()
3353
3354 def miniEditImages():
3355     "Create and return images for MiniEdit."
3356
3357     # Image data. Git will be unhappy. However, the alternative
3358     # is to keep track of separate binary files, which is also
3359     # unappealing.
3360
3361     return {
3362         'Select': BitmapImage(
3363             file='/usr/include/X11/bitmaps/left_ptr' ),
3364
3365         'Switch': PhotoImage( data=r"""
3366 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
3397             """),
3398
3399         'LegacySwitch': PhotoImage( data=r"""
3400 R0lGODlhMgAYAPcAAAEBAXmDjbe4uAE5cjF7xwFWq2Sa0S9biSlrrdTW1k2Ly02a5xUvSQFHjmep
3401 6bfI2Q5SlQIYLwFfvj6M3Jaan8fHyDuFzwFp0Vah60uU3AEiRhFgrgFRogFr10N9uTFrpytHYQFM
3402 mGWt9wIwX+bm5kaT4gtFgR1cnJPF9yt80CF0yAIMGHmp2c/P0AEoUb/P4Fei7qK4zgpLjgFkyQlf
3403 t1mf5jKD1WWJrQ86ZwFAgBhYmVOa4MPV52uv8y+A0iR3ywFbtUyX5ECI0Q1UmwIcOUGQ3RBXoQI0
3404 aRJbpr3BxVeJvQUJDafH5wIlS2aq7xBmv52lr7fH12el5Wml3097ph1ru7vM3HCz91Ke6lid40KQ
3405 4GSQvgQGClFnfwVJjszMzVCX3hljrdPT1AFLlBRnutPf6yd5zjeI2QE9eRBdrBNVl+3v70mV4ydf
3406 lwMVKwErVlul8AFChTGB1QE3bsTFxQImTVmAp0FjiUSM1k+b6QQvWQ1SlxMgLgFixEqU3xJhsgFT
3407 pn2Xs5OluZ+1yz1Xb6HN+Td9wy1zuYClykV5r0x2oeDh4qmvt8LDwxhuxRlLfyRioo2124mft9bi
3408 71mDr7fT79nl8Z2hpQs9b7vN4QMQIOPj5XOPrU2Jx32z6xtvwzeBywFFikFnjwcPFa29yxJjuFmP
3409 xQFv3qGxwRc/Z8vb6wsRGBNqwqmpqTdvqQIbNQFPngMzZAEfP0mQ13mHlQFYsAFnznOXu2mPtQxj
3410 vQ1Vn4Ot1+/x8my0/CJgnxNNh8DT5CdJaWyx+AELFWmt8QxPkxBZpwMFB015pgFduGCNuyx7zdnZ
3411 2WKm6h1xyOPp8aW70QtPkUmM0LrCyr/FyztljwFPm0OJzwFny7/L1xFjswE/e12i50iR2VR8o2Gf
3412 3xszS2eTvz2BxSlloQdJiwMHDzF3u7bJ3T2I1WCp8+Xt80FokQFJklef6mORw2ap7SJ1y77Q47nN
3413 3wFfu1Kb5cXJyxdhrdDR0wlNkTSF11Oa4yp4yQEuW0WQ3QIDBQI7dSH5BAEAAAAALAAAAAAyABgA
3414 Bwj/AAEIHDjKF6SDvhImPMHwhA6HOiLqUENRDYSLEIplxBcNHz4Z5GTI8BLKS5OBA1Ply2fDhxwf
3415 PlLITGFmmRkzP+DlVKHCmU9nnz45csSqKKsn9gileZKrVC4aRFACOGZu5UobNuRohRkzhc2b+36o
3416 qCaqrFmzZEV1ERBg3BOmMl5JZTBhwhm7ZyycYZnvJdeuNl21qkCHTiPDhxspTtKoQgUKCJ6wehMV
3417 5QctWupeo6TkjOd8e1lmdQkTGbTTMaDFiDGINeskX6YhEicUiQa5A/kUKaFFwQ0oXzjZ8Tbcm3Hj
3418 irwpMtTSgg9QMJf5WEZ9375AiED19ImpSQSUB4Kw/8HFSMyiRWJaqG/xhf2X91+oCbmq1e/MFD/2
3419 EcApVkWVJhp8J9AqsywQxDfAbLJJPAy+kMkL8shjxTkUnhOJZ5+JVp8cKfhwxwdf4fQLgG4MFAwW
3420 KOZRAxM81EAPPQvoE0QQfrDhx4399OMBMjz2yCMVivCoCAWXKLKMTPvoUYcsKwi0RCcwYCAlFjU0
3421 A6OBM4pXAhsl8FYELYWFWZhiZCbRQgIC2AGTLy408coxAoEDx5wwtGPALTVg0E4NKC7gp4FsBKoA
3422 Ki8U+oIVmVih6DnZPMBMAlGwIARWOLiggSYC+ZNIOulwY4AkSZCyxaikbqHMqaeaIp4+rAaxQxBg
3423 2P+IozuRzvLZIS4syYVAfMAhwhSC1EPCGoskIIYY9yS7Hny75OFnEIAGyiVvWkjjRxF11fXIG3WU
3424 KNA6wghDTCW88PKMJZOkm24Z7LarSjPtoIjFn1lKyyVmmBVhwRtvaDDMgFL0Eu4VhaiDwhXCXNFD
3425 D8QQw7ATEDsBw8RSxotFHs7CKJ60XWrRBj91EOGPQCA48c7J7zTjSTPctOzynjVkkYU+O9S8Axg4
3426 Z6BzBt30003Ps+AhNB5C4PCGC5gKJMMTZJBRytOl/CH1HxvQkMbVVxujtdZGGKGL17rsEfYQe+xR
3427 zNnFcGQCv7LsKlAtp8R9Sgd0032BLXjPoPcMffTd3YcEgAMOxOBA1GJ4AYgXAMjiHDTgggveCgRI
3428 3RfcnffefgcOeDKEG3444osDwgEspMNiTQhx5FoOShxcrrfff0uQjOycD+554qFzMHrpp4cwBju/
3429 5+CmVNbArnntndeCO+O689777+w0IH0o1P/TRJMohRA4EJwn47nyiocOSOmkn/57COxE3wD11Mfh
3430 fg45zCGyVF4Ufvvyze8ewv5jQK9++6FwXxzglwM0GPAfR8AeSo4gwAHCbxsQNCAa/kHBAVhwAHPI
3431 4BE2eIRYeHAEIBwBP0Y4Qn41YWRSCQgAOw==
3432             """),
3433
3434         'LegacyRouter': PhotoImage( data=r"""
3435 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
3464 gGPLHwLwcMIo12Qxu0ABAQA7
3465             """),
3466
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
3490             """),
3491
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/
3517             C8cSBBAQADs=
3518         """ ),
3519
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
3545         """ ),
3546
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=
3571         """ )
3572     }
3573
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
3580        help: string"""
3581     if default not in choicesDict:
3582         raise Exception( 'Invalid  default %s for choices dict: %s' %
3583                          ( default, name ) )
3584     if not helpStr:
3585         helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
3586                     '[,param=value...]' )
3587     opts.add_option( '--' + name,
3588                      type='string',
3589                      default = default,
3590                      help = helpStr )
3591
3592 if __name__ == '__main__':
3593     setLogLevel( 'info' )
3594     app = MiniEdit()
3595     ### import topology if specified ###
3596     app.parseArgs()
3597     app.importTopo()
3598
3599     app.mainloop()