Statistics
| Branch: | Tag: | Revision:

mininet / examples / miniedit.py @ 736db20c

History | View | Annotate | Download (152 KB)

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_VERSION = '2.2.0.1'
17

    
18
from optparse import OptionParser
19
from Tkinter import *
20
from ttk import Notebook
21
from tkMessageBox import showinfo, showerror, showwarning
22
from subprocess import call
23
import tkFont
24
import csv
25
import tkFileDialog
26
import tkSimpleDialog
27
import re
28
import json
29
from distutils.version import StrictVersion
30
import os
31
import sys
32
from functools import partial
33

    
34
if 'PYTHONPATH' in os.environ:
35
    sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
36

    
37
# someday: from ttk import *
38

    
39
from mininet.log import info, error, debug, output, setLogLevel
40
from mininet.net import Mininet, VERSION
41
from mininet.util import ipStr, netParse, ipAdd, quietRun
42
from mininet.util import buildTopo
43
from mininet.util import custom, customConstructor
44
from mininet.term import makeTerm, cleanUpScreens
45
from mininet.node import Controller, RemoteController, NOX, OVSController
46
from mininet.node import CPULimitedHost, Host, Node
47
from mininet.node import OVSKernelSwitch, OVSSwitch, UserSwitch
48
from mininet.link import TCLink, Intf, Link
49
from mininet.cli import CLI
50
from mininet.moduledeps import moduleDeps, pathCheck
51
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
52
from mininet.topolib import TreeTopo
53

    
54
print 'MiniEdit running against MiniNet '+VERSION
55
MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION)
56
if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
57
    from mininet.node import IVSSwitch
58

    
59
TOPODEF = 'none'
60
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
61
          'linear': LinearTopo,
62
          'reversed': SingleSwitchReversedTopo,
63
          'single': SingleSwitchTopo,
64
          'none': None,
65
          'tree': TreeTopo }
66
CONTROLLERDEF = 'ref'
67
CONTROLLERS = { 'ref': Controller,
68
                'ovsc': OVSController,
69
                'nox': NOX,
70
                'remote': RemoteController,
71
                'none': lambda name: None }
72
LINKDEF = 'default'
73
LINKS = { 'default': Link,
74
          'tc': TCLink }
75
HOSTDEF = 'proc'
76
HOSTS = { 'proc': Host,
77
          'rt': custom( CPULimitedHost, sched='rt' ),
78
          'cfs': custom( CPULimitedHost, sched='cfs' ) }
79

    
80

    
81
class InbandController( RemoteController ):
82

    
83
    def checkListening( self ):
84
        "Overridden to do nothing."
85
        return
86

    
87
class CustomUserSwitch(UserSwitch):
88
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
89
        UserSwitch.__init__( self, name, **kwargs )
90
        self.switchIP = None
91

    
92
    def getSwitchIP(self):
93
        return self.switchIP
94

    
95
    def setSwitchIP(self, ip):
96
        self.switchIP = ip
97

    
98
    def start( self, controllers ):
99
        # Call superclass constructor
100
        UserSwitch.start( self, controllers )
101
        # Set Switch IP address
102
        if (self.switchIP is not None):
103
            if not self.inNamespace:
104
                self.cmd( 'ifconfig', self, self.switchIP )
105
            else:
106
                self.cmd( 'ifconfig lo', self.switchIP )
107

    
108
class LegacyRouter( Node ):
109

    
110
    def __init__( self, name, inNamespace=True, **params ):
111
        Node.__init__( self, name, inNamespace, **params )
112

    
113
    def config( self, **_params ):
114
        if self.intfs:
115
            self.setParam( _params, 'setIP', ip='0.0.0.0' )
116
        r = Node.config( self, **_params )
117
        self.cmd('sysctl -w net.ipv4.ip_forward=1')
118
        return r
119

    
120
class LegacySwitch(OVSSwitch):
121

    
122
    def __init__( self, name, **params ):
123
        OVSSwitch.__init__( self, name, failMode='standalone', **params )
124
        self.switchIP = None
125

    
126
class customOvs(OVSSwitch):
127

    
128
    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
129
        OVSSwitch.__init__( self, name, failMode=failMode, datapath=datapath, **params )
130
        self.switchIP = None
131

    
132
    def getSwitchIP(self):
133
        return self.switchIP
134

    
135
    def setSwitchIP(self, ip):
136
        self.switchIP = ip
137

    
138
    def start( self, controllers ):
139
        # Call superclass constructor
140
        OVSSwitch.start( self, controllers )
141
        # Set Switch IP address
142
        if (self.switchIP is not None):
143
            self.cmd( 'ifconfig', self, self.switchIP )
144

    
145
class PrefsDialog(tkSimpleDialog.Dialog):
146

    
147
        def __init__(self, parent, title, prefDefaults):
148

    
149
            self.prefValues = prefDefaults
150

    
151
            tkSimpleDialog.Dialog.__init__(self, parent, title)
152

    
153
        def body(self, master):
154
            self.rootFrame = master
155
            self.leftfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
156
            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2)
157
            self.rightfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
158
            self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2)
159

    
160

    
161
            # Field for Base IP
162
            Label(self.leftfieldFrame, text="IP Base:").grid(row=0, sticky=E)
163
            self.ipEntry = Entry(self.leftfieldFrame)
164
            self.ipEntry.grid(row=0, column=1)
165
            ipBase =  self.prefValues['ipBase']
166
            self.ipEntry.insert(0, ipBase)
167

    
168
            # Selection of terminal type
169
            Label(self.leftfieldFrame, text="Default Terminal:").grid(row=1, sticky=E)
170
            self.terminalVar = StringVar(self.leftfieldFrame)
171
            self.terminalOption = OptionMenu(self.leftfieldFrame, self.terminalVar, "xterm", "gterm")
172
            self.terminalOption.grid(row=1, column=1, sticky=W)
173
            terminalType = self.prefValues['terminalType']
174
            self.terminalVar.set(terminalType)
175

    
176
            # Field for CLI
177
            Label(self.leftfieldFrame, text="Start CLI:").grid(row=2, sticky=E)
178
            self.cliStart = IntVar()
179
            self.cliButton = Checkbutton(self.leftfieldFrame, variable=self.cliStart)
180
            self.cliButton.grid(row=2, column=1, sticky=W)
181
            if self.prefValues['startCLI'] == '0':
182
                self.cliButton.deselect()
183
            else:
184
                self.cliButton.select()
185

    
186
            # Selection of switch type
187
            Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E)
188
            self.switchType = StringVar(self.leftfieldFrame)
189
            self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace")
190
            self.switchTypeMenu.grid(row=3, column=1, sticky=W)
191
            switchTypePref = self.prefValues['switchType']
192
            if switchTypePref == 'ivs':
193
                self.switchType.set("Indigo Virtual Switch")
194
            elif switchTypePref == 'userns':
195
                self.switchType.set("Userspace Switch inNamespace")
196
            elif switchTypePref == 'user':
197
                self.switchType.set("Userspace Switch")
198
            else:
199
                self.switchType.set("Open vSwitch Kernel Mode")
200

    
201

    
202
            # Fields for OVS OpenFlow version
203
            ovsFrame= LabelFrame(self.leftfieldFrame, text='Open vSwitch', padx=5, pady=5)
204
            ovsFrame.grid(row=4, column=0, columnspan=2, sticky=EW)
205
            Label(ovsFrame, text="OpenFlow 1.0:").grid(row=0, sticky=E)
206
            Label(ovsFrame, text="OpenFlow 1.1:").grid(row=1, sticky=E)
207
            Label(ovsFrame, text="OpenFlow 1.2:").grid(row=2, sticky=E)
208
            Label(ovsFrame, text="OpenFlow 1.3:").grid(row=3, sticky=E)
209

    
210
            self.ovsOf10 = IntVar()
211
            self.covsOf10 = Checkbutton(ovsFrame, variable=self.ovsOf10)
212
            self.covsOf10.grid(row=0, column=1, sticky=W)
213
            if self.prefValues['openFlowVersions']['ovsOf10'] == '0':
214
                self.covsOf10.deselect()
215
            else:
216
                self.covsOf10.select()
217

    
218
            self.ovsOf11 = IntVar()
219
            self.covsOf11 = Checkbutton(ovsFrame, variable=self.ovsOf11)
220
            self.covsOf11.grid(row=1, column=1, sticky=W)
221
            if self.prefValues['openFlowVersions']['ovsOf11'] == '0':
222
                self.covsOf11.deselect()
223
            else:
224
                self.covsOf11.select()
225

    
226
            self.ovsOf12 = IntVar()
227
            self.covsOf12 = Checkbutton(ovsFrame, variable=self.ovsOf12)
228
            self.covsOf12.grid(row=2, column=1, sticky=W)
229
            if self.prefValues['openFlowVersions']['ovsOf12'] == '0':
230
                self.covsOf12.deselect()
231
            else:
232
                self.covsOf12.select()
233

    
234
            self.ovsOf13 = IntVar()
235
            self.covsOf13 = Checkbutton(ovsFrame, variable=self.ovsOf13)
236
            self.covsOf13.grid(row=3, column=1, sticky=W)
237
            if self.prefValues['openFlowVersions']['ovsOf13'] == '0':
238
                self.covsOf13.deselect()
239
            else:
240
                self.covsOf13.select()
241

    
242
            # Field for DPCTL listen port
243
            Label(self.leftfieldFrame, text="dpctl port:").grid(row=5, sticky=E)
244
            self.dpctlEntry = Entry(self.leftfieldFrame)
245
            self.dpctlEntry.grid(row=5, column=1)
246
            if 'dpctl' in self.prefValues:
247
                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
248

    
249
            # sFlow
250
            sflowValues = self.prefValues['sflow']
251
            self.sflowFrame= LabelFrame(self.rightfieldFrame, text='sFlow Profile for Open vSwitch', padx=5, pady=5)
252
            self.sflowFrame.grid(row=0, column=0, columnspan=2, sticky=EW)
253

    
254
            Label(self.sflowFrame, text="Target:").grid(row=0, sticky=E)
255
            self.sflowTarget = Entry(self.sflowFrame)
256
            self.sflowTarget.grid(row=0, column=1)
257
            self.sflowTarget.insert(0, sflowValues['sflowTarget'])
258

    
259
            Label(self.sflowFrame, text="Sampling:").grid(row=1, sticky=E)
260
            self.sflowSampling = Entry(self.sflowFrame)
261
            self.sflowSampling.grid(row=1, column=1)
262
            self.sflowSampling.insert(0, sflowValues['sflowSampling'])
263

    
264
            Label(self.sflowFrame, text="Header:").grid(row=2, sticky=E)
265
            self.sflowHeader = Entry(self.sflowFrame)
266
            self.sflowHeader.grid(row=2, column=1)
267
            self.sflowHeader.insert(0, sflowValues['sflowHeader'])
268

    
269
            Label(self.sflowFrame, text="Polling:").grid(row=3, sticky=E)
270
            self.sflowPolling = Entry(self.sflowFrame)
271
            self.sflowPolling.grid(row=3, column=1)
272
            self.sflowPolling.insert(0, sflowValues['sflowPolling'])
273

    
274
            # NetFlow
275
            nflowValues = self.prefValues['netflow']
276
            self.nFrame= LabelFrame(self.rightfieldFrame, text='NetFlow Profile for Open vSwitch', padx=5, pady=5)
277
            self.nFrame.grid(row=1, column=0, columnspan=2, sticky=EW)
278

    
279
            Label(self.nFrame, text="Target:").grid(row=0, sticky=E)
280
            self.nflowTarget = Entry(self.nFrame)
281
            self.nflowTarget.grid(row=0, column=1)
282
            self.nflowTarget.insert(0, nflowValues['nflowTarget'])
283

    
284
            Label(self.nFrame, text="Active Timeout:").grid(row=1, sticky=E)
285
            self.nflowTimeout = Entry(self.nFrame)
286
            self.nflowTimeout.grid(row=1, column=1)
287
            self.nflowTimeout.insert(0, nflowValues['nflowTimeout'])
288

    
289
            Label(self.nFrame, text="Add ID to Interface:").grid(row=2, sticky=E)
290
            self.nflowAddId = IntVar()
291
            self.nflowAddIdButton = Checkbutton(self.nFrame, variable=self.nflowAddId)
292
            self.nflowAddIdButton.grid(row=2, column=1, sticky=W)
293
            if nflowValues['nflowAddId'] == '0':
294
                self.nflowAddIdButton.deselect()
295
            else:
296
                self.nflowAddIdButton.select()
297

    
298
            # initial focus
299
            return self.ipEntry
300

    
301
        def apply(self):
302
            ipBase = self.ipEntry.get()
303
            terminalType = self.terminalVar.get()
304
            startCLI = str(self.cliStart.get())
305
            sw = self.switchType.get()
306
            dpctl = self.dpctlEntry.get()
307

    
308
            ovsOf10 = str(self.ovsOf10.get())
309
            ovsOf11 = str(self.ovsOf11.get())
310
            ovsOf12 = str(self.ovsOf12.get())
311
            ovsOf13 = str(self.ovsOf13.get())
312

    
313
            sflowValues = {'sflowTarget':self.sflowTarget.get(),
314
                           'sflowSampling':self.sflowSampling.get(),
315
                           'sflowHeader':self.sflowHeader.get(),
316
                           'sflowPolling':self.sflowPolling.get()}
317
            nflowvalues = {'nflowTarget':self.nflowTarget.get(),
318
                           'nflowTimeout':self.nflowTimeout.get(),
319
                           'nflowAddId':str(self.nflowAddId.get())}
320
            self.result = {'ipBase':ipBase,
321
                           'terminalType':terminalType,
322
                           'dpctl':dpctl,
323
                           'sflow':sflowValues,
324
                           'netflow':nflowvalues,
325
                           'startCLI':startCLI}
326
            if sw == 'Indigo Virtual Switch':
327
                self.result['switchType'] = 'ivs'
328
                if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
329
                    self.ovsOk = False
330
                    showerror(title="Error",
331
                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
332
            elif sw == 'Userspace Switch':
333
                self.result['switchType'] = 'user'
334
            elif sw == 'Userspace Switch inNamespace':
335
                self.result['switchType'] = 'userns'
336
            else:
337
                self.result['switchType'] = 'ovs'
338

    
339
            self.ovsOk = True
340
            if ovsOf11 == "1":
341
                ovsVer = self.getOvsVersion()
342
                if StrictVersion(ovsVer) < StrictVersion('2.0'):
343
                    self.ovsOk = False
344
                    showerror(title="Error",
345
                              message='Open vSwitch version 2.0+ required. You have '+ovsVer+'.')
346
            if ovsOf12 == "1" or ovsOf13 == "1":
347
                ovsVer = self.getOvsVersion()
348
                if StrictVersion(ovsVer) < StrictVersion('1.10'):
349
                    self.ovsOk = False
350
                    showerror(title="Error",
351
                              message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.')
352

    
353
            if self.ovsOk:
354
                self.result['openFlowVersions']={'ovsOf10':ovsOf10,
355
                                                 'ovsOf11':ovsOf11,
356
                                                 'ovsOf12':ovsOf12,
357
                                                 'ovsOf13':ovsOf13}
358
            else:
359
                self.result = None
360

    
361
        def getOvsVersion(self):
362
            outp = quietRun("ovs-vsctl show")
363
            r = r'ovs_version: "(.*)"'
364
            m = re.search(r, outp)
365
            if m is None:
366
                print 'Version check failed'
367
                return None
368
            else:
369
                print 'Open vSwitch version is '+m.group(1)
370
                return m.group(1)
371

    
372

    
373
class CustomDialog(object):
374

    
375
        # TODO: Fix button placement and Title and window focus lock
376
        def __init__(self, master, title):
377
            self.top=Toplevel(master)
378

    
379
            self.bodyFrame = Frame(self.top)
380
            self.bodyFrame.grid(row=0, column=0, sticky='nswe')
381
            self.body(self.bodyFrame)
382

    
383
            #return self.b # initial focus
384
            buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey')
385
            buttonFrame.grid(row=1 , column=0, sticky='nswe')
386

    
387
            okButton = Button(buttonFrame, width=8, text='OK', relief='groove',
388
                       bd=4, command=self.okAction)
389
            okButton.grid(row=0, column=0, sticky=E)
390

    
391
            canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove',
392
                        bd=4, command=self.cancelAction)
393
            canlceButton.grid(row=0, column=1, sticky=W)
394

    
395
        def body(self, master):
396
            self.rootFrame = master
397

    
398
        def apply(self):
399
            self.top.destroy()
400

    
401
        def cancelAction(self):
402
            self.top.destroy()
403

    
404
        def okAction(self):
405
            self.apply()
406
            self.top.destroy()
407

    
408
class HostDialog(CustomDialog):
409

    
410
        def __init__(self, master, title, prefDefaults):
411

    
412
            self.prefValues = prefDefaults
413
            self.result = None
414

    
415
            CustomDialog.__init__(self, master, title)
416

    
417
        def body(self, master):
418
            self.rootFrame = master
419
            n = Notebook(self.rootFrame)
420
            self.propFrame = Frame(n)
421
            self.vlanFrame = Frame(n)
422
            self.interfaceFrame = Frame(n)
423
            self.mountFrame = Frame(n)
424
            n.add(self.propFrame, text='Properties')
425
            n.add(self.vlanFrame, text='VLAN Interfaces')
426
            n.add(self.interfaceFrame, text='External Interfaces')
427
            n.add(self.mountFrame, text='Private Directories')
428
            n.pack()
429

    
430
            ### TAB 1
431
            # Field for Hostname
432
            Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E)
433
            self.hostnameEntry = Entry(self.propFrame)
434
            self.hostnameEntry.grid(row=0, column=1)
435
            if 'hostname' in self.prefValues:
436
                self.hostnameEntry.insert(0, self.prefValues['hostname'])
437

    
438
            # Field for Switch IP
439
            Label(self.propFrame, text="IP Address:").grid(row=1, sticky=E)
440
            self.ipEntry = Entry(self.propFrame)
441
            self.ipEntry.grid(row=1, column=1)
442
            if 'ip' in self.prefValues:
443
                self.ipEntry.insert(0, self.prefValues['ip'])
444

    
445
            # Field for default route
446
            Label(self.propFrame, text="Default Route:").grid(row=2, sticky=E)
447
            self.routeEntry = Entry(self.propFrame)
448
            self.routeEntry.grid(row=2, column=1)
449
            if 'defaultRoute' in self.prefValues:
450
                self.routeEntry.insert(0, self.prefValues['defaultRoute'])
451

    
452
            # Field for CPU
453
            Label(self.propFrame, text="Amount CPU:").grid(row=3, sticky=E)
454
            self.cpuEntry = Entry(self.propFrame)
455
            self.cpuEntry.grid(row=3, column=1)
456
            if 'cpu' in self.prefValues:
457
                self.cpuEntry.insert(0, str(self.prefValues['cpu']))
458
            # Selection of Scheduler
459
            if 'sched' in self.prefValues:
460
                sched =  self.prefValues['sched']
461
            else:
462
                sched = 'host'
463
            self.schedVar = StringVar(self.propFrame)
464
            self.schedOption = OptionMenu(self.propFrame, self.schedVar, "host", "cfs", "rt")
465
            self.schedOption.grid(row=3, column=2, sticky=W)
466
            self.schedVar.set(sched)
467

    
468
            # Selection of Cores
469
            Label(self.propFrame, text="Cores:").grid(row=4, sticky=E)
470
            self.coreEntry = Entry(self.propFrame)
471
            self.coreEntry.grid(row=4, column=1)
472
            if 'cores' in self.prefValues:
473
                self.coreEntry.insert(1, self.prefValues['cores'])
474

    
475
            # Start command
476
            Label(self.propFrame, text="Start Command:").grid(row=5, sticky=E)
477
            self.startEntry = Entry(self.propFrame)
478
            self.startEntry.grid(row=5, column=1, sticky='nswe', columnspan=3)
479
            if 'startCommand' in self.prefValues:
480
                self.startEntry.insert(0, str(self.prefValues['startCommand']))
481
            # Stop command
482
            Label(self.propFrame, text="Stop Command:").grid(row=6, sticky=E)
483
            self.stopEntry = Entry(self.propFrame)
484
            self.stopEntry.grid(row=6, column=1, sticky='nswe', columnspan=3)
485
            if 'stopCommand' in self.prefValues:
486
                self.stopEntry.insert(0, str(self.prefValues['stopCommand']))
487

    
488
            ### TAB 2
489
            # External Interfaces
490
            self.externalInterfaces = 0
491
            Label(self.interfaceFrame, text="External Interface:").grid(row=0, column=0, sticky=E)
492
            self.b = Button( self.interfaceFrame, text='Add', command=self.addInterface)
493
            self.b.grid(row=0, column=1)
494

    
495
            self.interfaceFrame = VerticalScrolledTable(self.interfaceFrame, rows=0, columns=1, title='External Interfaces')
496
            self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
497
            self.tableFrame = self.interfaceFrame.interior
498
            self.tableFrame.addRow(value=['Interface Name'], readonly=True)
499

    
500
            # Add defined interfaces
501
            externalInterfaces = []
502
            if 'externalInterfaces' in self.prefValues:
503
                externalInterfaces = self.prefValues['externalInterfaces']
504

    
505
            for externalInterface in externalInterfaces:
506
                self.tableFrame.addRow(value=[externalInterface])
507

    
508
            ### TAB 3
509
            # VLAN Interfaces
510
            self.vlanInterfaces = 0
511
            Label(self.vlanFrame, text="VLAN Interface:").grid(row=0, column=0, sticky=E)
512
            self.vlanButton = Button( self.vlanFrame, text='Add', command=self.addVlanInterface)
513
            self.vlanButton.grid(row=0, column=1)
514

    
515
            self.vlanFrame = VerticalScrolledTable(self.vlanFrame, rows=0, columns=2, title='VLAN Interfaces')
516
            self.vlanFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
517
            self.vlanTableFrame = self.vlanFrame.interior
518
            self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True)
519

    
520
            vlanInterfaces = []
521
            if 'vlanInterfaces' in self.prefValues:
522
                vlanInterfaces = self.prefValues['vlanInterfaces']
523
            for vlanInterface in vlanInterfaces:
524
                self.vlanTableFrame.addRow(value=vlanInterface)
525

    
526
            ### TAB 4
527
            # Private Directories
528
            self.privateDirectories = 0
529
            Label(self.mountFrame, text="Private Directory:").grid(row=0, column=0, sticky=E)
530
            self.mountButton = Button( self.mountFrame, text='Add', command=self.addDirectory)
531
            self.mountButton.grid(row=0, column=1)
532

    
533
            self.mountFrame = VerticalScrolledTable(self.mountFrame, rows=0, columns=2, title='Directories')
534
            self.mountFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
535
            self.mountTableFrame = self.mountFrame.interior
536
            self.mountTableFrame.addRow(value=['Mount','Persistent Directory'], readonly=True)
537

    
538
            directoryList = []
539
            if 'privateDirectory' in self.prefValues:
540
                directoryList = self.prefValues['privateDirectory']
541
            for privateDir in directoryList:
542
                if isinstance( privateDir, tuple ):
543
                    self.mountTableFrame.addRow(value=privateDir)
544
                else:
545
                    self.mountTableFrame.addRow(value=[privateDir,''])
546

    
547

    
548
        def addDirectory( self ):
549
            self.mountTableFrame.addRow()
550

    
551
        def addVlanInterface( self ):
552
            self.vlanTableFrame.addRow()
553

    
554
        def addInterface( self ):
555
            self.tableFrame.addRow()
556

    
557
        def apply(self):
558
            externalInterfaces = []
559
            for row in range(self.tableFrame.rows):
560
                if (len(self.tableFrame.get(row, 0)) > 0 and
561
                    row > 0):
562
                    externalInterfaces.append(self.tableFrame.get(row, 0))
563
            vlanInterfaces = []
564
            for row in range(self.vlanTableFrame.rows):
565
                if (len(self.vlanTableFrame.get(row, 0)) > 0 and
566
                    len(self.vlanTableFrame.get(row, 1)) > 0 and
567
                    row > 0):
568
                    vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)])
569
            privateDirectories = []
570
            for row in range(self.mountTableFrame.rows):
571
                if (len(self.mountTableFrame.get(row, 0)) > 0 and row > 0):
572
                    if(len(self.mountTableFrame.get(row, 1)) > 0):
573
                        privateDirectories.append((self.mountTableFrame.get(row, 0), self.mountTableFrame.get(row, 1)))
574
                    else:
575
                        privateDirectories.append(self.mountTableFrame.get(row, 0))
576

    
577
            results = {'cpu': self.cpuEntry.get(),
578
                       'cores':self.coreEntry.get(),
579
                       'sched':self.schedVar.get(),
580
                       'hostname':self.hostnameEntry.get(),
581
                       'ip':self.ipEntry.get(),
582
                       'defaultRoute':self.routeEntry.get(),
583
                       'startCommand':self.startEntry.get(),
584
                       'stopCommand':self.stopEntry.get(),
585
                       'privateDirectory':privateDirectories,
586
                       'externalInterfaces':externalInterfaces,
587
                       'vlanInterfaces':vlanInterfaces}
588
            self.result = results
589

    
590
class SwitchDialog(CustomDialog):
591

    
592
        def __init__(self, master, title, prefDefaults):
593

    
594
            self.prefValues = prefDefaults
595
            self.result = None
596
            CustomDialog.__init__(self, master, title)
597

    
598
        def body(self, master):
599
            self.rootFrame = master
600
            self.leftfieldFrame = Frame(self.rootFrame)
601
            self.rightfieldFrame = Frame(self.rootFrame)
602
            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe')
603
            self.rightfieldFrame.grid(row=0, column=1, sticky='nswe')
604

    
605
            rowCount = 0
606
            externalInterfaces = []
607
            if 'externalInterfaces' in self.prefValues:
608
                externalInterfaces = self.prefValues['externalInterfaces']
609

    
610
            # Field for Hostname
611
            Label(self.leftfieldFrame, text="Hostname:").grid(row=rowCount, sticky=E)
612
            self.hostnameEntry = Entry(self.leftfieldFrame)
613
            self.hostnameEntry.grid(row=rowCount, column=1)
614
            self.hostnameEntry.insert(0, self.prefValues['hostname'])
615
            rowCount+=1
616

    
617
            # Field for DPID
618
            Label(self.leftfieldFrame, text="DPID:").grid(row=rowCount, sticky=E)
619
            self.dpidEntry = Entry(self.leftfieldFrame)
620
            self.dpidEntry.grid(row=rowCount, column=1)
621
            if 'dpid' in self.prefValues:
622
                self.dpidEntry.insert(0, self.prefValues['dpid'])
623
            rowCount+=1
624

    
625
            # Field for Netflow
626
            Label(self.leftfieldFrame, text="Enable NetFlow:").grid(row=rowCount, sticky=E)
627
            self.nflow = IntVar()
628
            self.nflowButton = Checkbutton(self.leftfieldFrame, variable=self.nflow)
629
            self.nflowButton.grid(row=rowCount, column=1, sticky=W)
630
            if 'netflow' in self.prefValues:
631
                if self.prefValues['netflow'] == '0':
632
                    self.nflowButton.deselect()
633
                else:
634
                    self.nflowButton.select()
635
            else:
636
                self.nflowButton.deselect()
637
            rowCount+=1
638

    
639
            # Field for sflow
640
            Label(self.leftfieldFrame, text="Enable sFlow:").grid(row=rowCount, sticky=E)
641
            self.sflow = IntVar()
642
            self.sflowButton = Checkbutton(self.leftfieldFrame, variable=self.sflow)
643
            self.sflowButton.grid(row=rowCount, column=1, sticky=W)
644
            if 'sflow' in self.prefValues:
645
                if self.prefValues['sflow'] == '0':
646
                    self.sflowButton.deselect()
647
                else:
648
                    self.sflowButton.select()
649
            else:
650
                self.sflowButton.deselect()
651
            rowCount+=1
652

    
653
            # Selection of switch type
654
            Label(self.leftfieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E)
655
            self.switchType = StringVar(self.leftfieldFrame)
656
            self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Default", "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace")
657
            self.switchTypeMenu.grid(row=rowCount, column=1, sticky=W)
658
            if 'switchType' in self.prefValues:
659
                switchTypePref = self.prefValues['switchType']
660
                if switchTypePref == 'ivs':
661
                    self.switchType.set("Indigo Virtual Switch")
662
                elif switchTypePref == 'userns':
663
                    self.switchType.set("Userspace Switch inNamespace")
664
                elif switchTypePref == 'user':
665
                    self.switchType.set("Userspace Switch")
666
                elif switchTypePref == 'ovs':
667
                    self.switchType.set("Open vSwitch Kernel Mode")
668
                else:
669
                    self.switchType.set("Default")
670
            else:
671
                self.switchType.set("Default")
672
            rowCount+=1
673

    
674
            # Field for Switch IP
675
            Label(self.leftfieldFrame, text="IP Address:").grid(row=rowCount, sticky=E)
676
            self.ipEntry = Entry(self.leftfieldFrame)
677
            self.ipEntry.grid(row=rowCount, column=1)
678
            if 'switchIP' in self.prefValues:
679
                self.ipEntry.insert(0, self.prefValues['switchIP'])
680
            rowCount+=1
681

    
682
            # Field for DPCTL port
683
            Label(self.leftfieldFrame, text="DPCTL port:").grid(row=rowCount, sticky=E)
684
            self.dpctlEntry = Entry(self.leftfieldFrame)
685
            self.dpctlEntry.grid(row=rowCount, column=1)
686
            if 'dpctl' in self.prefValues:
687
                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
688
            rowCount+=1
689

    
690
            # External Interfaces
691
            Label(self.rightfieldFrame, text="External Interface:").grid(row=0, sticky=E)
692
            self.b = Button( self.rightfieldFrame, text='Add', command=self.addInterface)
693
            self.b.grid(row=0, column=1)
694

    
695
            self.interfaceFrame = VerticalScrolledTable(self.rightfieldFrame, rows=0, columns=1, title='External Interfaces')
696
            self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
697
            self.tableFrame = self.interfaceFrame.interior
698

    
699
            # Add defined interfaces
700
            for externalInterface in externalInterfaces:
701
                self.tableFrame.addRow(value=[externalInterface])
702

    
703
            self.commandFrame = Frame(self.rootFrame)
704
            self.commandFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
705
            self.commandFrame.columnconfigure(1, weight=1)
706
            # Start command
707
            Label(self.commandFrame, text="Start Command:").grid(row=0, column=0, sticky=W)
708
            self.startEntry = Entry(self.commandFrame)
709
            self.startEntry.grid(row=0, column=1,  sticky='nsew')
710
            if 'startCommand' in self.prefValues:
711
                self.startEntry.insert(0, str(self.prefValues['startCommand']))
712
            # Stop command
713
            Label(self.commandFrame, text="Stop Command:").grid(row=1, column=0, sticky=W)
714
            self.stopEntry = Entry(self.commandFrame)
715
            self.stopEntry.grid(row=1, column=1, sticky='nsew')
716
            if 'stopCommand' in self.prefValues:
717
                self.stopEntry.insert(0, str(self.prefValues['stopCommand']))
718

    
719
        def addInterface( self ):
720
            self.tableFrame.addRow()
721

    
722
        def defaultDpid( self ,name):
723
            "Derive dpid from switch name, s1 -> 1"
724
            try:
725
                dpid = int( re.findall( r'\d+', name )[ 0 ] )
726
                dpid = hex( dpid )[ 2: ]
727
                return dpid
728
            except IndexError:
729
                return None
730
                #raise Exception( 'Unable to derive default datapath ID - '
731
                #                 'please either specify a dpid or use a '
732
                #                 'canonical switch name such as s23.' )
733

    
734
        def apply(self):
735
            externalInterfaces = []
736
            for row in range(self.tableFrame.rows):
737
                #print 'Interface is ' + self.tableFrame.get(row, 0)
738
                if (len(self.tableFrame.get(row, 0)) > 0):
739
                    externalInterfaces.append(self.tableFrame.get(row, 0))
740

    
741
            dpid = self.dpidEntry.get()
742
            if (self.defaultDpid(self.hostnameEntry.get()) is None
743
               and len(dpid) == 0):
744
                showerror(title="Error",
745
                              message= 'Unable to derive default datapath ID - '
746
                                 'please either specify a DPID or use a '
747
                                 'canonical switch name such as s23.' )
748

    
749
            
750
            results = {'externalInterfaces':externalInterfaces,
751
                       'hostname':self.hostnameEntry.get(),
752
                       'dpid':dpid,
753
                       'startCommand':self.startEntry.get(),
754
                       'stopCommand':self.stopEntry.get(),
755
                       'sflow':str(self.sflow.get()),
756
                       'netflow':str(self.nflow.get()),
757
                       'dpctl':self.dpctlEntry.get(),
758
                       'switchIP':self.ipEntry.get()}
759
            sw = self.switchType.get()
760
            if sw == 'Indigo Virtual Switch':
761
                results['switchType'] = 'ivs'
762
                if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
763
                    self.ovsOk = False
764
                    showerror(title="Error",
765
                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
766
            elif sw == 'Userspace Switch inNamespace':
767
                results['switchType'] = 'userns'
768
            elif sw == 'Userspace Switch':
769
                results['switchType'] = 'user'
770
            elif sw == 'Open vSwitch Kernel Mode':
771
                results['switchType'] = 'ovs'
772
            else:
773
                results['switchType'] = 'default'
774
            self.result = results
775

    
776

    
777
class VerticalScrolledTable(LabelFrame):
778
    """A pure Tkinter scrollable frame that actually works!
779

780
    * Use the 'interior' attribute to place widgets inside the scrollable frame
781
    * Construct and pack/place/grid normally
782
    * This frame only allows vertical scrolling
783
    
784
    """
785
    def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw):
786
        LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw)            
787

    
788
        # create a canvas object and a vertical scrollbar for scrolling it
789
        vscrollbar = Scrollbar(self, orient=VERTICAL)
790
        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
791
        canvas = Canvas(self, bd=0, highlightthickness=0,
792
                        yscrollcommand=vscrollbar.set)
793
        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
794
        vscrollbar.config(command=canvas.yview)
795

    
796
        # reset the view
797
        canvas.xview_moveto(0)
798
        canvas.yview_moveto(0)
799

    
800
        # create a frame inside the canvas which will be scrolled with it
801
        self.interior = interior = TableFrame(canvas, rows=rows, columns=columns)
802
        interior_id = canvas.create_window(0, 0, window=interior,
803
                                           anchor=NW)
804

    
805
        # track changes to the canvas and frame width and sync them,
806
        # also updating the scrollbar
807
        def _configure_interior(event):
808
            # update the scrollbars to match the size of the inner frame
809
            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
810
            canvas.config(scrollregion="0 0 %s %s" % size)
811
            if interior.winfo_reqwidth() != canvas.winfo_width():
812
                # update the canvas's width to fit the inner frame
813
                canvas.config(width=interior.winfo_reqwidth())
814
        interior.bind('<Configure>', _configure_interior)
815

    
816
        def _configure_canvas(event):
817
            if interior.winfo_reqwidth() != canvas.winfo_width():
818
                # update the inner frame's width to fill the canvas
819
                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
820
        canvas.bind('<Configure>', _configure_canvas)
821

    
822
        return
823

    
824
class TableFrame(Frame):
825
    def __init__(self, parent, rows=2, columns=2):
826

    
827
        Frame.__init__(self, parent, background="black")
828
        self._widgets = []
829
        self.rows = rows
830
        self.columns = columns
831
        for row in range(rows):
832
            current_row = []
833
            for column in range(columns):
834
                label = Entry(self, borderwidth=0)
835
                label.grid(row=row, column=column, sticky="wens", padx=1, pady=1)
836
                current_row.append(label)
837
            self._widgets.append(current_row)
838

    
839
    def set(self, row, column, value):
840
        widget = self._widgets[row][column]
841
        widget.insert(0, value)
842

    
843
    def get(self, row, column):
844
        widget = self._widgets[row][column]
845
        return widget.get()
846

    
847
    def addRow( self, value=None, readonly=False ):
848
        #print "Adding row " + str(self.rows +1)
849
        current_row = []
850
        for column in range(self.columns):
851
            label = Entry(self, borderwidth=0)
852
            label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1)
853
            if value is not None:
854
                label.insert(0, value[column])
855
            if (readonly == True):
856
                label.configure(state='readonly')
857
            current_row.append(label)
858
        self._widgets.append(current_row)
859
        self.update_idletasks()
860
        self.rows += 1
861

    
862
class LinkDialog(tkSimpleDialog.Dialog):
863

    
864
        def __init__(self, parent, title, linkDefaults):
865

    
866
            self.linkValues = linkDefaults
867

    
868
            tkSimpleDialog.Dialog.__init__(self, parent, title)
869

    
870
        def body(self, master):
871

    
872
            self.var = StringVar(master)
873
            Label(master, text="Bandwidth:").grid(row=0, sticky=E)
874
            self.e1 = Entry(master)
875
            self.e1.grid(row=0, column=1)
876
            Label(master, text="Mbit").grid(row=0, column=2, sticky=W)
877
            if 'bw' in self.linkValues:
878
                self.e1.insert(0,str(self.linkValues['bw']))
879

    
880
            Label(master, text="Delay:").grid(row=1, sticky=E)
881
            self.e2 = Entry(master)
882
            self.e2.grid(row=1, column=1)
883
            if 'delay' in self.linkValues:
884
                self.e2.insert(0, self.linkValues['delay'])
885

    
886
            Label(master, text="Loss:").grid(row=2, sticky=E)
887
            self.e3 = Entry(master)
888
            self.e3.grid(row=2, column=1)
889
            Label(master, text="%").grid(row=2, column=2, sticky=W)
890
            if 'loss' in self.linkValues:
891
                self.e3.insert(0, str(self.linkValues['loss']))
892

    
893
            Label(master, text="Max Queue size:").grid(row=3, sticky=E)
894
            self.e4 = Entry(master)
895
            self.e4.grid(row=3, column=1)
896
            if 'max_queue_size' in self.linkValues:
897
                self.e4.insert(0, str(self.linkValues['max_queue_size']))
898

    
899
            Label(master, text="Jitter:").grid(row=4, sticky=E)
900
            self.e5 = Entry(master)
901
            self.e5.grid(row=4, column=1)
902
            if 'jitter' in self.linkValues:
903
                self.e5.insert(0, self.linkValues['jitter'])
904

    
905
            Label(master, text="Speedup:").grid(row=5, sticky=E)
906
            self.e6 = Entry(master)
907
            self.e6.grid(row=5, column=1)
908
            if 'speedup' in self.linkValues:
909
                self.e6.insert(0, str(self.linkValues['speedup']))
910

    
911
            return self.e1 # initial focus
912

    
913
        def apply(self):
914
            self.result = {}
915
            if (len(self.e1.get()) > 0):
916
                self.result['bw'] = int(self.e1.get())
917
            if (len(self.e2.get()) > 0):
918
                self.result['delay'] = self.e2.get()
919
            if (len(self.e3.get()) > 0):
920
                self.result['loss'] = int(self.e3.get())
921
            if (len(self.e4.get()) > 0):
922
                self.result['max_queue_size'] = int(self.e4.get())
923
            if (len(self.e5.get()) > 0):
924
                self.result['jitter'] = self.e5.get()
925
            if (len(self.e6.get()) > 0):
926
                self.result['speedup'] = int(self.e6.get())
927

    
928
class ControllerDialog(tkSimpleDialog.Dialog):
929

    
930
        def __init__(self, parent, title, ctrlrDefaults=None):
931

    
932
            if ctrlrDefaults:
933
                self.ctrlrValues = ctrlrDefaults
934

    
935
            tkSimpleDialog.Dialog.__init__(self, parent, title)
936

    
937
        def body(self, master):
938

    
939
            self.var = StringVar(master)
940
            self.protcolvar = StringVar(master)
941

    
942
            rowCount=0
943
            # Field for Hostname
944
            Label(master, text="Name:").grid(row=rowCount, sticky=E)
945
            self.hostnameEntry = Entry(master)
946
            self.hostnameEntry.grid(row=rowCount, column=1)
947
            self.hostnameEntry.insert(0, self.ctrlrValues['hostname'])
948
            rowCount+=1
949

    
950
            # Field for Remove Controller Port
951
            Label(master, text="Controller Port:").grid(row=rowCount, sticky=E)
952
            self.e2 = Entry(master)
953
            self.e2.grid(row=rowCount, column=1)
954
            self.e2.insert(0, self.ctrlrValues['remotePort'])
955
            rowCount+=1
956

    
957
            # Field for Controller Type
958
            Label(master, text="Controller Type:").grid(row=rowCount, sticky=E)
959
            controllerType = self.ctrlrValues['controllerType']
960
            self.o1 = OptionMenu(master, self.var, "Remote Controller", "In-Band Controller", "OpenFlow Reference", "OVS Controller")
961
            self.o1.grid(row=rowCount, column=1, sticky=W)
962
            if controllerType == 'ref':
963
                self.var.set("OpenFlow Reference")
964
            elif controllerType == 'inband':
965
                self.var.set("In-Band Controller")
966
            elif controllerType == 'remote':
967
                self.var.set("Remote Controller")
968
            else:
969
                self.var.set("OVS Controller")
970
            rowCount+=1
971

    
972
            # Field for Controller Protcol
973
            Label(master, text="Protocol:").grid(row=rowCount, sticky=E)
974
            if 'controllerProtocol' in self.ctrlrValues:
975
               controllerProtocol = self.ctrlrValues['controllerProtocol']
976
            else:
977
               controllerProtocol = 'tcp'
978
            self.protcol = OptionMenu(master, self.protcolvar, "TCP", "SSL")
979
            self.protcol.grid(row=rowCount, column=1, sticky=W)
980
            if controllerProtocol == 'ssl':
981
                self.protcolvar.set("SSL")
982
            else:
983
                self.protcolvar.set("TCP")
984
            rowCount+=1
985

    
986
            # Field for Remove Controller IP
987
            remoteFrame= LabelFrame(master, text='Remote/In-Band Controller', padx=5, pady=5)
988
            remoteFrame.grid(row=rowCount, column=0, columnspan=2, sticky=W)
989

    
990
            Label(remoteFrame, text="IP Address:").grid(row=0, sticky=E)
991
            self.e1 = Entry(remoteFrame)
992
            self.e1.grid(row=0, column=1)
993
            self.e1.insert(0, self.ctrlrValues['remoteIP'])
994
            rowCount+=1
995

    
996
            return self.hostnameEntry # initial focus
997

    
998
        def apply(self):
999
            self.result = { 'hostname': self.hostnameEntry.get(),
1000
                            'remoteIP': self.e1.get(),
1001
                            'remotePort': int(self.e2.get())}
1002

    
1003
            controllerType = self.var.get()
1004
            if controllerType == 'Remote Controller':
1005
                self.result['controllerType'] = 'remote'
1006
            elif controllerType == 'In-Band Controller':
1007
                self.result['controllerType'] = 'inband'
1008
            elif controllerType == 'OpenFlow Reference':
1009
                self.result['controllerType'] = 'ref'
1010
            else:
1011
                self.result['controllerType'] = 'ovsc'
1012
            controllerProtocol = self.protcolvar.get()
1013
            if controllerProtocol == 'SSL':
1014
                self.result['controllerProtocol'] = 'ssl'
1015
            else:
1016
                self.result['controllerProtocol'] = 'tcp'
1017

    
1018
class ToolTip(object):
1019

    
1020
    def __init__(self, widget):
1021
        self.widget = widget
1022
        self.tipwindow = None
1023
        self.id = None
1024
        self.x = self.y = 0
1025

    
1026
    def showtip(self, text):
1027
        "Display text in tooltip window"
1028
        self.text = text
1029
        if self.tipwindow or not self.text:
1030
            return
1031
        x, y, cx, cy = self.widget.bbox("insert")
1032
        x = x + self.widget.winfo_rootx() + 27
1033
        y = y + cy + self.widget.winfo_rooty() +27
1034
        self.tipwindow = tw = Toplevel(self.widget)
1035
        tw.wm_overrideredirect(1)
1036
        tw.wm_geometry("+%d+%d" % (x, y))
1037
        try:
1038
            # For Mac OS
1039
            tw.tk.call("::tk::unsupported::MacWindowStyle",
1040
                       "style", tw._w,
1041
                       "help", "noActivates")
1042
        except TclError:
1043
            pass
1044
        label = Label(tw, text=self.text, justify=LEFT,
1045
                      background="#ffffe0", relief=SOLID, borderwidth=1,
1046
                      font=("tahoma", "8", "normal"))
1047
        label.pack(ipadx=1)
1048

    
1049
    def hidetip(self):
1050
        tw = self.tipwindow
1051
        self.tipwindow = None
1052
        if tw:
1053
            tw.destroy()
1054

    
1055
class MiniEdit( Frame ):
1056

    
1057
    "A simple network editor for Mininet."
1058

    
1059
    def __init__( self, parent=None, cheight=600, cwidth=1000 ):
1060

    
1061
        self.defaultIpBase='10.0.0.0/8'
1062

    
1063
        self.nflowDefaults = {'nflowTarget':'',
1064
                              'nflowTimeout':'600',
1065
                              'nflowAddId':'0'}
1066
        self.sflowDefaults = {'sflowTarget':'',
1067
                              'sflowSampling':'400',
1068
                              'sflowHeader':'128',
1069
                              'sflowPolling':'30'}
1070

    
1071
        self.appPrefs={
1072
            "ipBase": self.defaultIpBase,
1073
            "startCLI": "0",
1074
            "terminalType": 'xterm',
1075
            "switchType": 'ovs',
1076
            "dpctl": '',
1077
            'sflow':self.sflowDefaults,
1078
            'netflow':self.nflowDefaults,
1079
            'openFlowVersions':{'ovsOf10':'1',
1080
                                'ovsOf11':'0',
1081
                                'ovsOf12':'0',
1082
                                'ovsOf13':'0'}
1083

    
1084
        }
1085

    
1086

    
1087
        Frame.__init__( self, parent )
1088
        self.action = None
1089
        self.appName = 'MiniEdit'
1090
        self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" )
1091

    
1092
        # Style
1093
        self.font = ( 'Geneva', 9 )
1094
        self.smallFont = ( 'Geneva', 7 )
1095
        self.bg = 'white'
1096

    
1097
        # Title
1098
        self.top = self.winfo_toplevel()
1099
        self.top.title( self.appName )
1100

    
1101
        # Menu bar
1102
        self.createMenubar()
1103

    
1104
        # Editing canvas
1105
        self.cheight, self.cwidth = cheight, cwidth
1106
        self.cframe, self.canvas = self.createCanvas()
1107

    
1108
        # Toolbar
1109
        self.controllers = {}
1110

    
1111
        # Toolbar
1112
        self.images = miniEditImages()
1113
        self.buttons = {}
1114
        self.active = None
1115
        self.tools = ( 'Select', 'Host', 'Switch', 'LegacySwitch', 'LegacyRouter', 'NetLink', 'Controller' )
1116
        self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
1117
        self.toolbar = self.createToolbar()
1118

    
1119
        # Layout
1120
        self.toolbar.grid( column=0, row=0, sticky='nsew')
1121
        self.cframe.grid( column=1, row=0 )
1122
        self.columnconfigure( 1, weight=1 )
1123
        self.rowconfigure( 0, weight=1 )
1124
        self.pack( expand=True, fill='both' )
1125

    
1126
        # About box
1127
        self.aboutBox = None
1128

    
1129
        # Initialize node data
1130
        self.nodeBindings = self.createNodeBindings()
1131
        self.nodePrefixes = { 'LegacyRouter': 'r', 'LegacySwitch': 's', 'Switch': 's', 'Host': 'h' , 'Controller': 'c'}
1132
        self.widgetToItem = {}
1133
        self.itemToWidget = {}
1134

    
1135
        # Initialize link tool
1136
        self.link = self.linkWidget = None
1137

    
1138
        # Selection support
1139
        self.selection = None
1140

    
1141
        # Keyboard bindings
1142
        self.bind( '<Control-q>', lambda event: self.quit() )
1143
        self.bind( '<KeyPress-Delete>', self.deleteSelection )
1144
        self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
1145
        self.focus()
1146

    
1147
        self.hostPopup = Menu(self.top, tearoff=0)
1148
        self.hostPopup.add_command(label='Host Options', font=self.font)
1149
        self.hostPopup.add_separator()
1150
        self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails )
1151

    
1152
        self.hostRunPopup = Menu(self.top, tearoff=0)
1153
        self.hostRunPopup.add_command(label='Host Options', font=self.font)
1154
        self.hostRunPopup.add_separator()
1155
        self.hostRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
1156

    
1157
        self.legacyRouterRunPopup = Menu(self.top, tearoff=0)
1158
        self.legacyRouterRunPopup.add_command(label='Router Options', font=self.font)
1159
        self.legacyRouterRunPopup.add_separator()
1160
        self.legacyRouterRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
1161

    
1162
        self.switchPopup = Menu(self.top, tearoff=0)
1163
        self.switchPopup.add_command(label='Switch Options', font=self.font)
1164
        self.switchPopup.add_separator()
1165
        self.switchPopup.add_command(label='Properties', font=self.font, command=self.switchDetails )
1166

    
1167
        self.switchRunPopup = Menu(self.top, tearoff=0)
1168
        self.switchRunPopup.add_command(label='Switch Options', font=self.font)
1169
        self.switchRunPopup.add_separator()
1170
        self.switchRunPopup.add_command(label='List bridge details', font=self.font, command=self.listBridge )
1171

    
1172
        self.linkPopup = Menu(self.top, tearoff=0)
1173
        self.linkPopup.add_command(label='Link Options', font=self.font)
1174
        self.linkPopup.add_separator()
1175
        self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails )
1176

    
1177
        self.linkRunPopup = Menu(self.top, tearoff=0)
1178
        self.linkRunPopup.add_command(label='Link Options', font=self.font)
1179
        self.linkRunPopup.add_separator()
1180
        self.linkRunPopup.add_command(label='Link Up', font=self.font, command=self.linkUp )
1181
        self.linkRunPopup.add_command(label='Link Down', font=self.font, command=self.linkDown )
1182

    
1183
        self.controllerPopup = Menu(self.top, tearoff=0)
1184
        self.controllerPopup.add_command(label='Controller Options', font=self.font)
1185
        self.controllerPopup.add_separator()
1186
        self.controllerPopup.add_command(label='Properties', font=self.font, command=self.controllerDetails )
1187

    
1188

    
1189
        # Event handling initalization
1190
        self.linkx = self.linky = self.linkItem = None
1191
        self.lastSelection = None
1192

    
1193
        # Model initialization
1194
        self.links = {}
1195
        self.hostOpts = {}
1196
        self.switchOpts = {}
1197
        self.hostCount = 0
1198
        self.switchCount = 0
1199
        self.controllerCount = 0
1200
        self.net = None
1201

    
1202
        # Close window gracefully
1203
        Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
1204

    
1205
    def quit( self ):
1206
        "Stop our network, if any, then quit."
1207
        self.stop()
1208
        Frame.quit( self )
1209

    
1210
    def createMenubar( self ):
1211
        "Create our menu bar."
1212

    
1213
        font = self.font
1214

    
1215
        mbar = Menu( self.top, font=font )
1216
        self.top.configure( menu=mbar )
1217

    
1218

    
1219
        fileMenu = Menu( mbar, tearoff=False )
1220
        mbar.add_cascade( label="File", font=font, menu=fileMenu )
1221
        fileMenu.add_command( label="New", font=font, command=self.newTopology )
1222
        fileMenu.add_command( label="Open", font=font, command=self.loadTopology )
1223
        fileMenu.add_command( label="Save", font=font, command=self.saveTopology )
1224
        fileMenu.add_command( label="Export Level 2 Script", font=font, command=self.exportScript )
1225
        fileMenu.add_separator()
1226
        fileMenu.add_command( label='Quit', command=self.quit, font=font )
1227

    
1228
        editMenu = Menu( mbar, tearoff=False )
1229
        mbar.add_cascade( label="Edit", font=font, menu=editMenu )
1230
        editMenu.add_command( label="Cut", font=font,
1231
                              command=lambda: self.deleteSelection( None ) )
1232
        editMenu.add_command( label="Preferences", font=font, command=self.prefDetails)
1233

    
1234
        runMenu = Menu( mbar, tearoff=False )
1235
        mbar.add_cascade( label="Run", font=font, menu=runMenu )
1236
        runMenu.add_command( label="Run", font=font, command=self.doRun )
1237
        runMenu.add_command( label="Stop", font=font, command=self.doStop )
1238
        fileMenu.add_separator()
1239
        runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow )
1240
        runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal )
1241

    
1242
        # Application menu
1243
        appMenu = Menu( mbar, tearoff=False )
1244
        mbar.add_cascade( label="Help", font=font, menu=appMenu )
1245
        appMenu.add_command( label='About MiniEdit', command=self.about,
1246
                             font=font)
1247
    # Canvas
1248

    
1249
    def createCanvas( self ):
1250
        "Create and return our scrolling canvas frame."
1251
        f = Frame( self )
1252

    
1253
        canvas = Canvas( f, width=self.cwidth, height=self.cheight,
1254
                         bg=self.bg )
1255

    
1256
        # Scroll bars
1257
        xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
1258
        ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
1259
        canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
1260

    
1261
        # Resize box
1262
        resize = Label( f, bg='white' )
1263

    
1264
        # Layout
1265
        canvas.grid( row=0, column=1, sticky='nsew')
1266
        ybar.grid( row=0, column=2, sticky='ns')
1267
        xbar.grid( row=1, column=1, sticky='ew' )
1268
        resize.grid( row=1, column=2, sticky='nsew' )
1269

    
1270
        # Resize behavior
1271
        f.rowconfigure( 0, weight=1 )
1272
        f.columnconfigure( 1, weight=1 )
1273
        f.grid( row=0, column=0, sticky='nsew' )
1274
        f.bind( '<Configure>', lambda event: self.updateScrollRegion() )
1275

    
1276
        # Mouse bindings
1277
        canvas.bind( '<ButtonPress-1>', self.clickCanvas )
1278
        canvas.bind( '<B1-Motion>', self.dragCanvas )
1279
        canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
1280

    
1281
        return f, canvas
1282

    
1283
    def updateScrollRegion( self ):
1284
        "Update canvas scroll region to hold everything."
1285
        bbox = self.canvas.bbox( 'all' )
1286
        if bbox is not None:
1287
            self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ],
1288
                                   bbox[ 3 ] ) )
1289

    
1290
    def canvasx( self, x_root ):
1291
        "Convert root x coordinate to canvas coordinate."
1292
        c = self.canvas
1293
        return c.canvasx( x_root ) - c.winfo_rootx()
1294

    
1295
    def canvasy( self, y_root ):
1296
        "Convert root y coordinate to canvas coordinate."
1297
        c = self.canvas
1298
        return c.canvasy( y_root ) - c.winfo_rooty()
1299

    
1300
    # Toolbar
1301

    
1302
    def activate( self, toolName ):
1303
        "Activate a tool and press its button."
1304
        # Adjust button appearance
1305
        if self.active:
1306
            self.buttons[ self.active ].configure( relief='raised' )
1307
        self.buttons[ toolName ].configure( relief='sunken' )
1308
        # Activate dynamic bindings
1309
        self.active = toolName
1310

    
1311

    
1312
    def createToolTip(self, widget, text):
1313
        toolTip = ToolTip(widget)
1314
        def enter(event):
1315
            toolTip.showtip(text)
1316
        def leave(event):
1317
            toolTip.hidetip()
1318
        widget.bind('<Enter>', enter)
1319
        widget.bind('<Leave>', leave)
1320

    
1321
    def createToolbar( self ):
1322
        "Create and return our toolbar frame."
1323

    
1324
        toolbar = Frame( self )
1325

    
1326
        # Tools
1327
        for tool in self.tools:
1328
            cmd = ( lambda t=tool: self.activate( t ) )
1329
            b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
1330
            if tool in self.images:
1331
                b.config( height=35, image=self.images[ tool ] )
1332
                self.createToolTip(b, str(tool))
1333
                # b.config( compound='top' )
1334
            b.pack( fill='x' )
1335
            self.buttons[ tool ] = b
1336
        self.activate( self.tools[ 0 ] )
1337

    
1338
        # Spacer
1339
        Label( toolbar, text='' ).pack()
1340

    
1341
        # Commands
1342
        for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]:
1343
            doCmd = getattr( self, 'do' + cmd )
1344
            b = Button( toolbar, text=cmd, font=self.smallFont,
1345
                        fg=color, command=doCmd )
1346
            b.pack( fill='x', side='bottom' )
1347

    
1348
        return toolbar
1349

    
1350
    def doRun( self ):
1351
        "Run command."
1352
        self.activate( 'Select' )
1353
        for tool in self.tools:
1354
            self.buttons[ tool ].config( state='disabled' )
1355
        self.start()
1356

    
1357
    def doStop( self ):
1358
        "Stop command."
1359
        self.stop()
1360
        for tool in self.tools:
1361
            self.buttons[ tool ].config( state='normal' )
1362

    
1363
    def addNode( self, node, nodeNum, x, y, name=None):
1364
        "Add a new node to our canvas."
1365
        if 'Switch' == node:
1366
            self.switchCount += 1
1367
        if 'Host' == node:
1368
            self.hostCount += 1
1369
        if 'Controller' == node:
1370
            self.controllerCount += 1
1371
        if name is None:
1372
            name = self.nodePrefixes[ node ] + nodeNum
1373
        self.addNamedNode(node, name, x, y)
1374

    
1375
    def addNamedNode( self, node, name, x, y):
1376
        "Add a new node to our canvas."
1377
        c = self.canvas
1378
        icon = self.nodeIcon( node, name )
1379
        item = self.canvas.create_window( x, y, anchor='c', window=icon,
1380
                                          tags=node )
1381
        self.widgetToItem[ icon ] = item
1382
        self.itemToWidget[ item ] = icon
1383
        icon.links = {}
1384

    
1385
    def convertJsonUnicode(self, input):
1386
        "Some part of Mininet don't like Unicode"
1387
        if isinstance(input, dict):
1388
            return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in input.iteritems()}
1389
        elif isinstance(input, list):
1390
            return [self.convertJsonUnicode(element) for element in input]
1391
        elif isinstance(input, unicode):
1392
            return input.encode('utf-8')
1393
        else:
1394
            return input
1395

    
1396
    def loadTopology( self ):
1397
        "Load command."
1398
        c = self.canvas
1399

    
1400
        myFormats = [
1401
            ('Mininet Topology','*.mn'),
1402
            ('All Files','*'),
1403
        ]
1404
        f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
1405
        if f == None:
1406
            return
1407
        self.newTopology()
1408
        loadedTopology = self.convertJsonUnicode(json.load(f))
1409

    
1410
        # Load application preferences
1411
        if 'application' in loadedTopology:
1412
            self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items())
1413
            if "ovsOf10" not in self.appPrefs["openFlowVersions"]:
1414
                self.appPrefs["openFlowVersions"]["ovsOf10"] = '0'
1415
            if "ovsOf11" not in self.appPrefs["openFlowVersions"]:
1416
                self.appPrefs["openFlowVersions"]["ovsOf11"] = '0'
1417
            if "ovsOf12" not in self.appPrefs["openFlowVersions"]:
1418
                self.appPrefs["openFlowVersions"]["ovsOf12"] = '0'
1419
            if "ovsOf13" not in self.appPrefs["openFlowVersions"]:
1420
                self.appPrefs["openFlowVersions"]["ovsOf13"] = '0'
1421
            if "sflow" not in self.appPrefs:
1422
                self.appPrefs["sflow"] = self.sflowDefaults
1423
            if "netflow" not in self.appPrefs:
1424
                self.appPrefs["netflow"] = self.nflowDefaults
1425

    
1426
        # Load controllers
1427
        if ('controllers' in loadedTopology):
1428
            if (loadedTopology['version'] == '1'):
1429
                # This is old location of controller info
1430
                hostname = 'c0'
1431
                self.controllers = {}
1432
                self.controllers[hostname] = loadedTopology['controllers']['c0']
1433
                self.controllers[hostname]['hostname'] = hostname
1434
                self.addNode('Controller', 0, float(30), float(30), name=hostname)
1435
                icon = self.findWidgetByName(hostname)
1436
                icon.bind('<Button-3>', self.do_controllerPopup )
1437
            else:
1438
                controllers = loadedTopology['controllers']
1439
                for controller in controllers:
1440
                    hostname = controller['opts']['hostname']
1441
                    x = controller['x']
1442
                    y = controller['y']
1443
                    self.addNode('Controller', 0, float(x), float(y), name=hostname)
1444
                    self.controllers[hostname] = controller['opts']
1445
                    icon = self.findWidgetByName(hostname)
1446
                    icon.bind('<Button-3>', self.do_controllerPopup )
1447

    
1448

    
1449
        # Load hosts
1450
        hosts = loadedTopology['hosts']
1451
        for host in hosts:
1452
            nodeNum = host['number']
1453
            hostname = 'h'+nodeNum
1454
            if 'hostname' in host['opts']:
1455
                hostname = host['opts']['hostname']
1456
            else:
1457
                host['opts']['hostname'] = hostname
1458
            if 'nodeNum' not in host['opts']:
1459
                host['opts']['nodeNum'] = int(nodeNum)
1460
            x = host['x']
1461
            y = host['y']
1462
            self.addNode('Host', nodeNum, float(x), float(y), name=hostname)
1463

    
1464
            # Fix JSON converting tuple to list when saving
1465
            if 'privateDirectory' in host['opts']:
1466
                newDirList = []
1467
                for privateDir in host['opts']['privateDirectory']:
1468
                    if isinstance( privateDir, list ):
1469
                        newDirList.append((privateDir[0],privateDir[1]))
1470
                    else:
1471
                        newDirList.append(privateDir)
1472
                host['opts']['privateDirectory'] = newDirList
1473
            self.hostOpts[hostname] = host['opts']
1474
            icon = self.findWidgetByName(hostname)
1475
            icon.bind('<Button-3>', self.do_hostPopup )
1476

    
1477
        # Load switches
1478
        switches = loadedTopology['switches']
1479
        for switch in switches:
1480
            nodeNum = switch['number']
1481
            hostname = 's'+nodeNum
1482
            if 'controllers' not in switch['opts']:
1483
                switch['opts']['controllers'] = []
1484
            if 'switchType' not in switch['opts']:
1485
                switch['opts']['switchType'] = 'default'
1486
            if 'hostname' in switch['opts']:
1487
                hostname = switch['opts']['hostname']
1488
            else:
1489
                switch['opts']['hostname'] = hostname
1490
            if 'nodeNum' not in switch['opts']:
1491
                switch['opts']['nodeNum'] = int(nodeNum)
1492
            x = switch['x']
1493
            y = switch['y']
1494
            if switch['opts']['switchType'] == "legacyRouter":
1495
                self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname)
1496
                icon = self.findWidgetByName(hostname)
1497
                icon.bind('<Button-3>', self.do_legacyRouterPopup )
1498
            elif switch['opts']['switchType'] == "legacySwitch":
1499
                self.addNode('LegacySwitch', nodeNum, float(x), float(y), name=hostname)
1500
                icon = self.findWidgetByName(hostname)
1501
                icon.bind('<Button-3>', self.do_legacySwitchPopup )
1502
            else:
1503
                self.addNode('Switch', nodeNum, float(x), float(y), name=hostname)
1504
                icon = self.findWidgetByName(hostname)
1505
                icon.bind('<Button-3>', self.do_switchPopup )
1506
            self.switchOpts[hostname] = switch['opts']
1507

    
1508
            # create links to controllers
1509
            if (int(loadedTopology['version']) > 1):
1510
                controllers = self.switchOpts[hostname]['controllers']
1511
                for controller in controllers:
1512
                    dest = self.findWidgetByName(controller)
1513
                    dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1514
                    self.link = self.canvas.create_line(float(x),
1515
                                                        float(y),
1516
                                                        dx,
1517
                                                        dy,
1518
                                                        width=4,
1519
                                                        fill='red',
1520
                                                        dash=(6, 4, 2, 4),
1521
                                                        tag='link' )
1522
                    c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
1523
                    self.addLink( icon, dest, linktype='control' )
1524
                    self.createControlLinkBindings()
1525
                    self.link = self.linkWidget = None
1526
            else:
1527
                dest = self.findWidgetByName('c0')
1528
                dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1529
                self.link = self.canvas.create_line(float(x),
1530
                                                    float(y),
1531
                                                    dx,
1532
                                                    dy,
1533
                                                    width=4,
1534
                                                    fill='red',
1535
                                                    dash=(6, 4, 2, 4),
1536
                                                    tag='link' )
1537
                c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
1538
                self.addLink( icon, dest, linktype='control' )
1539
                self.createControlLinkBindings()
1540
                self.link = self.linkWidget = None
1541

    
1542
        # Load links
1543
        links = loadedTopology['links']
1544
        for link in links:
1545
            srcNode = link['src']
1546
            src = self.findWidgetByName(srcNode)
1547
            sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
1548

    
1549
            destNode = link['dest']
1550
            dest = self.findWidgetByName(destNode)
1551
            dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
1552

    
1553
            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
1554
                                             fill='blue', tag='link' )
1555
            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
1556
            self.addLink( src, dest, linkopts=link['opts'] )
1557
            self.createDataLinkBindings()
1558
            self.link = self.linkWidget = None
1559

    
1560
        f.close
1561

    
1562
    def findWidgetByName( self, name ):
1563
        for widget in self.widgetToItem:
1564
            if name ==  widget[ 'text' ]:
1565
                return widget
1566

    
1567
    def newTopology( self ):
1568
        "New command."
1569
        for widget in self.widgetToItem.keys():
1570
            self.deleteItem( self.widgetToItem[ widget ] )
1571
        self.hostCount = 0
1572
        self.switchCount = 0
1573
        self.controllerCount = 0
1574
        self.links = {}
1575
        self.hostOpts = {}
1576
        self.switchOpts = {}
1577
        self.controllers = {}
1578
        self.appPrefs["ipBase"]= self.defaultIpBase
1579

    
1580
    def saveTopology( self ):
1581
        "Save command."
1582
        myFormats = [
1583
            ('Mininet Topology','*.mn'),
1584
            ('All Files','*'),
1585
        ]
1586

    
1587
        savingDictionary = {}
1588
        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...")
1589
        if len(fileName ) > 0:
1590
            # Save Application preferences
1591
            savingDictionary['version'] = '2'
1592

    
1593
            # Save Switches and Hosts
1594
            hostsToSave = []
1595
            switchesToSave = []
1596
            controllersToSave = []
1597
            for widget in self.widgetToItem:
1598
                name = widget[ 'text' ]
1599
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1600
                x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] )
1601
                if 'Switch' in tags or 'LegacySwitch' in tags or 'LegacyRouter' in tags:
1602
                    nodeNum = self.switchOpts[name]['nodeNum']
1603
                    nodeToSave = {'number':str(nodeNum),
1604
                                  'x':str(x1),
1605
                                  'y':str(y1),
1606
                                  'opts':self.switchOpts[name] }
1607
                    switchesToSave.append(nodeToSave)
1608
                elif 'Host' in tags:
1609
                    nodeNum = self.hostOpts[name]['nodeNum']
1610
                    nodeToSave = {'number':str(nodeNum),
1611
                                  'x':str(x1),
1612
                                  'y':str(y1),
1613
                                  'opts':self.hostOpts[name] }
1614
                    hostsToSave.append(nodeToSave)
1615
                elif 'Controller' in tags:
1616
                    nodeToSave = {'x':str(x1),
1617
                                  'y':str(y1),
1618
                                  'opts':self.controllers[name] }
1619
                    controllersToSave.append(nodeToSave)
1620
                else:
1621
                    raise Exception( "Cannot create mystery node: " + name )
1622
            savingDictionary['hosts'] = hostsToSave
1623
            savingDictionary['switches'] = switchesToSave
1624
            savingDictionary['controllers'] = controllersToSave
1625

    
1626
            # Save Links
1627
            linksToSave = []
1628
            for link in self.links.values():
1629
                src = link['src']
1630
                dst = link['dest']
1631
                linkopts = link['linkOpts']
1632

    
1633
                srcName, dstName = src[ 'text' ], dst[ 'text' ]
1634
                linkToSave = {'src':srcName,
1635
                              'dest':dstName,
1636
                              'opts':linkopts}
1637
                if link['type'] == 'data':
1638
                    linksToSave.append(linkToSave)
1639
            savingDictionary['links'] = linksToSave
1640

    
1641
            # Save Application preferences
1642
            savingDictionary['application'] = self.appPrefs
1643

    
1644
            try:
1645
                f = open(fileName, 'wb')
1646
                f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': ')))
1647
            except Exception as er:
1648
                print er
1649
            finally:
1650
                f.close()
1651

    
1652
    def exportScript( self ):
1653
        "Export command."
1654
        myFormats = [
1655
            ('Mininet Custom Topology','*.py'),
1656
            ('All Files','*'),
1657
        ]
1658

    
1659
        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...")
1660
        if len(fileName ) > 0:
1661
            #print "Now saving under %s" % fileName
1662
            f = open(fileName, 'wb')
1663

    
1664
            f.write("#!/usr/bin/python\n")
1665
            f.write("\n")
1666
            f.write("from mininet.net import Mininet\n")
1667
            f.write("from mininet.node import Controller, RemoteController, OVSController\n")
1668
            f.write("from mininet.node import CPULimitedHost, Host, Node\n")
1669
            f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n")
1670
            if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
1671
                f.write("from mininet.node import IVSSwitch\n")
1672
            f.write("from mininet.cli import CLI\n")
1673
            f.write("from mininet.log import setLogLevel, info\n")
1674
            f.write("from mininet.link import TCLink, Intf\n")
1675
            f.write("from subprocess import call\n")
1676

    
1677
            inBandCtrl = False
1678
            hasLegacySwitch = False
1679
            for widget in self.widgetToItem:
1680
                name = widget[ 'text' ]
1681
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1682

    
1683
                if 'Controller' in tags:
1684
                    opts = self.controllers[name]
1685
                    controllerType = opts['controllerType']
1686
                    if controllerType == 'inband':
1687
                        inBandCtrl = True
1688

    
1689
            if inBandCtrl == True:
1690
                f.write("\n")
1691
                f.write("class InbandController( RemoteController ):\n")
1692
                f.write("\n")
1693
                f.write("    def checkListening( self ):\n")
1694
                f.write("        \"Overridden to do nothing.\"\n")
1695
                f.write("        return\n")
1696

    
1697
            f.write("\n")
1698
            f.write("def myNetwork():\n")
1699
            f.write("\n")
1700
            f.write("    net = Mininet( topo=None,\n")
1701
            if len(self.appPrefs['dpctl']) > 0:
1702
                f.write("                   listenPort="+self.appPrefs['dpctl']+",\n")
1703
            f.write("                   build=False,\n")
1704
            f.write("                   ipBase='"+self.appPrefs['ipBase']+"')\n")
1705
            f.write("\n")
1706
            f.write("    info( '*** Adding controller\\n' )\n")
1707
            for widget in self.widgetToItem:
1708
                name = widget[ 'text' ]
1709
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1710
    
1711
                if 'Controller' in tags:
1712
                    opts = self.controllers[name]
1713
                    controllerType = opts['controllerType']
1714
                    if 'controllerProtocol' in opts:
1715
                        controllerProtocol = opts['controllerProtocol']
1716
                    else:
1717
                        controllerProtocol = 'tcp'
1718
                    controllerIP = opts['remoteIP']
1719
                    controllerPort = opts['remotePort']
1720

    
1721
    
1722
                    f.write("    "+name+"=net.addController(name='"+name+"',\n")
1723
        
1724
                    if controllerType == 'remote':
1725
                        f.write("                      controller=RemoteController,\n")
1726
                        f.write("                      ip='"+controllerIP+"',\n")
1727
                    elif controllerType == 'inband':
1728
                        f.write("                      controller=InbandController,\n")
1729
                        f.write("                      ip='"+controllerIP+"',\n")
1730
                    elif controllerType == 'ovsc':
1731
                        f.write("                      controller=OVSController,\n")
1732
                    else:
1733
                        f.write("                      controller=Controller,\n")
1734
        
1735
                    f.write("                      protocol='"+controllerProtocol+"',\n")
1736
                    f.write("                      port="+str(controllerPort)+")\n")
1737
                    f.write("\n")
1738

    
1739
            # Save Switches and Hosts
1740
            f.write("    info( '*** Add switches\\n')\n")
1741
            for widget in self.widgetToItem:
1742
                name = widget[ 'text' ]
1743
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1744
                if 'LegacyRouter' in tags:
1745
                    f.write("    "+name+" = net.addHost('"+name+"', cls=Node, ip='0.0.0.0')\n")
1746
                    f.write("    "+name+".cmd('sysctl -w net.ipv4.ip_forward=1')\n")
1747
                if 'LegacySwitch' in tags:
1748
                    f.write("    "+name+" = net.addSwitch('"+name+"', cls=OVSKernelSwitch, failMode='standalone')\n")
1749
                if 'Switch' in tags:
1750
                    opts = self.switchOpts[name]
1751
                    nodeNum = opts['nodeNum']
1752
                    f.write("    "+name+" = net.addSwitch('"+name+"'")
1753
                    if opts['switchType'] == 'default':
1754
                        if self.appPrefs['switchType'] == 'ivs':
1755
                            f.write(", cls=IVSSwitch")
1756
                        elif self.appPrefs['switchType'] == 'user':
1757
                            f.write(", cls=UserSwitch")
1758
                        elif self.appPrefs['switchType'] == 'userns':
1759
                            f.write(", cls=UserSwitch, inNamespace=True")
1760
                        else:
1761
                            f.write(", cls=OVSKernelSwitch")
1762
                    elif opts['switchType'] == 'ivs':
1763
                        f.write(", cls=IVSSwitch")
1764
                    elif opts['switchType'] == 'user':
1765
                        f.write(", cls=UserSwitch")
1766
                    elif opts['switchType'] == 'userns':
1767
                        f.write(", cls=UserSwitch, inNamespace=True")
1768
                    else:
1769
                        f.write(", cls=OVSKernelSwitch")
1770
                    if 'dpctl' in opts:
1771
                        f.write(", listenPort="+opts['dpctl'])
1772
                    if 'dpid' in opts:
1773
                        f.write(", dpid='"+opts['dpid']+"'")
1774
                    f.write(")\n")
1775
                    if ('externalInterfaces' in opts):
1776
                        for extInterface in opts['externalInterfaces']:
1777
                            f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
1778

    
1779
            f.write("\n")
1780
            f.write("    info( '*** Add hosts\\n')\n")
1781
            for widget in self.widgetToItem:
1782
                name = widget[ 'text' ]
1783
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1784
                if 'Host' in tags:
1785
                    opts = self.hostOpts[name]
1786
                    ip = None
1787
                    defaultRoute = None
1788
                    if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
1789
                        defaultRoute = "'via "+opts['defaultRoute']+"'"
1790
                    else:
1791
                        defaultRoute = 'None'
1792
                    if 'ip' in opts and len(opts['ip']) > 0:
1793
                        ip = opts['ip']
1794
                    else:
1795
                        nodeNum = self.hostOpts[name]['nodeNum']
1796
                        ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
1797
                        ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
1798

    
1799
                    if 'cores' in opts or 'cpu' in opts:
1800
                        f.write("    "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
1801
                        if 'cores' in opts:
1802
                            f.write("    "+name+".setCPUs(cores='"+opts['cores']+"')\n")
1803
                        if 'cpu' in opts:
1804
                            f.write("    "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n")
1805
                    else:
1806
                        f.write("    "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
1807
                    if ('externalInterfaces' in opts):
1808
                        for extInterface in opts['externalInterfaces']:
1809
                            f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
1810
            f.write("\n")
1811

    
1812
            # Save Links
1813
            f.write("    info( '*** Add links\\n')\n")
1814
            for key,linkDetail in self.links.iteritems():
1815
              tags = self.canvas.gettags(key)
1816
              if 'data' in tags:
1817
                optsExist = False
1818
                src = linkDetail['src']
1819
                dst = linkDetail['dest']
1820
                linkopts = linkDetail['linkOpts']
1821
                srcName, dstName = src[ 'text' ], dst[ 'text' ]
1822
                bw = ''
1823
                delay = ''
1824
                loss = ''
1825
                max_queue_size = ''
1826
                linkOpts = "{"
1827
                if 'bw' in linkopts:
1828
                    bw =  linkopts['bw']
1829
                    linkOpts = linkOpts + "'bw':"+str(bw)
1830
                    optsExist = True
1831
                if 'delay' in linkopts:
1832
                    delay =  linkopts['delay']
1833
                    if optsExist:
1834
                        linkOpts = linkOpts + ","
1835
                    linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'"
1836
                    optsExist = True
1837
                if 'loss' in linkopts:
1838
                    if optsExist:
1839
                        linkOpts = linkOpts + ","
1840
                    linkOpts = linkOpts + "'loss':"+str(linkopts['loss'])
1841
                    optsExist = True
1842
                if 'max_queue_size' in linkopts:
1843
                    if optsExist:
1844
                        linkOpts = linkOpts + ","
1845
                    linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size'])
1846
                    optsExist = True
1847
                if 'jitter' in linkopts:
1848
                    if optsExist:
1849
                        linkOpts = linkOpts + ","
1850
                    linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'"
1851
                    optsExist = True
1852
                if 'speedup' in linkopts:
1853
                    if optsExist:
1854
                        linkOpts = linkOpts + ","
1855
                    linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup'])
1856
                    optsExist = True
1857

    
1858
                linkOpts = linkOpts + "}"
1859
                if optsExist:
1860
                    f.write("    "+srcName+dstName+" = "+linkOpts+"\n")
1861
                f.write("    net.addLink("+srcName+", "+dstName)
1862
                if optsExist:
1863
                    f.write(", cls=TCLink , **"+srcName+dstName)
1864
                f.write(")\n")
1865

    
1866
            f.write("\n")
1867
            f.write("    info( '*** Starting network\\n')\n")
1868
            f.write("    net.build()\n")
1869

    
1870
            f.write("    info( '*** Starting controllers\\n')\n")
1871
            f.write("    for controller in net.controllers:\n")
1872
            f.write("        controller.start()\n")
1873
            f.write("\n")
1874

    
1875
            f.write("    info( '*** Starting switches\\n')\n")
1876
            for widget in self.widgetToItem:
1877
                name = widget[ 'text' ]
1878
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1879
                if 'Switch' in tags or 'LegacySwitch' in tags:
1880
                    opts = self.switchOpts[name]
1881
                    ctrlList = ",".join(opts['controllers'])
1882
                    f.write("    net.get('"+name+"').start(["+ctrlList+"])\n")
1883

    
1884
            f.write("\n")
1885

    
1886
            f.write("    info( '*** Post configure switches and hosts\\n')\n")
1887
            for widget in self.widgetToItem:
1888
                name = widget[ 'text' ]
1889
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1890
                if 'Switch' in tags:
1891
                    opts = self.switchOpts[name]
1892
                    if opts['switchType'] == 'default':
1893
                        if self.appPrefs['switchType'] == 'user':
1894
                            if ('switchIP' in opts):
1895
                                if (len(opts['switchIP'])>0):
1896
                                    f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1897
                        elif self.appPrefs['switchType'] == 'userns':
1898
                            if ('switchIP' in opts):
1899
                                if (len(opts['switchIP'])>0):
1900
                                    f.write("    "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n")
1901
                        elif self.appPrefs['switchType'] == 'ovs':
1902
                            if ('switchIP' in opts):
1903
                                if (len(opts['switchIP'])>0):
1904
                                    f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1905
                    elif opts['switchType'] == 'user':
1906
                        if ('switchIP' in opts):
1907
                            if (len(opts['switchIP'])>0):
1908
                                f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1909
                    elif opts['switchType'] == 'userns':
1910
                        if ('switchIP' in opts):
1911
                            if (len(opts['switchIP'])>0):
1912
                                f.write("    "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n")
1913
                    elif opts['switchType'] == 'ovs':
1914
                        if ('switchIP' in opts):
1915
                            if (len(opts['switchIP'])>0):
1916
                                f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1917
            for widget in self.widgetToItem:
1918
                name = widget[ 'text' ]
1919
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1920
                if 'Host' in tags:
1921
                    opts = self.hostOpts[name]
1922
                    # Attach vlan interfaces
1923
                    if ('vlanInterfaces' in opts):
1924
                        for vlanInterface in opts['vlanInterfaces']:
1925
                            f.write("    "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n")
1926
                            f.write("    "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n")
1927
                    # Run User Defined Start Command
1928
                    if ('startCommand' in opts):
1929
                        f.write("    "+name+".cmdPrint('"+opts['startCommand']+"')\n")
1930
                if 'Switch' in tags:
1931
                    opts = self.switchOpts[name]
1932
                    # Run User Defined Start Command
1933
                    if ('startCommand' in opts):
1934
                        f.write("    "+name+".cmdPrint('"+opts['startCommand']+"')\n")
1935

    
1936
            # Configure NetFlow
1937
            nflowValues = self.appPrefs['netflow']
1938
            if len(nflowValues['nflowTarget']) > 0:
1939
                nflowEnabled = False
1940
                nflowSwitches = ''
1941
                for widget in self.widgetToItem:
1942
                    name = widget[ 'text' ]
1943
                    tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1944
    
1945
                    if 'Switch' in tags:
1946
                        opts = self.switchOpts[name]
1947
                        if 'netflow' in opts:
1948
                            if opts['netflow'] == '1':
1949
                                nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF'
1950
                                nflowEnabled=True
1951
                if nflowEnabled:
1952
                    nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout']
1953
                    if nflowValues['nflowAddId'] == '1':
1954
                        nflowCmd = nflowCmd + ' add_id_to_interface=true'
1955
                    else:
1956
                        nflowCmd = nflowCmd + ' add_id_to_interface=false'
1957
                    f.write("    \n")
1958
                    f.write("    call('"+nflowCmd+nflowSwitches+"', shell=True)\n")
1959

    
1960
            # Configure sFlow
1961
            sflowValues = self.appPrefs['sflow']
1962
            if len(sflowValues['sflowTarget']) > 0:
1963
                sflowEnabled = False
1964
                sflowSwitches = ''
1965
                for widget in self.widgetToItem:
1966
                    name = widget[ 'text' ]
1967
                    tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1968
    
1969
                    if 'Switch' in tags:
1970
                        opts = self.switchOpts[name]
1971
                        if 'sflow' in opts:
1972
                            if opts['sflow'] == '1':
1973
                                sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
1974
                                sflowEnabled=True
1975
                if sflowEnabled:
1976
                    sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling']
1977
                    f.write("    \n")
1978
                    f.write("    call('"+sflowCmd+sflowSwitches+"', shell=True)\n")
1979

    
1980
            f.write("\n")
1981
            f.write("    CLI(net)\n")
1982
            for widget in self.widgetToItem:
1983
                name = widget[ 'text' ]
1984
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1985
                if 'Host' in tags:
1986
                    opts = self.hostOpts[name]
1987
                    # Run User Defined Stop Command
1988
                    if ('stopCommand' in opts):
1989
                        f.write("    "+name+".cmdPrint('"+opts['stopCommand']+"')\n")
1990
                if 'Switch' in tags:
1991
                    opts = self.switchOpts[name]
1992
                    # Run User Defined Stop Command
1993
                    if ('stopCommand' in opts):
1994
                        f.write("    "+name+".cmdPrint('"+opts['stopCommand']+"')\n")
1995

    
1996
            f.write("    net.stop()\n")
1997
            f.write("\n")
1998
            f.write("if __name__ == '__main__':\n")
1999
            f.write("    setLogLevel( 'info' )\n")
2000
            f.write("    myNetwork()\n")
2001
            f.write("\n")
2002

    
2003

    
2004
            f.close()
2005

    
2006

    
2007
    # Generic canvas handler
2008
    #
2009
    # We could have used bindtags, as in nodeIcon, but
2010
    # the dynamic approach used here
2011
    # may actually require less code. In any case, it's an
2012
    # interesting introspection-based alternative to bindtags.
2013

    
2014
    def canvasHandle( self, eventName, event ):
2015
        "Generic canvas event handler"
2016
        if self.active is None:
2017
            return
2018
        toolName = self.active
2019
        handler = getattr( self, eventName + toolName, None )
2020
        if handler is not None:
2021
            handler( event )
2022

    
2023
    def clickCanvas( self, event ):
2024
        "Canvas click handler."
2025
        self.canvasHandle( 'click', event )
2026

    
2027
    def dragCanvas( self, event ):
2028
        "Canvas drag handler."
2029
        self.canvasHandle( 'drag', event )
2030

    
2031
    def releaseCanvas( self, event ):
2032
        "Canvas mouse up handler."
2033
        self.canvasHandle( 'release', event )
2034

    
2035
    # Currently the only items we can select directly are
2036
    # links. Nodes are handled by bindings in the node icon.
2037

    
2038
    def findItem( self, x, y ):
2039
        "Find items at a location in our canvas."
2040
        items = self.canvas.find_overlapping( x, y, x, y )
2041
        if len( items ) == 0:
2042
            return None
2043
        else:
2044
            return items[ 0 ]
2045

    
2046
    # Canvas bindings for Select, Host, Switch and Link tools
2047

    
2048
    def clickSelect( self, event ):
2049
        "Select an item."
2050
        self.selectItem( self.findItem( event.x, event.y ) )
2051

    
2052
    def deleteItem( self, item ):
2053
        "Delete an item."
2054
        # Don't delete while network is running
2055
        if self.buttons[ 'Select' ][ 'state' ] == 'disabled':
2056
            return
2057
        # Delete from model
2058
        if item in self.links:
2059
            self.deleteLink( item )
2060
        if item in self.itemToWidget:
2061
            self.deleteNode( item )
2062
        # Delete from view
2063
        self.canvas.delete( item )
2064

    
2065
    def deleteSelection( self, _event ):
2066
        "Delete the selected item."
2067
        if self.selection is not None:
2068
            self.deleteItem( self.selection )
2069
        self.selectItem( None )
2070

    
2071
    def nodeIcon( self, node, name ):
2072
        "Create a new node icon."
2073
        icon = Button( self.canvas, image=self.images[ node ],
2074
                       text=name, compound='top' )
2075
        # Unfortunately bindtags wants a tuple
2076
        bindtags = [ str( self.nodeBindings ) ]
2077
        bindtags += list( icon.bindtags() )
2078
        icon.bindtags( tuple( bindtags ) )
2079
        return icon
2080

    
2081
    def newNode( self, node, event ):
2082
        "Add a new node to our canvas."
2083
        c = self.canvas
2084
        x, y = c.canvasx( event.x ), c.canvasy( event.y )
2085
        name = self.nodePrefixes[ node ]
2086
        if 'Switch' == node:
2087
            self.switchCount += 1
2088
            name = self.nodePrefixes[ node ] + str( self.switchCount )
2089
            self.switchOpts[name] = {}
2090
            self.switchOpts[name]['nodeNum']=self.switchCount
2091
            self.switchOpts[name]['hostname']=name
2092
            self.switchOpts[name]['switchType']='default'
2093
            self.switchOpts[name]['controllers']=[]
2094
        if 'LegacyRouter' == node:
2095
            self.switchCount += 1
2096
            name = self.nodePrefixes[ node ] + str( self.switchCount )
2097
            self.switchOpts[name] = {}
2098
            self.switchOpts[name]['nodeNum']=self.switchCount
2099
            self.switchOpts[name]['hostname']=name
2100
            self.switchOpts[name]['switchType']='legacyRouter'
2101
        if 'LegacySwitch' == node:
2102
            self.switchCount += 1
2103
            name = self.nodePrefixes[ node ] + str( self.switchCount )
2104
            self.switchOpts[name] = {}
2105
            self.switchOpts[name]['nodeNum']=self.switchCount
2106
            self.switchOpts[name]['hostname']=name
2107
            self.switchOpts[name]['switchType']='legacySwitch'
2108
            self.switchOpts[name]['controllers']=[]
2109
        if 'Host' == node:
2110
            self.hostCount += 1
2111
            name = self.nodePrefixes[ node ] + str( self.hostCount )
2112
            self.hostOpts[name] = {'sched':'host'}
2113
            self.hostOpts[name]['nodeNum']=self.hostCount
2114
            self.hostOpts[name]['hostname']=name
2115
        if 'Controller' == node:
2116
            name = self.nodePrefixes[ node ] + str( self.controllerCount )
2117
            ctrlr = { 'controllerType': 'ref',
2118
                      'hostname': name,
2119
                      'controllerProtocol': 'tcp',
2120
                      'remoteIP': '127.0.0.1',
2121
                      'remotePort': 6633}
2122
            self.controllers[name] = ctrlr
2123
            # We want to start controller count at 0
2124
            self.controllerCount += 1
2125

    
2126
        icon = self.nodeIcon( node, name )
2127
        item = self.canvas.create_window( x, y, anchor='c', window=icon,
2128
                                          tags=node )
2129
        self.widgetToItem[ icon ] = item
2130
        self.itemToWidget[ item ] = icon
2131
        self.selectItem( item )
2132
        icon.links = {}
2133
        if 'Switch' == node:
2134
            icon.bind('<Button-3>', self.do_switchPopup )
2135
        if 'LegacyRouter' == node:
2136
            icon.bind('<Button-3>', self.do_legacyRouterPopup )
2137
        if 'LegacySwitch' == node:
2138
            icon.bind('<Button-3>', self.do_legacySwitchPopup )
2139
        if 'Host' == node:
2140
            icon.bind('<Button-3>', self.do_hostPopup )
2141
        if 'Controller' == node:
2142
            icon.bind('<Button-3>', self.do_controllerPopup )
2143

    
2144
    def clickController( self, event ):
2145
        "Add a new Controller to our canvas."
2146
        self.newNode( 'Controller', event )
2147

    
2148
    def clickHost( self, event ):
2149
        "Add a new host to our canvas."
2150
        self.newNode( 'Host', event )
2151

    
2152
    def clickLegacyRouter( self, event ):
2153
        "Add a new switch to our canvas."
2154
        self.newNode( 'LegacyRouter', event )
2155

    
2156
    def clickLegacySwitch( self, event ):
2157
        "Add a new switch to our canvas."
2158
        self.newNode( 'LegacySwitch', event )
2159

    
2160
    def clickSwitch( self, event ):
2161
        "Add a new switch to our canvas."
2162
        self.newNode( 'Switch', event )
2163

    
2164
    def dragNetLink( self, event ):
2165
        "Drag a link's endpoint to another node."
2166
        if self.link is None:
2167
            return
2168
        # Since drag starts in widget, we use root coords
2169
        x = self.canvasx( event.x_root )
2170
        y = self.canvasy( event.y_root )
2171
        c = self.canvas
2172
        c.coords( self.link, self.linkx, self.linky, x, y )
2173

    
2174
    def releaseNetLink( self, _event ):
2175
        "Give up on the current link."
2176
        if self.link is not None:
2177
            self.canvas.delete( self.link )
2178
        self.linkWidget = self.linkItem = self.link = None
2179

    
2180
    # Generic node handlers
2181

    
2182
    def createNodeBindings( self ):
2183
        "Create a set of bindings for nodes."
2184
        bindings = {
2185
            '<ButtonPress-1>': self.clickNode,
2186
            '<B1-Motion>': self.dragNode,
2187
            '<ButtonRelease-1>': self.releaseNode,
2188
            '<Enter>': self.enterNode,
2189
            '<Leave>': self.leaveNode
2190
        }
2191
        l = Label()  # lightweight-ish owner for bindings
2192
        for event, binding in bindings.items():
2193
            l.bind( event, binding )
2194
        return l
2195

    
2196
    def selectItem( self, item ):
2197
        "Select an item and remember old selection."
2198
        self.lastSelection = self.selection
2199
        self.selection = item
2200

    
2201
    def enterNode( self, event ):
2202
        "Select node on entry."
2203
        self.selectNode( event )
2204

    
2205
    def leaveNode( self, _event ):
2206
        "Restore old selection on exit."
2207
        self.selectItem( self.lastSelection )
2208

    
2209
    def clickNode( self, event ):
2210
        "Node click handler."
2211
        if self.active is 'NetLink':
2212
            self.startLink( event )
2213
        else:
2214
            self.selectNode( event )
2215
        return 'break'
2216

    
2217
    def dragNode( self, event ):
2218
        "Node drag handler."
2219
        if self.active is 'NetLink':
2220
            self.dragNetLink( event )
2221
        else:
2222
            self.dragNodeAround( event )
2223

    
2224
    def releaseNode( self, event ):
2225
        "Node release handler."
2226
        if self.active is 'NetLink':
2227
            self.finishLink( event )
2228

    
2229
    # Specific node handlers
2230

    
2231
    def selectNode( self, event ):
2232
        "Select the node that was clicked on."
2233
        item = self.widgetToItem.get( event.widget, None )
2234
        self.selectItem( item )
2235

    
2236
    def dragNodeAround( self, event ):
2237
        "Drag a node around on the canvas."
2238
        c = self.canvas
2239
        # Convert global to local coordinates;
2240
        # Necessary since x, y are widget-relative
2241
        x = self.canvasx( event.x_root )
2242
        y = self.canvasy( event.y_root )
2243
        w = event.widget
2244
        # Adjust node position
2245
        item = self.widgetToItem[ w ]
2246
        c.coords( item, x, y )
2247
        # Adjust link positions
2248
        for dest in w.links:
2249
            link = w.links[ dest ]
2250
            item = self.widgetToItem[ dest ]
2251
            x1, y1 = c.coords( item )
2252
            c.coords( link, x, y, x1, y1 )
2253
        self.updateScrollRegion()
2254

    
2255
    def createControlLinkBindings( self ):
2256
        "Create a set of bindings for nodes."
2257
        # Link bindings
2258
        # Selection still needs a bit of work overall
2259
        # Callbacks ignore event
2260

    
2261
        def select( _event, link=self.link ):
2262
            "Select item on mouse entry."
2263
            self.selectItem( link )
2264

    
2265
        def highlight( _event, link=self.link ):
2266
            "Highlight item on mouse entry."
2267
            self.selectItem( link )
2268
            self.canvas.itemconfig( link, fill='green' )
2269

    
2270
        def unhighlight( _event, link=self.link ):
2271
            "Unhighlight item on mouse exit."
2272
            self.canvas.itemconfig( link, fill='red' )
2273
            #self.selectItem( None )
2274

    
2275
        self.canvas.tag_bind( self.link, '<Enter>', highlight )
2276
        self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
2277
        self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
2278

    
2279
    def createDataLinkBindings( self ):
2280
        "Create a set of bindings for nodes."
2281
        # Link bindings
2282
        # Selection still needs a bit of work overall
2283
        # Callbacks ignore event
2284

    
2285
        def select( _event, link=self.link ):
2286
            "Select item on mouse entry."
2287
            self.selectItem( link )
2288

    
2289
        def highlight( _event, link=self.link ):
2290
            "Highlight item on mouse entry."
2291
            self.selectItem( link )
2292
            self.canvas.itemconfig( link, fill='green' )
2293

    
2294
        def unhighlight( _event, link=self.link ):
2295
            "Unhighlight item on mouse exit."
2296
            self.canvas.itemconfig( link, fill='blue' )
2297
            #self.selectItem( None )
2298

    
2299
        self.canvas.tag_bind( self.link, '<Enter>', highlight )
2300
        self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
2301
        self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
2302
        self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup )
2303

    
2304

    
2305
    def startLink( self, event ):
2306
        "Start a new link."
2307
        if event.widget not in self.widgetToItem:
2308
            # Didn't click on a node
2309
            return
2310

    
2311
        w = event.widget
2312
        item = self.widgetToItem[ w ]
2313
        x, y = self.canvas.coords( item )
2314
        self.link = self.canvas.create_line( x, y, x, y, width=4,
2315
                                             fill='blue', tag='link' )
2316
        self.linkx, self.linky = x, y
2317
        self.linkWidget = w
2318
        self.linkItem = item
2319

    
2320

    
2321
    def finishLink( self, event ):
2322
        "Finish creating a link"
2323
        if self.link is None:
2324
            return
2325
        source = self.linkWidget
2326
        c = self.canvas
2327
        # Since we dragged from the widget, use root coords
2328
        x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root )
2329
        target = self.findItem( x, y )
2330
        dest = self.itemToWidget.get( target, None )
2331
        if ( source is None or dest is None or source == dest
2332
                or dest in source.links or source in dest.links ):
2333
            self.releaseNetLink( event )
2334
            return
2335
        # For now, don't allow hosts to be directly linked
2336
        stags = self.canvas.gettags( self.widgetToItem[ source ] )
2337
        dtags = self.canvas.gettags( target )
2338
        if (('Host' in stags and 'Host' in dtags) or
2339
           ('Controller' in dtags and 'LegacyRouter' in stags) or
2340
           ('Controller' in stags and 'LegacyRouter' in dtags) or
2341
           ('Controller' in dtags and 'LegacySwitch' in stags) or
2342
           ('Controller' in stags and 'LegacySwitch' in dtags) or
2343
           ('Controller' in dtags and 'Host' in stags) or
2344
           ('Controller' in stags and 'Host' in dtags) or
2345
           ('Controller' in stags and 'Controller' in dtags)):
2346
            self.releaseNetLink( event )
2347
            return
2348

    
2349
        # Set link type
2350
        linkType='data'
2351
        if 'Controller' in stags or 'Controller' in dtags:
2352
            linkType='control'
2353
            c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red')
2354
            self.createControlLinkBindings()
2355
        else:
2356
            linkType='data'
2357
            self.createDataLinkBindings()
2358
        c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,))
2359

    
2360
        x, y = c.coords( target )
2361
        c.coords( self.link, self.linkx, self.linky, x, y )
2362
        self.addLink( source, dest, linktype=linkType )
2363
        if linkType == 'control':
2364
            controllerName = ''
2365
            switchName = ''
2366
            if 'Controller' in stags:
2367
                controllerName = source[ 'text' ]
2368
                switchName = dest[ 'text' ]
2369
            else:
2370
                controllerName = dest[ 'text' ]
2371
                switchName = source[ 'text' ]
2372

    
2373
            self.switchOpts[switchName]['controllers'].append(controllerName)
2374

    
2375
        # We're done
2376
        self.link = self.linkWidget = None
2377

    
2378
    # Menu handlers
2379

    
2380
    def about( self ):
2381
        "Display about box."
2382
        about = self.aboutBox
2383
        if about is None:
2384
            bg = 'white'
2385
            about = Toplevel( bg='white' )
2386
            about.title( 'About' )
2387
            info = self.appName + ': a simple network editor for MiniNet'
2388
            version = 'MiniEdit '+MINIEDIT_VERSION
2389
            author = 'Originally by: Bob Lantz <rlantz@cs>, April 2010'
2390
            enhancements = 'Enhancements by: Gregory Gee, Since July 2013'
2391
            www = 'http://gregorygee.wordpress.com/category/miniedit/'
2392
            line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg )
2393
            line2 = Label( about, text=version, font='Helvetica 9', bg=bg )
2394
            line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
2395
            line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg )
2396
            line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER )
2397
            line5.insert(0, www)
2398
            line5.configure(state='readonly')
2399
            line1.pack( padx=20, pady=10 )
2400
            line2.pack(pady=10 )
2401
            line3.pack(pady=10 )
2402
            line4.pack(pady=10 )
2403
            line5.pack(pady=10 )
2404
            hide = ( lambda about=about: about.withdraw() )
2405
            self.aboutBox = about
2406
            # Hide on close rather than destroying window
2407
            Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
2408
        # Show (existing) window
2409
        about.deiconify()
2410

    
2411
    def createToolImages( self ):
2412
        "Create toolbar (and icon) images."
2413

    
2414
    def checkIntf( self, intf ):
2415
        "Make sure intf exists and is not configured."
2416
        if ( ' %s:' % intf ) not in quietRun( 'ip link show' ):
2417
            showerror(title="Error",
2418
                      message='External interface ' +intf + ' does not exist! Skipping.')
2419
            return False
2420
        ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) )
2421
        if ips:
2422
            showerror(title="Error",
2423
                      message= intf + ' has an IP address and is probably in use! Skipping.' )
2424
            return False
2425
        return True
2426

    
2427
    def hostDetails( self, _ignore=None ):
2428
        if ( self.selection is None or
2429
             self.net is not None or
2430
             self.selection not in self.itemToWidget ):
2431
            return
2432
        widget = self.itemToWidget[ self.selection ]
2433
        name = widget[ 'text' ]
2434
        tags = self.canvas.gettags( self.selection )
2435
        if 'Host' not in tags:
2436
            return
2437

    
2438
        prefDefaults = self.hostOpts[name]
2439
        hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults)
2440
        self.master.wait_window(hostBox.top)
2441
        if hostBox.result:
2442
            newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']}
2443
            newHostOpts['sched'] = hostBox.result['sched']
2444
            if len(hostBox.result['startCommand']) > 0:
2445
                newHostOpts['startCommand'] = hostBox.result['startCommand']
2446
            if len(hostBox.result['stopCommand']) > 0:
2447
                newHostOpts['stopCommand'] = hostBox.result['stopCommand']
2448
            if len(hostBox.result['cpu']) > 0:
2449
                newHostOpts['cpu'] = float(hostBox.result['cpu'])
2450
            if len(hostBox.result['cores']) > 0:
2451
                newHostOpts['cores'] = hostBox.result['cores']
2452
            if len(hostBox.result['hostname']) > 0:
2453
                newHostOpts['hostname'] = hostBox.result['hostname']
2454
                name = hostBox.result['hostname']
2455
                widget[ 'text' ] = name
2456
            if len(hostBox.result['defaultRoute']) > 0:
2457
                newHostOpts['defaultRoute'] = hostBox.result['defaultRoute']
2458
            if len(hostBox.result['ip']) > 0:
2459
                newHostOpts['ip'] = hostBox.result['ip']
2460
            if len(hostBox.result['externalInterfaces']) > 0:
2461
                newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces']
2462
            if len(hostBox.result['vlanInterfaces']) > 0:
2463
                newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces']
2464
            if len(hostBox.result['privateDirectory']) > 0:
2465
                newHostOpts['privateDirectory'] = hostBox.result['privateDirectory']
2466
            self.hostOpts[name] = newHostOpts
2467
            print 'New host details for ' + name + ' = ' + str(newHostOpts)
2468

    
2469
    def switchDetails( self, _ignore=None ):
2470
        if ( self.selection is None or
2471
             self.net is not None or
2472
             self.selection not in self.itemToWidget ):
2473
            return
2474
        widget = self.itemToWidget[ self.selection ]
2475
        name = widget[ 'text' ]
2476
        tags = self.canvas.gettags( self.selection )
2477
        if 'Switch' not in tags:
2478
            return
2479

    
2480
        prefDefaults = self.switchOpts[name]
2481
        switchBox = SwitchDialog(self, title='Switch Details', prefDefaults=prefDefaults)
2482
        self.master.wait_window(switchBox.top)
2483
        if switchBox.result:
2484
            newSwitchOpts = {'nodeNum':self.switchOpts[name]['nodeNum']}
2485
            newSwitchOpts['switchType'] = switchBox.result['switchType']
2486
            newSwitchOpts['controllers'] = self.switchOpts[name]['controllers']
2487
            if len(switchBox.result['startCommand']) > 0:
2488
                newSwitchOpts['startCommand'] = switchBox.result['startCommand']
2489
            if len(switchBox.result['stopCommand']) > 0:
2490
                newSwitchOpts['stopCommand'] = switchBox.result['stopCommand']
2491
            if len(switchBox.result['dpctl']) > 0:
2492
                newSwitchOpts['dpctl'] = switchBox.result['dpctl']
2493
            if len(switchBox.result['dpid']) > 0:
2494
                newSwitchOpts['dpid'] = switchBox.result['dpid']
2495
            if len(switchBox.result['hostname']) > 0:
2496
                newSwitchOpts['hostname'] = switchBox.result['hostname']
2497
                name = switchBox.result['hostname']
2498
                widget[ 'text' ] = name
2499
            if len(switchBox.result['externalInterfaces']) > 0:
2500
                newSwitchOpts['externalInterfaces'] = switchBox.result['externalInterfaces']
2501
            newSwitchOpts['switchIP'] = switchBox.result['switchIP']
2502
            newSwitchOpts['sflow'] = switchBox.result['sflow']
2503
            newSwitchOpts['netflow'] = switchBox.result['netflow']
2504
            self.switchOpts[name] = newSwitchOpts
2505
            print 'New switch details for ' + name + ' = ' + str(newSwitchOpts)
2506

    
2507
    def linkUp( self ):
2508
        if ( self.selection is None or
2509
             self.net is None):
2510
            return
2511
        link = self.selection
2512
        linkDetail =  self.links[link]
2513
        src = linkDetail['src']
2514
        dst = linkDetail['dest']
2515
        srcName, dstName = src[ 'text' ], dst[ 'text' ]
2516
        self.net.configLinkStatus(srcName, dstName, 'up')
2517
        self.canvas.itemconfig(link, dash=())
2518

    
2519
    def linkDown( self ):
2520
        if ( self.selection is None or
2521
             self.net is None):
2522
            return
2523
        link = self.selection
2524
        linkDetail =  self.links[link]
2525
        src = linkDetail['src']
2526
        dst = linkDetail['dest']
2527
        srcName, dstName = src[ 'text' ], dst[ 'text' ]
2528
        self.net.configLinkStatus(srcName, dstName, 'down')
2529
        self.canvas.itemconfig(link, dash=(4, 4))
2530

    
2531
    def linkDetails( self, _ignore=None ):
2532
        if ( self.selection is None or
2533
             self.net is not None):
2534
            return
2535
        link = self.selection
2536

    
2537
        linkDetail =  self.links[link]
2538
        src = linkDetail['src']
2539
        dest = linkDetail['dest']
2540
        linkopts = linkDetail['linkOpts']
2541
        linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
2542
        if linkBox.result is not None:
2543
            linkDetail['linkOpts'] = linkBox.result
2544
            print 'New link details = ' + str(linkBox.result)
2545

    
2546
    def prefDetails( self ):
2547
        prefDefaults = self.appPrefs
2548
        prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults)
2549
        print 'New Prefs = ' + str(prefBox.result)
2550
        if prefBox.result:
2551
            self.appPrefs = prefBox.result
2552

    
2553

    
2554
    def controllerDetails( self ):
2555
        if ( self.selection is None or
2556
             self.net is not None or
2557
             self.selection not in self.itemToWidget ):
2558
            return
2559
        widget = self.itemToWidget[ self.selection ]
2560
        name = widget[ 'text' ]
2561
        tags = self.canvas.gettags( self.selection )
2562
        oldName = name
2563
        if 'Controller' not in tags:
2564
            return
2565

    
2566
        ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name])
2567
        if ctrlrBox.result:
2568
            #print 'Controller is ' + ctrlrBox.result[0]
2569
            if len(ctrlrBox.result['hostname']) > 0:
2570
                name = ctrlrBox.result['hostname']
2571
                widget[ 'text' ] = name
2572
            else:
2573
                ctrlrBox.result['hostname'] = name
2574
            self.controllers[name] = ctrlrBox.result
2575
            print 'New controller details for ' + name + ' = ' + str(self.controllers[name])
2576
            # Find references to controller and change name
2577
            if oldName != name:
2578
                for widget in self.widgetToItem:
2579
                    switchName = widget[ 'text' ]
2580
                    tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2581
                    if 'Switch' in tags:
2582
                        switch = self.switchOpts[switchName]
2583
                        if oldName in switch['controllers']:
2584
                            switch['controllers'].remove(oldName)
2585
                            switch['controllers'].append(name)
2586

    
2587

    
2588
    def listBridge( self, _ignore=None ):
2589
        if ( self.selection is None or
2590
             self.net is None or
2591
             self.selection not in self.itemToWidget ):
2592
            return
2593
        name = self.itemToWidget[ self.selection ][ 'text' ]
2594
        tags = self.canvas.gettags( self.selection )
2595

    
2596
        if name not in self.net.nameToNode:
2597
            return
2598
        if 'Switch' in tags or 'LegacySwitch' in tags:
2599
           call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True)
2600

    
2601
    def ovsShow( self, _ignore=None ):
2602
        call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True)
2603

    
2604
    def rootTerminal( self, _ignore=None ):
2605
        call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True)
2606

    
2607
    # Model interface
2608
    #
2609
    # Ultimately we will either want to use a topo or
2610
    # mininet object here, probably.
2611

    
2612
    def addLink( self, source, dest, linktype='data', linkopts={} ):
2613
        "Add link to model."
2614
        source.links[ dest ] = self.link
2615
        dest.links[ source ] = self.link
2616
        self.links[ self.link ] = {'type' :linktype,
2617
                                   'src':source,
2618
                                   'dest':dest,
2619
                                   'linkOpts':linkopts}
2620

    
2621
    def deleteLink( self, link ):
2622
        "Delete link from model."
2623
        pair = self.links.get( link, None )
2624
        if pair is not None:
2625
            source=pair['src']
2626
            dest=pair['dest']
2627
            del source.links[ dest ]
2628
            del dest.links[ source ]
2629
            stags = self.canvas.gettags( self.widgetToItem[ source ] )
2630
            dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
2631
            ltags = self.canvas.gettags( link )
2632

    
2633
            if 'control' in ltags:
2634
                controllerName = ''
2635
                switchName = ''
2636
                if 'Controller' in stags:
2637
                    controllerName = source[ 'text' ]
2638
                    switchName = dest[ 'text' ]
2639
                else:
2640
                    controllerName = dest[ 'text' ]
2641
                    switchName = source[ 'text' ]
2642
    
2643
                if controllerName in self.switchOpts[switchName]['controllers']:
2644
                    self.switchOpts[switchName]['controllers'].remove(controllerName)
2645

    
2646

    
2647
        if link is not None:
2648
            del self.links[ link ]
2649

    
2650
    def deleteNode( self, item ):
2651
        "Delete node (and its links) from model."
2652

    
2653
        widget = self.itemToWidget[ item ]
2654
        tags = self.canvas.gettags(item)
2655
        if 'Controller' in tags:
2656
            # remove from switch controller lists
2657
            for serachwidget in self.widgetToItem:
2658
                name = serachwidget[ 'text' ]
2659
                tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] )
2660
                if 'Switch' in tags:
2661
                    if widget['text'] in self.switchOpts[name]['controllers']:
2662
                        self.switchOpts[name]['controllers'].remove(widget['text'])
2663
            
2664
        for link in widget.links.values():
2665
            # Delete from view and model
2666
            self.deleteItem( link )
2667
        del self.itemToWidget[ item ]
2668
        del self.widgetToItem[ widget ]
2669

    
2670
    def buildNodes( self, net):
2671
        # Make nodes
2672
        print "Getting Hosts and Switches."
2673
        for widget in self.widgetToItem:
2674
            name = widget[ 'text' ]
2675
            tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2676
            #print name+' has '+str(tags)
2677

    
2678
            if 'Switch' in tags:
2679
                opts = self.switchOpts[name]
2680
                #print str(opts)
2681

    
2682
                # Create the correct switch class
2683
                switchClass = customOvs
2684
                switchParms={}
2685
                if 'dpctl' in opts:
2686
                    switchParms['listenPort']=int(opts['dpctl'])
2687
                if 'dpid' in opts:
2688
                    switchParms['dpid']=opts['dpid']
2689
                if opts['switchType'] == 'default':
2690
                    if self.appPrefs['switchType'] == 'ivs':
2691
                        switchClass = IVSSwitch
2692
                    elif self.appPrefs['switchType'] == 'user':
2693
                        switchClass = CustomUserSwitch
2694
                    elif self.appPrefs['switchType'] == 'userns':
2695
                        switchParms['inNamespace'] = True
2696
                        switchClass = CustomUserSwitch
2697
                    else:
2698
                        switchClass = customOvs
2699
                elif opts['switchType'] == 'user':
2700
                    switchClass = CustomUserSwitch
2701
                elif opts['switchType'] == 'userns':
2702
                    switchClass = CustomUserSwitch
2703
                    switchParms['inNamespace'] = True
2704
                elif opts['switchType'] == 'ivs':
2705
                    switchClass = IVSSwitch
2706
                else:
2707
                    switchClass = customOvs
2708

    
2709
                if switchClass == customOvs:
2710
                    # Set OpenFlow versions
2711
                    self.openFlowVersions = []
2712
                    if self.appPrefs['openFlowVersions']['ovsOf10'] == '1':
2713
                        self.openFlowVersions.append('OpenFlow10')
2714
                    if self.appPrefs['openFlowVersions']['ovsOf11'] == '1':
2715
                        self.openFlowVersions.append('OpenFlow11')
2716
                    if self.appPrefs['openFlowVersions']['ovsOf12'] == '1':
2717
                        self.openFlowVersions.append('OpenFlow12')
2718
                    if self.appPrefs['openFlowVersions']['ovsOf13'] == '1':
2719
                        self.openFlowVersions.append('OpenFlow13')
2720
                    protoList = ",".join(self.openFlowVersions)
2721
                    switchParms['protocols'] = protoList
2722
                newSwitch = net.addSwitch( name , cls=switchClass, **switchParms)
2723

    
2724
                # Some post startup config
2725
                if switchClass == CustomUserSwitch:
2726
                    if ('switchIP' in opts):
2727
                        if (len(opts['switchIP']) > 0):
2728
                            newSwitch.setSwitchIP(opts['switchIP'])
2729
                if switchClass == customOvs:
2730
                    if ('switchIP' in opts):
2731
                        if (len(opts['switchIP']) > 0):
2732
                            newSwitch.setSwitchIP(opts['switchIP'])
2733

    
2734
                # Attach external interfaces
2735
                if ('externalInterfaces' in opts):
2736
                    for extInterface in opts['externalInterfaces']:
2737
                        if self.checkIntf(extInterface):
2738
                           Intf( extInterface, node=newSwitch )
2739

    
2740
            elif 'LegacySwitch' in tags:
2741
                newSwitch = net.addSwitch( name , cls=LegacySwitch)
2742
            elif 'LegacyRouter' in tags:
2743
                newSwitch = net.addHost( name , cls=LegacyRouter)
2744
            elif 'Host' in tags:
2745
                opts = self.hostOpts[name]
2746
                #print str(opts)
2747
                ip = None
2748
                defaultRoute = None
2749
                if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
2750
                    defaultRoute = 'via '+opts['defaultRoute']
2751
                if 'ip' in opts and len(opts['ip']) > 0:
2752
                    ip = opts['ip']
2753
                else:
2754
                    nodeNum = self.hostOpts[name]['nodeNum']
2755
                    ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
2756
                    ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
2757

    
2758
                # Create the correct host class
2759
                if 'cores' in opts or 'cpu' in opts:
2760
                    if ('privateDirectory' in opts):
2761
                        hostCls = partial( CPULimitedHost,
2762
                                           privateDirs=opts['privateDirectory'] )
2763
                    else:
2764
                        hostCls=CPULimitedHost
2765
                else:
2766
                    if ('privateDirectory' in opts):
2767
                        hostCls = partial( Host,
2768
                                           privateDirs=opts['privateDirectory'] )
2769
                    else:
2770
                        hostCls=Host
2771
                print hostCls
2772
                newHost = net.addHost( name,
2773
                                       cls=hostCls,
2774
                                       ip=ip,
2775
                                       defaultRoute=defaultRoute
2776
                                      )
2777

    
2778
                # Set the CPULimitedHost specific options
2779
                if 'cores' in opts:
2780
                    newHost.setCPUs(cores = opts['cores'])
2781
                if 'cpu' in opts:
2782
                    newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched'])
2783

    
2784
                # Attach external interfaces
2785
                if ('externalInterfaces' in opts):
2786
                    for extInterface in opts['externalInterfaces']:
2787
                        if self.checkIntf(extInterface):
2788
                           Intf( extInterface, node=newHost )
2789
                if ('vlanInterfaces' in opts):
2790
                    if len(opts['vlanInterfaces']) > 0:
2791
                        print 'Checking that OS is VLAN prepared'
2792
                        self.pathCheck('vconfig', moduleName='vlan package')
2793
                        moduleDeps( add='8021q' )
2794
            elif 'Controller' in tags:
2795
                opts = self.controllers[name]
2796

    
2797
                # Get controller info from panel
2798
                controllerType = opts['controllerType']
2799
                if 'controllerProtocol' in opts:
2800
                    controllerProtocol = opts['controllerProtocol']
2801
                else:
2802
                    controllerProtocol = 'tcp'
2803
                    opts['controllerProtocol'] = 'tcp'
2804
                controllerIP = opts['remoteIP']
2805
                controllerPort = opts['remotePort']
2806

    
2807
                # Make controller
2808
                print 'Getting controller selection:'+controllerType
2809
                if controllerType == 'remote':
2810
                    net.addController(name=name,
2811
                                      controller=RemoteController,
2812
                                      ip=controllerIP,
2813
                                      protocol=controllerProtocol,
2814
                                      port=controllerPort)
2815
                elif controllerType == 'inband':
2816
                    net.addController(name=name,
2817
                                      controller=InbandController,
2818
                                      ip=controllerIP,
2819
                                      protocol=controllerProtocol,
2820
                                      port=controllerPort)
2821
                elif controllerType == 'ovsc':
2822
                    net.addController(name=name,
2823
                                      controller=OVSController,
2824
                                      protocol=controllerProtocol,
2825
                                      port=controllerPort)
2826
                else:
2827
                    net.addController(name=name,
2828
                                      controller=Controller,
2829
                                      protocol=controllerProtocol,
2830
                                      port=controllerPort)
2831

    
2832
            else:
2833
                raise Exception( "Cannot create mystery node: " + name )
2834

    
2835
    def pathCheck( self, *args, **kwargs ):
2836
        "Make sure each program in *args can be found in $PATH."
2837
        moduleName = kwargs.get( 'moduleName', 'it' )
2838
        for arg in args:
2839
            if not quietRun( 'which ' + arg ):
2840
                showerror(title="Error",
2841
                      message= 'Cannot find required executable %s.\n' % arg +
2842
                       'Please make sure that %s is installed ' % moduleName +
2843
                       'and available in your $PATH.' )
2844

    
2845
    def buildLinks( self, net):
2846
        # Make links
2847
        print "Getting Links."
2848
        for key,link in self.links.iteritems():
2849
            tags = self.canvas.gettags(key)
2850
            if 'data' in tags:
2851
                src=link['src']
2852
                dst=link['dest']
2853
                linkopts=link['linkOpts']
2854
                srcName, dstName = src[ 'text' ], dst[ 'text' ]
2855
                srcNode, dstNode = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
2856
                if linkopts:
2857
                    net.addLink(srcNode, dstNode, cls=TCLink, **linkopts)
2858
                else:
2859
                    #print str(srcNode)
2860
                    #print str(dstNode)
2861
                    net.addLink(srcNode, dstNode)
2862
                self.canvas.itemconfig(key, dash=())
2863

    
2864

    
2865
    def build( self ):
2866
        print "Build network based on our topology."
2867

    
2868
        dpctl = None
2869
        if len(self.appPrefs['dpctl']) > 0:
2870
            dpctl = int(self.appPrefs['dpctl'])
2871
        net = Mininet( topo=None,
2872
                       listenPort=dpctl,
2873
                       build=False,
2874
                       ipBase=self.appPrefs['ipBase'] )
2875

    
2876
        self.buildNodes(net)
2877
        self.buildLinks(net)
2878

    
2879
        # Build network (we have to do this separately at the moment )
2880
        net.build()
2881

    
2882
        return net
2883

    
2884

    
2885
    def postStartSetup( self ):
2886

    
2887
        # Setup host details
2888
        for widget in self.widgetToItem:
2889
            name = widget[ 'text' ]
2890
            tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2891
            if 'Host' in tags:
2892
                newHost = self.net.get(name)
2893
                opts = self.hostOpts[name]
2894
                # Attach vlan interfaces
2895
                if ('vlanInterfaces' in opts):
2896
                    for vlanInterface in opts['vlanInterfaces']:
2897
                        print 'adding vlan interface '+vlanInterface[1]
2898
                        newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0])
2899
                # Run User Defined Start Command
2900
                if ('startCommand' in opts):
2901
                    newHost.cmdPrint(opts['startCommand'])
2902
            if 'Switch' in tags:
2903
                newNode = self.net.get(name)
2904
                opts = self.switchOpts[name]
2905
                # Run User Defined Start Command
2906
                if ('startCommand' in opts):
2907
                    newNode.cmdPrint(opts['startCommand'])
2908

    
2909

    
2910
        # Configure NetFlow
2911
        nflowValues = self.appPrefs['netflow']
2912
        if len(nflowValues['nflowTarget']) > 0:
2913
            nflowEnabled = False
2914
            nflowSwitches = ''
2915
            for widget in self.widgetToItem:
2916
                name = widget[ 'text' ]
2917
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2918
    
2919
                if 'Switch' in tags:
2920
                    opts = self.switchOpts[name]
2921
                    if 'netflow' in opts:
2922
                        if opts['netflow'] == '1':
2923
                            print name+' has Netflow enabled'
2924
                            nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF'
2925
                            nflowEnabled=True
2926
            if nflowEnabled:
2927
                nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout']
2928
                if nflowValues['nflowAddId'] == '1':
2929
                    nflowCmd = nflowCmd + ' add_id_to_interface=true'
2930
                else:
2931
                    nflowCmd = nflowCmd + ' add_id_to_interface=false'
2932
                print 'cmd = '+nflowCmd+nflowSwitches
2933
                call(nflowCmd+nflowSwitches, shell=True)
2934

    
2935
            else:
2936
                print 'No switches with Netflow'
2937
        else:
2938
            print 'No NetFlow targets specified.'
2939

    
2940
        # Configure sFlow
2941
        sflowValues = self.appPrefs['sflow']
2942
        if len(sflowValues['sflowTarget']) > 0:
2943
            sflowEnabled = False
2944
            sflowSwitches = ''
2945
            for widget in self.widgetToItem:
2946
                name = widget[ 'text' ]
2947
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2948
    
2949
                if 'Switch' in tags:
2950
                    opts = self.switchOpts[name]
2951
                    if 'sflow' in opts:
2952
                        if opts['sflow'] == '1':
2953
                            print name+' has sflow enabled'
2954
                            sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
2955
                            sflowEnabled=True
2956
            if sflowEnabled:
2957
                sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling']
2958
                print 'cmd = '+sflowCmd+sflowSwitches
2959
                call(sflowCmd+sflowSwitches, shell=True)
2960

    
2961
            else:
2962
                print 'No switches with sflow'
2963
        else:
2964
            print 'No sFlow targets specified.'
2965

    
2966
        ## NOTE: MAKE SURE THIS IS LAST THING CALLED
2967
        # Start the CLI if enabled
2968
        if self.appPrefs['startCLI'] == '1':
2969
            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")
2970
            CLI(self.net)
2971

    
2972
    def start( self ):
2973
        "Start network."
2974
        if self.net is None:
2975
            self.net = self.build()
2976

    
2977
            # Since I am going to inject per switch controllers.
2978
            # I can't call net.start().  I have to replicate what it
2979
            # does and add the controller options.
2980
            #self.net.start()
2981
            info( '**** Starting %s controllers\n' % len( self.net.controllers ) )
2982
            for controller in self.net.controllers:
2983
                info( str(controller) + ' ')
2984
                controller.start()
2985
            info('\n')
2986
            info( '**** Starting %s switches\n' % len( self.net.switches ) )
2987
            #for switch in self.net.switches:
2988
            #    info( switch.name + ' ')
2989
            #    switch.start( self.net.controllers )
2990
            for widget in self.widgetToItem:
2991
                name = widget[ 'text' ]
2992
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2993
                if 'Switch' in tags:
2994
                    opts = self.switchOpts[name]
2995
                    switchControllers = []
2996
                    for ctrl in opts['controllers']:
2997
                        switchControllers.append(self.net.get(ctrl))
2998
                    info( name + ' ')
2999
                    # Figure out what controllers will manage this switch
3000
                    self.net.get(name).start( switchControllers )
3001
                if 'LegacySwitch' in tags:
3002
                    self.net.get(name).start( [] )
3003
                    info( name + ' ')
3004
            info('\n')
3005

    
3006
            self.postStartSetup()
3007

    
3008
    def stop( self ):
3009
        "Stop network."
3010
        if self.net is not None:
3011
            # Stop host details
3012
            for widget in self.widgetToItem:
3013
                name = widget[ 'text' ]
3014
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
3015
                if 'Host' in tags:
3016
                    newHost = self.net.get(name)
3017
                    opts = self.hostOpts[name]
3018
                    # Run User Defined Stop Command
3019
                    if ('stopCommand' in opts):
3020
                        newHost.cmdPrint(opts['stopCommand'])
3021
                if 'Switch' in tags:
3022
                    newNode = self.net.get(name)
3023
                    opts = self.switchOpts[name]
3024
                    # Run User Defined Stop Command
3025
                    if ('stopCommand' in opts):
3026
                        newNode.cmdPrint(opts['stopCommand'])
3027

    
3028
            self.net.stop()
3029
        cleanUpScreens()
3030
        self.net = None
3031

    
3032
    def do_linkPopup(self, event):
3033
        # display the popup menu
3034
        if ( self.net is None ):
3035
            try:
3036
                self.linkPopup.tk_popup(event.x_root, event.y_root, 0)
3037
            finally:
3038
                # make sure to release the grab (Tk 8.0a1 only)
3039
                self.linkPopup.grab_release()
3040
        else:
3041
            try:
3042
                self.linkRunPopup.tk_popup(event.x_root, event.y_root, 0)
3043
            finally:
3044
                # make sure to release the grab (Tk 8.0a1 only)
3045
                self.linkRunPopup.grab_release()
3046

    
3047
    def do_controllerPopup(self, event):
3048
        # display the popup menu
3049
        if ( self.net is None ):
3050
            try:
3051
                self.controllerPopup.tk_popup(event.x_root, event.y_root, 0)
3052
            finally:
3053
                # make sure to release the grab (Tk 8.0a1 only)
3054
                self.controllerPopup.grab_release()
3055

    
3056
    def do_legacyRouterPopup(self, event):
3057
        # display the popup menu
3058
        if ( self.net is not None ):
3059
            try:
3060
                self.legacyRouterRunPopup.tk_popup(event.x_root, event.y_root, 0)
3061
            finally:
3062
                # make sure to release the grab (Tk 8.0a1 only)
3063
                self.legacyRouterRunPopup.grab_release()
3064

    
3065
    def do_hostPopup(self, event):
3066
        # display the popup menu
3067
        if ( self.net is None ):
3068
            try:
3069
                self.hostPopup.tk_popup(event.x_root, event.y_root, 0)
3070
            finally:
3071
                # make sure to release the grab (Tk 8.0a1 only)
3072
                self.hostPopup.grab_release()
3073
        else:
3074
            try:
3075
                self.hostRunPopup.tk_popup(event.x_root, event.y_root, 0)
3076
            finally:
3077
                # make sure to release the grab (Tk 8.0a1 only)
3078
                self.hostRunPopup.grab_release()
3079

    
3080
    def do_legacySwitchPopup(self, event):
3081
        # display the popup menu
3082
        if ( self.net is not None ):
3083
            try:
3084
                self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
3085
            finally:
3086
                # make sure to release the grab (Tk 8.0a1 only)
3087
                self.switchRunPopup.grab_release()
3088

    
3089
    def do_switchPopup(self, event):
3090
        # display the popup menu
3091
        if ( self.net is None ):
3092
            try:
3093
                self.switchPopup.tk_popup(event.x_root, event.y_root, 0)
3094
            finally:
3095
                # make sure to release the grab (Tk 8.0a1 only)
3096
                self.switchPopup.grab_release()
3097
        else:
3098
            try:
3099
                self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
3100
            finally:
3101
                # make sure to release the grab (Tk 8.0a1 only)
3102
                self.switchRunPopup.grab_release()
3103

    
3104
    def xterm( self, _ignore=None ):
3105
        "Make an xterm when a button is pressed."
3106
        if ( self.selection is None or
3107
             self.net is None or
3108
             self.selection not in self.itemToWidget ):
3109
            return
3110
        name = self.itemToWidget[ self.selection ][ 'text' ]
3111
        if name not in self.net.nameToNode:
3112
            return
3113
        term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] )
3114
        if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
3115
            self.net.terms += term
3116
        else:
3117
            self.net.terms.append(term)
3118

    
3119
    def iperf( self, _ignore=None ):
3120
        "Make an xterm when a button is pressed."
3121
        if ( self.selection is None or
3122
             self.net is None or
3123
             self.selection not in self.itemToWidget ):
3124
            return
3125
        name = self.itemToWidget[ self.selection ][ 'text' ]
3126
        if name not in self.net.nameToNode:
3127
            return
3128
        self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' )
3129

    
3130
    """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """
3131

    
3132
    def parseArgs( self ):
3133
        """Parse command-line args and return options object.
3134
           returns: opts parse options dict"""
3135

    
3136
        if '--custom' in sys.argv:
3137
            index = sys.argv.index( '--custom' )
3138
            if len( sys.argv ) > index + 1:
3139
                filename = sys.argv[ index + 1 ]
3140
                self.parseCustomFile( filename )
3141
            else:
3142
                raise Exception( 'Custom file name not found' )
3143

    
3144
        desc = ( "The %prog utility creates Mininet network from the\n"
3145
                 "command line. It can create parametrized topologies,\n"
3146
                 "invoke the Mininet CLI, and run tests." )
3147

    
3148
        usage = ( '%prog [options]\n'
3149
                  '(type %prog -h for details)' )
3150

    
3151
        opts = OptionParser( description=desc, usage=usage )
3152

    
3153
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
3154
        addDictOption( opts, LINKS, LINKDEF, 'link' )
3155

    
3156
        opts.add_option( '--custom', type='string', default=None,
3157
                         help='read custom topo and node params from .py' +
3158
                         'file' )
3159

    
3160
        self.options, self.args = opts.parse_args()
3161
        # We don't accept extra arguments after the options
3162
        if self.args:
3163
            opts.print_help()
3164
            exit()
3165

    
3166
    def setCustom( self, name, value ):
3167
        "Set custom parameters for MininetRunner."
3168
        if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
3169
            # Update dictionaries
3170
            param = name.upper()
3171
            globals()[ param ].update( value )
3172
        elif name == 'validate':
3173
            # Add custom validate function
3174
            self.validate = value
3175
        else:
3176
            # Add or modify global variable or class
3177
            globals()[ name ] = value
3178

    
3179
    def parseCustomFile( self, fileName ):
3180
        "Parse custom file and add params before parsing cmd-line options."
3181
        customs = {}
3182
        if os.path.isfile( fileName ):
3183
            execfile( fileName, customs, customs )
3184
            for name, val in customs.iteritems():
3185
                self.setCustom( name, val )
3186
        else:
3187
            raise Exception( 'could not find custom file: %s' % fileName )
3188

    
3189
    def importTopo( self ):
3190
        print 'topo='+self.options.topo
3191
        if self.options.topo == 'none':
3192
            return
3193
        self.newTopology()
3194
        topo = buildTopo( TOPOS, self.options.topo )
3195
        link = customConstructor( LINKS, self.options.link )
3196
        importNet = Mininet(topo=topo, build=False, link=link)
3197
        importNet.build()
3198

    
3199
        c = self.canvas
3200
        rowIncrement = 100
3201
        currentY = 100
3202

    
3203
        # Add Controllers
3204
        print 'controllers:'+str(len(importNet.controllers))
3205
        for controller in importNet.controllers:
3206
            name = controller.name
3207
            x = self.controllerCount*100+100
3208
            self.addNode('Controller', self.controllerCount,
3209
                 float(x), float(currentY), name=name)
3210
            icon = self.findWidgetByName(name)
3211
            icon.bind('<Button-3>', self.do_controllerPopup )
3212
            ctrlr = { 'controllerType': 'ref',
3213
                      'hostname': name,
3214
                      'controllerProtocol': controller.protocol,
3215
                      'remoteIP': controller.ip,
3216
                      'remotePort': controller.port}
3217
            self.controllers[name] = ctrlr
3218

    
3219

    
3220

    
3221
        currentY = currentY + rowIncrement
3222

    
3223
        # Add switches
3224
        print 'switches:'+str(len(importNet.switches))
3225
        columnCount = 0
3226
        for switch in importNet.switches:
3227
            name = switch.name
3228
            self.switchOpts[name] = {}
3229
            self.switchOpts[name]['nodeNum']=self.switchCount
3230
            self.switchOpts[name]['hostname']=name
3231
            self.switchOpts[name]['switchType']='default'
3232
            self.switchOpts[name]['controllers']=[]
3233

    
3234
            x = columnCount*100+100
3235
            self.addNode('Switch', self.switchCount,
3236
                 float(x), float(currentY), name=name)
3237
            icon = self.findWidgetByName(name)
3238
            icon.bind('<Button-3>', self.do_switchPopup )
3239
            # Now link to controllers
3240
            for controller in importNet.controllers:
3241
                self.switchOpts[name]['controllers'].append(controller.name)
3242
                dest = self.findWidgetByName(controller.name)
3243
                dx, dy = c.coords( self.widgetToItem[ dest ] )
3244
                self.link = c.create_line(float(x),
3245
                                          float(currentY),
3246
                                          dx,
3247
                                          dy,
3248
                                          width=4,
3249
                                          fill='red',
3250
                                          dash=(6, 4, 2, 4),
3251
                                          tag='link' )
3252
                c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
3253
                self.addLink( icon, dest, linktype='control' )
3254
                self.createControlLinkBindings()
3255
                self.link = self.linkWidget = None
3256
            if columnCount == 9:
3257
                columnCount = 0
3258
                currentY = currentY + rowIncrement
3259
            else:
3260
                columnCount =columnCount+1
3261

    
3262

    
3263
        currentY = currentY + rowIncrement
3264
        # Add hosts
3265
        print 'hosts:'+str(len(importNet.hosts))
3266
        columnCount = 0
3267
        for host in importNet.hosts:
3268
            name = host.name
3269
            self.hostOpts[name] = {'sched':'host'}
3270
            self.hostOpts[name]['nodeNum']=self.hostCount
3271
            self.hostOpts[name]['hostname']=name
3272
            self.hostOpts[name]['ip']=host.IP()
3273

    
3274
            x = columnCount*100+100
3275
            self.addNode('Host', self.hostCount,
3276
                 float(x), float(currentY), name=name)
3277
            icon = self.findWidgetByName(name)
3278
            icon.bind('<Button-3>', self.do_hostPopup )
3279
            if columnCount == 9:
3280
                columnCount = 0
3281
                currentY = currentY + rowIncrement
3282
            else:
3283
                columnCount =columnCount+1
3284

    
3285
        print 'links:'+str(len(topo.links()))
3286
        #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')]
3287
        for link in topo.links():
3288
            print str(link)
3289
            srcNode = link[0]
3290
            src = self.findWidgetByName(srcNode)
3291
            sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
3292

    
3293
            destNode = link[1]
3294
            dest = self.findWidgetByName(destNode)
3295
            dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
3296

    
3297
            params = topo.linkInfo( srcNode, destNode )
3298
            print 'Link Parameters='+str(params)
3299

    
3300
            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
3301
                                             fill='blue', tag='link' )
3302
            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
3303
            self.addLink( src, dest, linkopts=params )
3304
            self.createDataLinkBindings()
3305
            self.link = self.linkWidget = None
3306

    
3307
        importNet.stop()
3308

    
3309
def miniEditImages():
3310
    "Create and return images for MiniEdit."
3311

    
3312
    # Image data. Git will be unhappy. However, the alternative
3313
    # is to keep track of separate binary files, which is also
3314
    # unappealing.
3315

    
3316
    return {
3317
        'Select': BitmapImage(
3318
            file='/usr/include/X11/bitmaps/left_ptr' ),
3319

    
3320
        'Switch': PhotoImage( data=r"""
3321
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
3352
            """),
3353

    
3354
        'LegacySwitch': PhotoImage( data=r"""
3355
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
3386
4BE2eIRYeHAEIBwBP0Y4Qn41YWRSCQgAOw==
3387
            """),
3388

    
3389
        'LegacyRouter': PhotoImage( data=r"""
3390
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
3419
gGPLHwLwcMIo12Qxu0ABAQA7
3420
            """),
3421

    
3422
        'Controller': PhotoImage( data=r"""
3423
            R0lGODlhMAAwAPcAAAEBAWfNAYWFhcfHx+3t6/f390lJUaWlpfPz8/Hx72lpaZGRke/v77m5uc0B
3424
            AeHh4e/v7WNjY3t7e5eXlyMjI4mJidPT0+3t7f///09PT7Ozs/X19fHx8ZWTk8HBwX9/fwAAAAAA
3425
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3426
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3427
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3428
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3429
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3430
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3431
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3432
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3433
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3434
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3435
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3436
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAwADAA
3437
            Bwj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAEIeMCxo8ePHwVkBGABg8mTKFOmtDByAIYN
3438
            MGPCRCCzQIENNzEMGOkBAwIKQIMKpYCgKAIHCDB4GNkAA4OnUJ9++CDhQ1QGFzA0GKkBA4GvYMOK
3439
            BYtBA1cNaNOqXcuWq8q3b81m7Cqzbk2bMMu6/Tl0qFEEAZLKxdj1KlSqVA3rnet1rOOwiwmznUzZ
3440
            LdzLJgdfpIv3pmebN2Pm1GyRbocNp1PLNMDaAM3Im1/alQk4gO28pCt2RdCBt+/eRg8IP1AUdmmf
3441
            f5MrL56bYlcOvaP7Xo6Ag3HdGDho3869u/YE1507t+3AgLz58ujPMwg/sTBUCAzgy49PH0LW5u0x
3442
            XFiwvz////5dcJ9bjxVIAHsSdUXAAgs2yOCDDn6FYEQaFGDgYxNCpEFfHHKIX4IDhCjiiCSS+CGF
3443
            FlCmogYpcnVABTDGKGOMAlRQYwUHnKjhAjX2aOOPN8LImgAL6PiQBhLMqCSNAThQgQRGOqRBBD1W
3444
            aaOVAggnQARRNqRBBxmEKeaYZIrZQZcMKbDiigqM5OabcMYp55x01ilnQAA7
3445
            """),
3446

    
3447
        'Host': PhotoImage( data=r"""
3448
            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
3449
            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
3450
            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
3451
            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
3452
            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
3453
            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
3454
            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
3455
            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
3456
            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
3457
            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
3458
            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
3459
            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
3460
            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
3461
            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
3462
            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
3463
            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
3464
            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
3465
            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
3466
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
3467
            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
3468
            ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw
3469
            BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2
3470
            HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO
3471
            p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/
3472
            C8cSBBAQADs=
3473
        """ ),
3474

    
3475
        'OldSwitch': PhotoImage( data=r"""
3476
            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
3477
            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
3478
            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
3479
            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
3480
            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
3481
            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
3482
            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
3483
            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
3484
            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
3485
            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
3486
            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
3487