Statistics
| Branch: | Tag: | Revision:

mininet / examples / miniedit.py @ 8d493b68

History | View | Annotate | Download (153 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, HostWithPrivateDirs
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 CPULimitedHostWithPrivateDirs( CPULimitedHost ):
82
    "Host with private directories"
83

    
84
    def __init__( self, name, sched='cfs', **kwargs ):
85
        "privateDirs: list of private directory strings or tuples"
86
        self.name = name
87
        self.privateDirs = kwargs.pop( 'privateDirs', [] )
88
        CPULimitedHost.__init__( self, name, sched, **kwargs )
89
        self.mountPrivateDirs()
90

    
91
    def mountPrivateDirs( self ):
92
        "mount private directories"
93
        for directory in self.privateDirs:
94
            if isinstance( directory, tuple ):
95
                # mount given private directory
96
                privateDir = directory[ 1 ] % self.__dict__
97
                mountPoint = directory[ 0 ]
98
                self.cmd( 'mkdir -p %s' % privateDir )
99
                self.cmd( 'mkdir -p %s' % mountPoint )
100
                self.cmd( 'mount --bind %s %s' %
101
                               ( privateDir, mountPoint ) )
102
            else:
103
                # mount temporary filesystem on directory
104
                self.cmd( 'mkdir -p %s' % directory )
105
                self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
106

    
107
class InbandController( RemoteController ):
108

    
109
    def checkListening( self ):
110
        "Overridden to do nothing."
111
        return
112

    
113
class CustomUserSwitch(UserSwitch):
114
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
115
        UserSwitch.__init__( self, name, **kwargs )
116
        self.switchIP = None
117

    
118
    def getSwitchIP(self):
119
        return self.switchIP
120

    
121
    def setSwitchIP(self, ip):
122
        self.switchIP = ip
123

    
124
    def start( self, controllers ):
125
        # Call superclass constructor
126
        UserSwitch.start( self, controllers )
127
        # Set Switch IP address
128
        if (self.switchIP is not None):
129
            if not self.inNamespace:
130
                self.cmd( 'ifconfig', self, self.switchIP )
131
            else:
132
                self.cmd( 'ifconfig lo', self.switchIP )
133

    
134
class LegacyRouter( Node ):
135

    
136
    def __init__( self, name, inNamespace=True, **params ):
137
        Node.__init__( self, name, inNamespace, **params )
138

    
139
    def config( self, **_params ):
140
        if self.intfs:
141
            self.setParam( _params, 'setIP', ip='0.0.0.0' )
142
        r = Node.config( self, **_params )
143
        self.cmd('sysctl -w net.ipv4.ip_forward=1')
144
        return r
145

    
146
class LegacySwitch(OVSSwitch):
147

    
148
    def __init__( self, name, **params ):
149
        OVSSwitch.__init__( self, name, failMode='standalone', **params )
150
        self.switchIP = None
151

    
152
class customOvs(OVSSwitch):
153

    
154
    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
155
        OVSSwitch.__init__( self, name, failMode=failMode, datapath=datapath, **params )
156
        self.switchIP = None
157

    
158
    def getSwitchIP(self):
159
        return self.switchIP
160

    
161
    def setSwitchIP(self, ip):
162
        self.switchIP = ip
163

    
164
    def start( self, controllers ):
165
        # Call superclass constructor
166
        OVSSwitch.start( self, controllers )
167
        # Set Switch IP address
168
        if (self.switchIP is not None):
169
            self.cmd( 'ifconfig', self, self.switchIP )
170

    
171
class PrefsDialog(tkSimpleDialog.Dialog):
172

    
173
        def __init__(self, parent, title, prefDefaults):
174

    
175
            self.prefValues = prefDefaults
176

    
177
            tkSimpleDialog.Dialog.__init__(self, parent, title)
178

    
179
        def body(self, master):
180
            self.rootFrame = master
181
            self.leftfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
182
            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2)
183
            self.rightfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
184
            self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2)
185

    
186

    
187
            # Field for Base IP
188
            Label(self.leftfieldFrame, text="IP Base:").grid(row=0, sticky=E)
189
            self.ipEntry = Entry(self.leftfieldFrame)
190
            self.ipEntry.grid(row=0, column=1)
191
            ipBase =  self.prefValues['ipBase']
192
            self.ipEntry.insert(0, ipBase)
193

    
194
            # Selection of terminal type
195
            Label(self.leftfieldFrame, text="Default Terminal:").grid(row=1, sticky=E)
196
            self.terminalVar = StringVar(self.leftfieldFrame)
197
            self.terminalOption = OptionMenu(self.leftfieldFrame, self.terminalVar, "xterm", "gterm")
198
            self.terminalOption.grid(row=1, column=1, sticky=W)
199
            terminalType = self.prefValues['terminalType']
200
            self.terminalVar.set(terminalType)
201

    
202
            # Field for CLI
203
            Label(self.leftfieldFrame, text="Start CLI:").grid(row=2, sticky=E)
204
            self.cliStart = IntVar()
205
            self.cliButton = Checkbutton(self.leftfieldFrame, variable=self.cliStart)
206
            self.cliButton.grid(row=2, column=1, sticky=W)
207
            if self.prefValues['startCLI'] == '0':
208
                self.cliButton.deselect()
209
            else:
210
                self.cliButton.select()
211

    
212
            # Selection of switch type
213
            Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E)
214
            self.switchType = StringVar(self.leftfieldFrame)
215
            self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace")
216
            self.switchTypeMenu.grid(row=3, column=1, sticky=W)
217
            switchTypePref = self.prefValues['switchType']
218
            if switchTypePref == 'ivs':
219
                self.switchType.set("Indigo Virtual Switch")
220
            elif switchTypePref == 'userns':
221
                self.switchType.set("Userspace Switch inNamespace")
222
            elif switchTypePref == 'user':
223
                self.switchType.set("Userspace Switch")
224
            else:
225
                self.switchType.set("Open vSwitch Kernel Mode")
226

    
227

    
228
            # Fields for OVS OpenFlow version
229
            ovsFrame= LabelFrame(self.leftfieldFrame, text='Open vSwitch', padx=5, pady=5)
230
            ovsFrame.grid(row=4, column=0, columnspan=2, sticky=EW)
231
            Label(ovsFrame, text="OpenFlow 1.0:").grid(row=0, sticky=E)
232
            Label(ovsFrame, text="OpenFlow 1.1:").grid(row=1, sticky=E)
233
            Label(ovsFrame, text="OpenFlow 1.2:").grid(row=2, sticky=E)
234
            Label(ovsFrame, text="OpenFlow 1.3:").grid(row=3, sticky=E)
235

    
236
            self.ovsOf10 = IntVar()
237
            self.covsOf10 = Checkbutton(ovsFrame, variable=self.ovsOf10)
238
            self.covsOf10.grid(row=0, column=1, sticky=W)
239
            if self.prefValues['openFlowVersions']['ovsOf10'] == '0':
240
                self.covsOf10.deselect()
241
            else:
242
                self.covsOf10.select()
243

    
244
            self.ovsOf11 = IntVar()
245
            self.covsOf11 = Checkbutton(ovsFrame, variable=self.ovsOf11)
246
            self.covsOf11.grid(row=1, column=1, sticky=W)
247
            if self.prefValues['openFlowVersions']['ovsOf11'] == '0':
248
                self.covsOf11.deselect()
249
            else:
250
                self.covsOf11.select()
251

    
252
            self.ovsOf12 = IntVar()
253
            self.covsOf12 = Checkbutton(ovsFrame, variable=self.ovsOf12)
254
            self.covsOf12.grid(row=2, column=1, sticky=W)
255
            if self.prefValues['openFlowVersions']['ovsOf12'] == '0':
256
                self.covsOf12.deselect()
257
            else:
258
                self.covsOf12.select()
259

    
260
            self.ovsOf13 = IntVar()
261
            self.covsOf13 = Checkbutton(ovsFrame, variable=self.ovsOf13)
262
            self.covsOf13.grid(row=3, column=1, sticky=W)
263
            if self.prefValues['openFlowVersions']['ovsOf13'] == '0':
264
                self.covsOf13.deselect()
265
            else:
266
                self.covsOf13.select()
267

    
268
            # Field for DPCTL listen port
269
            Label(self.leftfieldFrame, text="dpctl port:").grid(row=5, sticky=E)
270
            self.dpctlEntry = Entry(self.leftfieldFrame)
271
            self.dpctlEntry.grid(row=5, column=1)
272
            if 'dpctl' in self.prefValues:
273
                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
274

    
275
            # sFlow
276
            sflowValues = self.prefValues['sflow']
277
            self.sflowFrame= LabelFrame(self.rightfieldFrame, text='sFlow Profile for Open vSwitch', padx=5, pady=5)
278
            self.sflowFrame.grid(row=0, column=0, columnspan=2, sticky=EW)
279

    
280
            Label(self.sflowFrame, text="Target:").grid(row=0, sticky=E)
281
            self.sflowTarget = Entry(self.sflowFrame)
282
            self.sflowTarget.grid(row=0, column=1)
283
            self.sflowTarget.insert(0, sflowValues['sflowTarget'])
284

    
285
            Label(self.sflowFrame, text="Sampling:").grid(row=1, sticky=E)
286
            self.sflowSampling = Entry(self.sflowFrame)
287
            self.sflowSampling.grid(row=1, column=1)
288
            self.sflowSampling.insert(0, sflowValues['sflowSampling'])
289

    
290
            Label(self.sflowFrame, text="Header:").grid(row=2, sticky=E)
291
            self.sflowHeader = Entry(self.sflowFrame)
292
            self.sflowHeader.grid(row=2, column=1)
293
            self.sflowHeader.insert(0, sflowValues['sflowHeader'])
294

    
295
            Label(self.sflowFrame, text="Polling:").grid(row=3, sticky=E)
296
            self.sflowPolling = Entry(self.sflowFrame)
297
            self.sflowPolling.grid(row=3, column=1)
298
            self.sflowPolling.insert(0, sflowValues['sflowPolling'])
299

    
300
            # NetFlow
301
            nflowValues = self.prefValues['netflow']
302
            self.nFrame= LabelFrame(self.rightfieldFrame, text='NetFlow Profile for Open vSwitch', padx=5, pady=5)
303
            self.nFrame.grid(row=1, column=0, columnspan=2, sticky=EW)
304

    
305
            Label(self.nFrame, text="Target:").grid(row=0, sticky=E)
306
            self.nflowTarget = Entry(self.nFrame)
307
            self.nflowTarget.grid(row=0, column=1)
308
            self.nflowTarget.insert(0, nflowValues['nflowTarget'])
309

    
310
            Label(self.nFrame, text="Active Timeout:").grid(row=1, sticky=E)
311
            self.nflowTimeout = Entry(self.nFrame)
312
            self.nflowTimeout.grid(row=1, column=1)
313
            self.nflowTimeout.insert(0, nflowValues['nflowTimeout'])
314

    
315
            Label(self.nFrame, text="Add ID to Interface:").grid(row=2, sticky=E)
316
            self.nflowAddId = IntVar()
317
            self.nflowAddIdButton = Checkbutton(self.nFrame, variable=self.nflowAddId)
318
            self.nflowAddIdButton.grid(row=2, column=1, sticky=W)
319
            if nflowValues['nflowAddId'] == '0':
320
                self.nflowAddIdButton.deselect()
321
            else:
322
                self.nflowAddIdButton.select()
323

    
324
            # initial focus
325
            return self.ipEntry
326

    
327
        def apply(self):
328
            ipBase = self.ipEntry.get()
329
            terminalType = self.terminalVar.get()
330
            startCLI = str(self.cliStart.get())
331
            sw = self.switchType.get()
332
            dpctl = self.dpctlEntry.get()
333

    
334
            ovsOf10 = str(self.ovsOf10.get())
335
            ovsOf11 = str(self.ovsOf11.get())
336
            ovsOf12 = str(self.ovsOf12.get())
337
            ovsOf13 = str(self.ovsOf13.get())
338

    
339
            sflowValues = {'sflowTarget':self.sflowTarget.get(),
340
                           'sflowSampling':self.sflowSampling.get(),
341
                           'sflowHeader':self.sflowHeader.get(),
342
                           'sflowPolling':self.sflowPolling.get()}
343
            nflowvalues = {'nflowTarget':self.nflowTarget.get(),
344
                           'nflowTimeout':self.nflowTimeout.get(),
345
                           'nflowAddId':str(self.nflowAddId.get())}
346
            self.result = {'ipBase':ipBase,
347
                           'terminalType':terminalType,
348
                           'dpctl':dpctl,
349
                           'sflow':sflowValues,
350
                           'netflow':nflowvalues,
351
                           'startCLI':startCLI}
352
            if sw == 'Indigo Virtual Switch':
353
                self.result['switchType'] = 'ivs'
354
                if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
355
                    self.ovsOk = False
356
                    showerror(title="Error",
357
                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
358
            elif sw == 'Userspace Switch':
359
                self.result['switchType'] = 'user'
360
            elif sw == 'Userspace Switch inNamespace':
361
                self.result['switchType'] = 'userns'
362
            else:
363
                self.result['switchType'] = 'ovs'
364

    
365
            self.ovsOk = True
366
            if ovsOf11 == "1":
367
                ovsVer = self.getOvsVersion()
368
                if StrictVersion(ovsVer) < StrictVersion('2.0'):
369
                    self.ovsOk = False
370
                    showerror(title="Error",
371
                              message='Open vSwitch version 2.0+ required. You have '+ovsVer+'.')
372
            if ovsOf12 == "1" or ovsOf13 == "1":
373
                ovsVer = self.getOvsVersion()
374
                if StrictVersion(ovsVer) < StrictVersion('1.10'):
375
                    self.ovsOk = False
376
                    showerror(title="Error",
377
                              message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.')
378

    
379
            if self.ovsOk:
380
                self.result['openFlowVersions']={'ovsOf10':ovsOf10,
381
                                                 'ovsOf11':ovsOf11,
382
                                                 'ovsOf12':ovsOf12,
383
                                                 'ovsOf13':ovsOf13}
384
            else:
385
                self.result = None
386

    
387
        def getOvsVersion(self):
388
            outp = quietRun("ovs-vsctl show")
389
            r = r'ovs_version: "(.*)"'
390
            m = re.search(r, outp)
391
            if m is None:
392
                print 'Version check failed'
393
                return None
394
            else:
395
                print 'Open vSwitch version is '+m.group(1)
396
                return m.group(1)
397

    
398

    
399
class CustomDialog(object):
400

    
401
        # TODO: Fix button placement and Title and window focus lock
402
        def __init__(self, master, title):
403
            self.top=Toplevel(master)
404

    
405
            self.bodyFrame = Frame(self.top)
406
            self.bodyFrame.grid(row=0, column=0, sticky='nswe')
407
            self.body(self.bodyFrame)
408

    
409
            #return self.b # initial focus
410
            buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey')
411
            buttonFrame.grid(row=1 , column=0, sticky='nswe')
412

    
413
            okButton = Button(buttonFrame, width=8, text='OK', relief='groove',
414
                       bd=4, command=self.okAction)
415
            okButton.grid(row=0, column=0, sticky=E)
416

    
417
            canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove',
418
                        bd=4, command=self.cancelAction)
419
            canlceButton.grid(row=0, column=1, sticky=W)
420

    
421
        def body(self, master):
422
            self.rootFrame = master
423

    
424
        def apply(self):
425
            self.top.destroy()
426

    
427
        def cancelAction(self):
428
            self.top.destroy()
429

    
430
        def okAction(self):
431
            self.apply()
432
            self.top.destroy()
433

    
434
class HostDialog(CustomDialog):
435

    
436
        def __init__(self, master, title, prefDefaults):
437

    
438
            self.prefValues = prefDefaults
439
            self.result = None
440

    
441
            CustomDialog.__init__(self, master, title)
442

    
443
        def body(self, master):
444
            self.rootFrame = master
445
            n = Notebook(self.rootFrame)
446
            self.propFrame = Frame(n)
447
            self.vlanFrame = Frame(n)
448
            self.interfaceFrame = Frame(n)
449
            self.mountFrame = Frame(n)
450
            n.add(self.propFrame, text='Properties')
451
            n.add(self.vlanFrame, text='VLAN Interfaces')
452
            n.add(self.interfaceFrame, text='External Interfaces')
453
            n.add(self.mountFrame, text='Private Directories')
454
            n.pack()
455

    
456
            ### TAB 1
457
            # Field for Hostname
458
            Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E)
459
            self.hostnameEntry = Entry(self.propFrame)
460
            self.hostnameEntry.grid(row=0, column=1)
461
            if 'hostname' in self.prefValues:
462
                self.hostnameEntry.insert(0, self.prefValues['hostname'])
463

    
464
            # Field for Switch IP
465
            Label(self.propFrame, text="IP Address:").grid(row=1, sticky=E)
466
            self.ipEntry = Entry(self.propFrame)
467
            self.ipEntry.grid(row=1, column=1)
468
            if 'ip' in self.prefValues:
469
                self.ipEntry.insert(0, self.prefValues['ip'])
470

    
471
            # Field for default route
472
            Label(self.propFrame, text="Default Route:").grid(row=2, sticky=E)
473
            self.routeEntry = Entry(self.propFrame)
474
            self.routeEntry.grid(row=2, column=1)
475
            if 'defaultRoute' in self.prefValues:
476
                self.routeEntry.insert(0, self.prefValues['defaultRoute'])
477

    
478
            # Field for CPU
479
            Label(self.propFrame, text="Amount CPU:").grid(row=3, sticky=E)
480
            self.cpuEntry = Entry(self.propFrame)
481
            self.cpuEntry.grid(row=3, column=1)
482
            if 'cpu' in self.prefValues:
483
                self.cpuEntry.insert(0, str(self.prefValues['cpu']))
484
            # Selection of Scheduler
485
            if 'sched' in self.prefValues:
486
                sched =  self.prefValues['sched']
487
            else:
488
                sched = 'host'
489
            self.schedVar = StringVar(self.propFrame)
490
            self.schedOption = OptionMenu(self.propFrame, self.schedVar, "host", "cfs", "rt")
491
            self.schedOption.grid(row=3, column=2, sticky=W)
492
            self.schedVar.set(sched)
493

    
494
            # Selection of Cores
495
            Label(self.propFrame, text="Cores:").grid(row=4, sticky=E)
496
            self.coreEntry = Entry(self.propFrame)
497
            self.coreEntry.grid(row=4, column=1)
498
            if 'cores' in self.prefValues:
499
                self.coreEntry.insert(1, self.prefValues['cores'])
500

    
501
            # Start command
502
            Label(self.propFrame, text="Start Command:").grid(row=5, sticky=E)
503
            self.startEntry = Entry(self.propFrame)
504
            self.startEntry.grid(row=5, column=1, sticky='nswe', columnspan=3)
505
            if 'startCommand' in self.prefValues:
506
                self.startEntry.insert(0, str(self.prefValues['startCommand']))
507
            # Stop command
508
            Label(self.propFrame, text="Stop Command:").grid(row=6, sticky=E)
509
            self.stopEntry = Entry(self.propFrame)
510
            self.stopEntry.grid(row=6, column=1, sticky='nswe', columnspan=3)
511
            if 'stopCommand' in self.prefValues:
512
                self.stopEntry.insert(0, str(self.prefValues['stopCommand']))
513

    
514
            ### TAB 2
515
            # External Interfaces
516
            self.externalInterfaces = 0
517
            Label(self.interfaceFrame, text="External Interface:").grid(row=0, column=0, sticky=E)
518
            self.b = Button( self.interfaceFrame, text='Add', command=self.addInterface)
519
            self.b.grid(row=0, column=1)
520

    
521
            self.interfaceFrame = VerticalScrolledTable(self.interfaceFrame, rows=0, columns=1, title='External Interfaces')
522
            self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
523
            self.tableFrame = self.interfaceFrame.interior
524
            self.tableFrame.addRow(value=['Interface Name'], readonly=True)
525

    
526
            # Add defined interfaces
527
            externalInterfaces = []
528
            if 'externalInterfaces' in self.prefValues:
529
                externalInterfaces = self.prefValues['externalInterfaces']
530

    
531
            for externalInterface in externalInterfaces:
532
                self.tableFrame.addRow(value=[externalInterface])
533

    
534
            ### TAB 3
535
            # VLAN Interfaces
536
            self.vlanInterfaces = 0
537
            Label(self.vlanFrame, text="VLAN Interface:").grid(row=0, column=0, sticky=E)
538
            self.vlanButton = Button( self.vlanFrame, text='Add', command=self.addVlanInterface)
539
            self.vlanButton.grid(row=0, column=1)
540

    
541
            self.vlanFrame = VerticalScrolledTable(self.vlanFrame, rows=0, columns=2, title='VLAN Interfaces')
542
            self.vlanFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
543
            self.vlanTableFrame = self.vlanFrame.interior
544
            self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True)
545

    
546
            vlanInterfaces = []
547
            if 'vlanInterfaces' in self.prefValues:
548
                vlanInterfaces = self.prefValues['vlanInterfaces']
549
            for vlanInterface in vlanInterfaces:
550
                self.vlanTableFrame.addRow(value=vlanInterface)
551

    
552
            ### TAB 4
553
            # Private Directories
554
            self.privateDirectories = 0
555
            Label(self.mountFrame, text="Private Directory:").grid(row=0, column=0, sticky=E)
556
            self.mountButton = Button( self.mountFrame, text='Add', command=self.addDirectory)
557
            self.mountButton.grid(row=0, column=1)
558

    
559
            self.mountFrame = VerticalScrolledTable(self.mountFrame, rows=0, columns=2, title='Directories')
560
            self.mountFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
561
            self.mountTableFrame = self.mountFrame.interior
562
            self.mountTableFrame.addRow(value=['Mount','Persistent Directory'], readonly=True)
563

    
564
            directoryList = []
565
            if 'privateDirectory' in self.prefValues:
566
                directoryList = self.prefValues['privateDirectory']
567
            for privateDir in directoryList:
568
                if isinstance( privateDir, tuple ):
569
                    self.mountTableFrame.addRow(value=privateDir)
570
                else:
571
                    self.mountTableFrame.addRow(value=[privateDir,''])
572

    
573

    
574
        def addDirectory( self ):
575
            self.mountTableFrame.addRow()
576

    
577
        def addVlanInterface( self ):
578
            self.vlanTableFrame.addRow()
579

    
580
        def addInterface( self ):
581
            self.tableFrame.addRow()
582

    
583
        def apply(self):
584
            externalInterfaces = []
585
            for row in range(self.tableFrame.rows):
586
                if (len(self.tableFrame.get(row, 0)) > 0 and
587
                    row > 0):
588
                    externalInterfaces.append(self.tableFrame.get(row, 0))
589
            vlanInterfaces = []
590
            for row in range(self.vlanTableFrame.rows):
591
                if (len(self.vlanTableFrame.get(row, 0)) > 0 and
592
                    len(self.vlanTableFrame.get(row, 1)) > 0 and
593
                    row > 0):
594
                    vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)])
595
            privateDirectories = []
596
            for row in range(self.mountTableFrame.rows):
597
                if (len(self.mountTableFrame.get(row, 0)) > 0 and row > 0):
598
                    if(len(self.mountTableFrame.get(row, 1)) > 0):
599
                        privateDirectories.append((self.mountTableFrame.get(row, 0), self.mountTableFrame.get(row, 1)))
600
                    else:
601
                        privateDirectories.append(self.mountTableFrame.get(row, 0))
602

    
603
            results = {'cpu': self.cpuEntry.get(),
604
                       'cores':self.coreEntry.get(),
605
                       'sched':self.schedVar.get(),
606
                       'hostname':self.hostnameEntry.get(),
607
                       'ip':self.ipEntry.get(),
608
                       'defaultRoute':self.routeEntry.get(),
609
                       'startCommand':self.startEntry.get(),
610
                       'stopCommand':self.stopEntry.get(),
611
                       'privateDirectory':privateDirectories,
612
                       'externalInterfaces':externalInterfaces,
613
                       'vlanInterfaces':vlanInterfaces}
614
            self.result = results
615

    
616
class SwitchDialog(CustomDialog):
617

    
618
        def __init__(self, master, title, prefDefaults):
619

    
620
            self.prefValues = prefDefaults
621
            self.result = None
622
            CustomDialog.__init__(self, master, title)
623

    
624
        def body(self, master):
625
            self.rootFrame = master
626
            self.leftfieldFrame = Frame(self.rootFrame)
627
            self.rightfieldFrame = Frame(self.rootFrame)
628
            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe')
629
            self.rightfieldFrame.grid(row=0, column=1, sticky='nswe')
630

    
631
            rowCount = 0
632
            externalInterfaces = []
633
            if 'externalInterfaces' in self.prefValues:
634
                externalInterfaces = self.prefValues['externalInterfaces']
635

    
636
            # Field for Hostname
637
            Label(self.leftfieldFrame, text="Hostname:").grid(row=rowCount, sticky=E)
638
            self.hostnameEntry = Entry(self.leftfieldFrame)
639
            self.hostnameEntry.grid(row=rowCount, column=1)
640
            self.hostnameEntry.insert(0, self.prefValues['hostname'])
641
            rowCount+=1
642

    
643
            # Field for DPID
644
            Label(self.leftfieldFrame, text="DPID:").grid(row=rowCount, sticky=E)
645
            self.dpidEntry = Entry(self.leftfieldFrame)
646
            self.dpidEntry.grid(row=rowCount, column=1)
647
            if 'dpid' in self.prefValues:
648
                self.dpidEntry.insert(0, self.prefValues['dpid'])
649
            rowCount+=1
650

    
651
            # Field for Netflow
652
            Label(self.leftfieldFrame, text="Enable NetFlow:").grid(row=rowCount, sticky=E)
653
            self.nflow = IntVar()
654
            self.nflowButton = Checkbutton(self.leftfieldFrame, variable=self.nflow)
655
            self.nflowButton.grid(row=rowCount, column=1, sticky=W)
656
            if 'netflow' in self.prefValues:
657
                if self.prefValues['netflow'] == '0':
658
                    self.nflowButton.deselect()
659
                else:
660
                    self.nflowButton.select()
661
            else:
662
                self.nflowButton.deselect()
663
            rowCount+=1
664

    
665
            # Field for sflow
666
            Label(self.leftfieldFrame, text="Enable sFlow:").grid(row=rowCount, sticky=E)
667
            self.sflow = IntVar()
668
            self.sflowButton = Checkbutton(self.leftfieldFrame, variable=self.sflow)
669
            self.sflowButton.grid(row=rowCount, column=1, sticky=W)
670
            if 'sflow' in self.prefValues:
671
                if self.prefValues['sflow'] == '0':
672
                    self.sflowButton.deselect()
673
                else:
674
                    self.sflowButton.select()
675
            else:
676
                self.sflowButton.deselect()
677
            rowCount+=1
678

    
679
            # Selection of switch type
680
            Label(self.leftfieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E)
681
            self.switchType = StringVar(self.leftfieldFrame)
682
            self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Default", "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace")
683
            self.switchTypeMenu.grid(row=rowCount, column=1, sticky=W)
684
            if 'switchType' in self.prefValues:
685
                switchTypePref = self.prefValues['switchType']
686
                if switchTypePref == 'ivs':
687
                    self.switchType.set("Indigo Virtual Switch")
688
                elif switchTypePref == 'userns':
689
                    self.switchType.set("Userspace Switch inNamespace")
690
                elif switchTypePref == 'user':
691
                    self.switchType.set("Userspace Switch")
692
                elif switchTypePref == 'ovs':
693
                    self.switchType.set("Open vSwitch Kernel Mode")
694
                else:
695
                    self.switchType.set("Default")
696
            else:
697
                self.switchType.set("Default")
698
            rowCount+=1
699

    
700
            # Field for Switch IP
701
            Label(self.leftfieldFrame, text="IP Address:").grid(row=rowCount, sticky=E)
702
            self.ipEntry = Entry(self.leftfieldFrame)
703
            self.ipEntry.grid(row=rowCount, column=1)
704
            if 'switchIP' in self.prefValues:
705
                self.ipEntry.insert(0, self.prefValues['switchIP'])
706
            rowCount+=1
707

    
708
            # Field for DPCTL port
709
            Label(self.leftfieldFrame, text="DPCTL port:").grid(row=rowCount, sticky=E)
710
            self.dpctlEntry = Entry(self.leftfieldFrame)
711
            self.dpctlEntry.grid(row=rowCount, column=1)
712
            if 'dpctl' in self.prefValues:
713
                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
714
            rowCount+=1
715

    
716
            # External Interfaces
717
            Label(self.rightfieldFrame, text="External Interface:").grid(row=0, sticky=E)
718
            self.b = Button( self.rightfieldFrame, text='Add', command=self.addInterface)
719
            self.b.grid(row=0, column=1)
720

    
721
            self.interfaceFrame = VerticalScrolledTable(self.rightfieldFrame, rows=0, columns=1, title='External Interfaces')
722
            self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
723
            self.tableFrame = self.interfaceFrame.interior
724

    
725
            # Add defined interfaces
726
            for externalInterface in externalInterfaces:
727
                self.tableFrame.addRow(value=[externalInterface])
728

    
729
            self.commandFrame = Frame(self.rootFrame)
730
            self.commandFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
731
            self.commandFrame.columnconfigure(1, weight=1)
732
            # Start command
733
            Label(self.commandFrame, text="Start Command:").grid(row=0, column=0, sticky=W)
734
            self.startEntry = Entry(self.commandFrame)
735
            self.startEntry.grid(row=0, column=1,  sticky='nsew')
736
            if 'startCommand' in self.prefValues:
737
                self.startEntry.insert(0, str(self.prefValues['startCommand']))
738
            # Stop command
739
            Label(self.commandFrame, text="Stop Command:").grid(row=1, column=0, sticky=W)
740
            self.stopEntry = Entry(self.commandFrame)
741
            self.stopEntry.grid(row=1, column=1, sticky='nsew')
742
            if 'stopCommand' in self.prefValues:
743
                self.stopEntry.insert(0, str(self.prefValues['stopCommand']))
744

    
745
        def addInterface( self ):
746
            self.tableFrame.addRow()
747

    
748
        def defaultDpid( self ,name):
749
            "Derive dpid from switch name, s1 -> 1"
750
            try:
751
                dpid = int( re.findall( r'\d+', name )[ 0 ] )
752
                dpid = hex( dpid )[ 2: ]
753
                return dpid
754
            except IndexError:
755
                return None
756
                #raise Exception( 'Unable to derive default datapath ID - '
757
                #                 'please either specify a dpid or use a '
758
                #                 'canonical switch name such as s23.' )
759

    
760
        def apply(self):
761
            externalInterfaces = []
762
            for row in range(self.tableFrame.rows):
763
                #print 'Interface is ' + self.tableFrame.get(row, 0)
764
                if (len(self.tableFrame.get(row, 0)) > 0):
765
                    externalInterfaces.append(self.tableFrame.get(row, 0))
766

    
767
            dpid = self.dpidEntry.get()
768
            if (self.defaultDpid(self.hostnameEntry.get()) is None
769
               and len(dpid) == 0):
770
                showerror(title="Error",
771
                              message= 'Unable to derive default datapath ID - '
772
                                 'please either specify a DPID or use a '
773
                                 'canonical switch name such as s23.' )
774

    
775
            
776
            results = {'externalInterfaces':externalInterfaces,
777
                       'hostname':self.hostnameEntry.get(),
778
                       'dpid':dpid,
779
                       'startCommand':self.startEntry.get(),
780
                       'stopCommand':self.stopEntry.get(),
781
                       'sflow':str(self.sflow.get()),
782
                       'netflow':str(self.nflow.get()),
783
                       'dpctl':self.dpctlEntry.get(),
784
                       'switchIP':self.ipEntry.get()}
785
            sw = self.switchType.get()
786
            if sw == 'Indigo Virtual Switch':
787
                results['switchType'] = 'ivs'
788
                if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
789
                    self.ovsOk = False
790
                    showerror(title="Error",
791
                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
792
            elif sw == 'Userspace Switch inNamespace':
793
                results['switchType'] = 'userns'
794
            elif sw == 'Userspace Switch':
795
                results['switchType'] = 'user'
796
            elif sw == 'Open vSwitch Kernel Mode':
797
                results['switchType'] = 'ovs'
798
            else:
799
                results['switchType'] = 'default'
800
            self.result = results
801

    
802

    
803
class VerticalScrolledTable(LabelFrame):
804
    """A pure Tkinter scrollable frame that actually works!
805

806
    * Use the 'interior' attribute to place widgets inside the scrollable frame
807
    * Construct and pack/place/grid normally
808
    * This frame only allows vertical scrolling
809
    
810
    """
811
    def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw):
812
        LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw)            
813

    
814
        # create a canvas object and a vertical scrollbar for scrolling it
815
        vscrollbar = Scrollbar(self, orient=VERTICAL)
816
        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
817
        canvas = Canvas(self, bd=0, highlightthickness=0,
818
                        yscrollcommand=vscrollbar.set)
819
        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
820
        vscrollbar.config(command=canvas.yview)
821

    
822
        # reset the view
823
        canvas.xview_moveto(0)
824
        canvas.yview_moveto(0)
825

    
826
        # create a frame inside the canvas which will be scrolled with it
827
        self.interior = interior = TableFrame(canvas, rows=rows, columns=columns)
828
        interior_id = canvas.create_window(0, 0, window=interior,
829
                                           anchor=NW)
830

    
831
        # track changes to the canvas and frame width and sync them,
832
        # also updating the scrollbar
833
        def _configure_interior(event):
834
            # update the scrollbars to match the size of the inner frame
835
            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
836
            canvas.config(scrollregion="0 0 %s %s" % size)
837
            if interior.winfo_reqwidth() != canvas.winfo_width():
838
                # update the canvas's width to fit the inner frame
839
                canvas.config(width=interior.winfo_reqwidth())
840
        interior.bind('<Configure>', _configure_interior)
841

    
842
        def _configure_canvas(event):
843
            if interior.winfo_reqwidth() != canvas.winfo_width():
844
                # update the inner frame's width to fill the canvas
845
                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
846
        canvas.bind('<Configure>', _configure_canvas)
847

    
848
        return
849

    
850
class TableFrame(Frame):
851
    def __init__(self, parent, rows=2, columns=2):
852

    
853
        Frame.__init__(self, parent, background="black")
854
        self._widgets = []
855
        self.rows = rows
856
        self.columns = columns
857
        for row in range(rows):
858
            current_row = []
859
            for column in range(columns):
860
                label = Entry(self, borderwidth=0)
861
                label.grid(row=row, column=column, sticky="wens", padx=1, pady=1)
862
                current_row.append(label)
863
            self._widgets.append(current_row)
864

    
865
    def set(self, row, column, value):
866
        widget = self._widgets[row][column]
867
        widget.insert(0, value)
868

    
869
    def get(self, row, column):
870
        widget = self._widgets[row][column]
871
        return widget.get()
872

    
873
    def addRow( self, value=None, readonly=False ):
874
        #print "Adding row " + str(self.rows +1)
875
        current_row = []
876
        for column in range(self.columns):
877
            label = Entry(self, borderwidth=0)
878
            label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1)
879
            if value is not None:
880
                label.insert(0, value[column])
881
            if (readonly == True):
882
                label.configure(state='readonly')
883
            current_row.append(label)
884
        self._widgets.append(current_row)
885
        self.update_idletasks()
886
        self.rows += 1
887

    
888
class LinkDialog(tkSimpleDialog.Dialog):
889

    
890
        def __init__(self, parent, title, linkDefaults):
891

    
892
            self.linkValues = linkDefaults
893

    
894
            tkSimpleDialog.Dialog.__init__(self, parent, title)
895

    
896
        def body(self, master):
897

    
898
            self.var = StringVar(master)
899
            Label(master, text="Bandwidth:").grid(row=0, sticky=E)
900
            self.e1 = Entry(master)
901
            self.e1.grid(row=0, column=1)
902
            Label(master, text="Mbit").grid(row=0, column=2, sticky=W)
903
            if 'bw' in self.linkValues:
904
                self.e1.insert(0,str(self.linkValues['bw']))
905

    
906
            Label(master, text="Delay:").grid(row=1, sticky=E)
907
            self.e2 = Entry(master)
908
            self.e2.grid(row=1, column=1)
909
            if 'delay' in self.linkValues:
910
                self.e2.insert(0, self.linkValues['delay'])
911

    
912
            Label(master, text="Loss:").grid(row=2, sticky=E)
913
            self.e3 = Entry(master)
914
            self.e3.grid(row=2, column=1)
915
            Label(master, text="%").grid(row=2, column=2, sticky=W)
916
            if 'loss' in self.linkValues:
917
                self.e3.insert(0, str(self.linkValues['loss']))
918

    
919
            Label(master, text="Max Queue size:").grid(row=3, sticky=E)
920
            self.e4 = Entry(master)
921
            self.e4.grid(row=3, column=1)
922
            if 'max_queue_size' in self.linkValues:
923
                self.e4.insert(0, str(self.linkValues['max_queue_size']))
924

    
925
            Label(master, text="Jitter:").grid(row=4, sticky=E)
926
            self.e5 = Entry(master)
927
            self.e5.grid(row=4, column=1)
928
            if 'jitter' in self.linkValues:
929
                self.e5.insert(0, self.linkValues['jitter'])
930

    
931
            Label(master, text="Speedup:").grid(row=5, sticky=E)
932
            self.e6 = Entry(master)
933
            self.e6.grid(row=5, column=1)
934
            if 'speedup' in self.linkValues:
935
                self.e6.insert(0, str(self.linkValues['speedup']))
936

    
937
            return self.e1 # initial focus
938

    
939
        def apply(self):
940
            self.result = {}
941
            if (len(self.e1.get()) > 0):
942
                self.result['bw'] = int(self.e1.get())
943
            if (len(self.e2.get()) > 0):
944
                self.result['delay'] = self.e2.get()
945
            if (len(self.e3.get()) > 0):
946
                self.result['loss'] = int(self.e3.get())
947
            if (len(self.e4.get()) > 0):
948
                self.result['max_queue_size'] = int(self.e4.get())
949
            if (len(self.e5.get()) > 0):
950
                self.result['jitter'] = self.e5.get()
951
            if (len(self.e6.get()) > 0):
952
                self.result['speedup'] = int(self.e6.get())
953

    
954
class ControllerDialog(tkSimpleDialog.Dialog):
955

    
956
        def __init__(self, parent, title, ctrlrDefaults=None):
957

    
958
            if ctrlrDefaults:
959
                self.ctrlrValues = ctrlrDefaults
960

    
961
            tkSimpleDialog.Dialog.__init__(self, parent, title)
962

    
963
        def body(self, master):
964

    
965
            self.var = StringVar(master)
966
            self.protcolvar = StringVar(master)
967

    
968
            rowCount=0
969
            # Field for Hostname
970
            Label(master, text="Name:").grid(row=rowCount, sticky=E)
971
            self.hostnameEntry = Entry(master)
972
            self.hostnameEntry.grid(row=rowCount, column=1)
973
            self.hostnameEntry.insert(0, self.ctrlrValues['hostname'])
974
            rowCount+=1
975

    
976
            # Field for Remove Controller Port
977
            Label(master, text="Controller Port:").grid(row=rowCount, sticky=E)
978
            self.e2 = Entry(master)
979
            self.e2.grid(row=rowCount, column=1)
980
            self.e2.insert(0, self.ctrlrValues['remotePort'])
981
            rowCount+=1
982

    
983
            # Field for Controller Type
984
            Label(master, text="Controller Type:").grid(row=rowCount, sticky=E)
985
            controllerType = self.ctrlrValues['controllerType']
986
            self.o1 = OptionMenu(master, self.var, "Remote Controller", "In-Band Controller", "OpenFlow Reference", "OVS Controller")
987
            self.o1.grid(row=rowCount, column=1, sticky=W)
988
            if controllerType == 'ref':
989
                self.var.set("OpenFlow Reference")
990
            elif controllerType == 'inband':
991
                self.var.set("In-Band Controller")
992
            elif controllerType == 'remote':
993
                self.var.set("Remote Controller")
994
            else:
995
                self.var.set("OVS Controller")
996
            rowCount+=1
997

    
998
            # Field for Controller Protcol
999
            Label(master, text="Protocol:").grid(row=rowCount, sticky=E)
1000
            if 'controllerProtocol' in self.ctrlrValues:
1001
               controllerProtocol = self.ctrlrValues['controllerProtocol']
1002
            else:
1003
               controllerProtocol = 'tcp'
1004
            self.protcol = OptionMenu(master, self.protcolvar, "TCP", "SSL")
1005
            self.protcol.grid(row=rowCount, column=1, sticky=W)
1006
            if controllerProtocol == 'ssl':
1007
                self.protcolvar.set("SSL")
1008
            else:
1009
                self.protcolvar.set("TCP")
1010
            rowCount+=1
1011

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

    
1016
            Label(remoteFrame, text="IP Address:").grid(row=0, sticky=E)
1017
            self.e1 = Entry(remoteFrame)
1018
            self.e1.grid(row=0, column=1)
1019
            self.e1.insert(0, self.ctrlrValues['remoteIP'])
1020
            rowCount+=1
1021

    
1022
            return self.hostnameEntry # initial focus
1023

    
1024
        def apply(self):
1025
            self.result = { 'hostname': self.hostnameEntry.get(),
1026
                            'remoteIP': self.e1.get(),
1027
                            'remotePort': int(self.e2.get())}
1028

    
1029
            controllerType = self.var.get()
1030
            if controllerType == 'Remote Controller':
1031
                self.result['controllerType'] = 'remote'
1032
            elif controllerType == 'In-Band Controller':
1033
                self.result['controllerType'] = 'inband'
1034
            elif controllerType == 'OpenFlow Reference':
1035
                self.result['controllerType'] = 'ref'
1036
            else:
1037
                self.result['controllerType'] = 'ovsc'
1038
            controllerProtocol = self.protcolvar.get()
1039
            if controllerProtocol == 'SSL':
1040
                self.result['controllerProtocol'] = 'ssl'
1041
            else:
1042
                self.result['controllerProtocol'] = 'tcp'
1043

    
1044
class ToolTip(object):
1045

    
1046
    def __init__(self, widget):
1047
        self.widget = widget
1048
        self.tipwindow = None
1049
        self.id = None
1050
        self.x = self.y = 0
1051

    
1052
    def showtip(self, text):
1053
        "Display text in tooltip window"
1054
        self.text = text
1055
        if self.tipwindow or not self.text:
1056
            return
1057
        x, y, cx, cy = self.widget.bbox("insert")
1058
        x = x + self.widget.winfo_rootx() + 27
1059
        y = y + cy + self.widget.winfo_rooty() +27
1060
        self.tipwindow = tw = Toplevel(self.widget)
1061
        tw.wm_overrideredirect(1)
1062
        tw.wm_geometry("+%d+%d" % (x, y))
1063
        try:
1064
            # For Mac OS
1065
            tw.tk.call("::tk::unsupported::MacWindowStyle",
1066
                       "style", tw._w,
1067
                       "help", "noActivates")
1068
        except TclError:
1069
            pass
1070
        label = Label(tw, text=self.text, justify=LEFT,
1071
                      background="#ffffe0", relief=SOLID, borderwidth=1,
1072
                      font=("tahoma", "8", "normal"))
1073
        label.pack(ipadx=1)
1074

    
1075
    def hidetip(self):
1076
        tw = self.tipwindow
1077
        self.tipwindow = None
1078
        if tw:
1079
            tw.destroy()
1080

    
1081
class MiniEdit( Frame ):
1082

    
1083
    "A simple network editor for Mininet."
1084

    
1085
    def __init__( self, parent=None, cheight=600, cwidth=1000 ):
1086

    
1087
        self.defaultIpBase='10.0.0.0/8'
1088

    
1089
        self.nflowDefaults = {'nflowTarget':'',
1090
                              'nflowTimeout':'600',
1091
                              'nflowAddId':'0'}
1092
        self.sflowDefaults = {'sflowTarget':'',
1093
                              'sflowSampling':'400',
1094
                              'sflowHeader':'128',
1095
                              'sflowPolling':'30'}
1096

    
1097
        self.appPrefs={
1098
            "ipBase": self.defaultIpBase,
1099
            "startCLI": "0",
1100
            "terminalType": 'xterm',
1101
            "switchType": 'ovs',
1102
            "dpctl": '',
1103
            'sflow':self.sflowDefaults,
1104
            'netflow':self.nflowDefaults,
1105
            'openFlowVersions':{'ovsOf10':'1',
1106
                                'ovsOf11':'0',
1107
                                'ovsOf12':'0',
1108
                                'ovsOf13':'0'}
1109

    
1110
        }
1111

    
1112

    
1113
        Frame.__init__( self, parent )
1114
        self.action = None
1115
        self.appName = 'MiniEdit'
1116
        self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" )
1117

    
1118
        # Style
1119
        self.font = ( 'Geneva', 9 )
1120
        self.smallFont = ( 'Geneva', 7 )
1121
        self.bg = 'white'
1122

    
1123
        # Title
1124
        self.top = self.winfo_toplevel()
1125
        self.top.title( self.appName )
1126

    
1127
        # Menu bar
1128
        self.createMenubar()
1129

    
1130
        # Editing canvas
1131
        self.cheight, self.cwidth = cheight, cwidth
1132
        self.cframe, self.canvas = self.createCanvas()
1133

    
1134
        # Toolbar
1135
        self.controllers = {}
1136

    
1137
        # Toolbar
1138
        self.images = miniEditImages()
1139
        self.buttons = {}
1140
        self.active = None
1141
        self.tools = ( 'Select', 'Host', 'Switch', 'LegacySwitch', 'LegacyRouter', 'NetLink', 'Controller' )
1142
        self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
1143
        self.toolbar = self.createToolbar()
1144

    
1145
        # Layout
1146
        self.toolbar.grid( column=0, row=0, sticky='nsew')
1147
        self.cframe.grid( column=1, row=0 )
1148
        self.columnconfigure( 1, weight=1 )
1149
        self.rowconfigure( 0, weight=1 )
1150
        self.pack( expand=True, fill='both' )
1151

    
1152
        # About box
1153
        self.aboutBox = None
1154

    
1155
        # Initialize node data
1156
        self.nodeBindings = self.createNodeBindings()
1157
        self.nodePrefixes = { 'LegacyRouter': 'r', 'LegacySwitch': 's', 'Switch': 's', 'Host': 'h' , 'Controller': 'c'}
1158
        self.widgetToItem = {}
1159
        self.itemToWidget = {}
1160

    
1161
        # Initialize link tool
1162
        self.link = self.linkWidget = None
1163

    
1164
        # Selection support
1165
        self.selection = None
1166

    
1167
        # Keyboard bindings
1168
        self.bind( '<Control-q>', lambda event: self.quit() )
1169
        self.bind( '<KeyPress-Delete>', self.deleteSelection )
1170
        self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
1171
        self.focus()
1172

    
1173
        self.hostPopup = Menu(self.top, tearoff=0)
1174
        self.hostPopup.add_command(label='Host Options', font=self.font)
1175
        self.hostPopup.add_separator()
1176
        self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails )
1177

    
1178
        self.hostRunPopup = Menu(self.top, tearoff=0)
1179
        self.hostRunPopup.add_command(label='Host Options', font=self.font)
1180
        self.hostRunPopup.add_separator()
1181
        self.hostRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
1182

    
1183
        self.legacyRouterRunPopup = Menu(self.top, tearoff=0)
1184
        self.legacyRouterRunPopup.add_command(label='Router Options', font=self.font)
1185
        self.legacyRouterRunPopup.add_separator()
1186
        self.legacyRouterRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
1187

    
1188
        self.switchPopup = Menu(self.top, tearoff=0)
1189
        self.switchPopup.add_command(label='Switch Options', font=self.font)
1190
        self.switchPopup.add_separator()
1191
        self.switchPopup.add_command(label='Properties', font=self.font, command=self.switchDetails )
1192

    
1193
        self.switchRunPopup = Menu(self.top, tearoff=0)
1194
        self.switchRunPopup.add_command(label='Switch Options', font=self.font)
1195
        self.switchRunPopup.add_separator()
1196
        self.switchRunPopup.add_command(label='List bridge details', font=self.font, command=self.listBridge )
1197

    
1198
        self.linkPopup = Menu(self.top, tearoff=0)
1199
        self.linkPopup.add_command(label='Link Options', font=self.font)
1200
        self.linkPopup.add_separator()
1201
        self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails )
1202

    
1203
        self.linkRunPopup = Menu(self.top, tearoff=0)
1204
        self.linkRunPopup.add_command(label='Link Options', font=self.font)
1205
        self.linkRunPopup.add_separator()
1206
        self.linkRunPopup.add_command(label='Link Up', font=self.font, command=self.linkUp )
1207
        self.linkRunPopup.add_command(label='Link Down', font=self.font, command=self.linkDown )
1208

    
1209
        self.controllerPopup = Menu(self.top, tearoff=0)
1210
        self.controllerPopup.add_command(label='Controller Options', font=self.font)
1211
        self.controllerPopup.add_separator()
1212
        self.controllerPopup.add_command(label='Properties', font=self.font, command=self.controllerDetails )
1213

    
1214

    
1215
        # Event handling initalization
1216
        self.linkx = self.linky = self.linkItem = None
1217
        self.lastSelection = None
1218

    
1219
        # Model initialization
1220
        self.links = {}
1221
        self.hostOpts = {}
1222
        self.switchOpts = {}
1223
        self.hostCount = 0
1224
        self.switchCount = 0
1225
        self.controllerCount = 0
1226
        self.net = None
1227

    
1228
        # Close window gracefully
1229
        Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
1230

    
1231
    def quit( self ):
1232
        "Stop our network, if any, then quit."
1233
        self.stop()
1234
        Frame.quit( self )
1235

    
1236
    def createMenubar( self ):
1237
        "Create our menu bar."
1238

    
1239
        font = self.font
1240

    
1241
        mbar = Menu( self.top, font=font )
1242
        self.top.configure( menu=mbar )
1243

    
1244

    
1245
        fileMenu = Menu( mbar, tearoff=False )
1246
        mbar.add_cascade( label="File", font=font, menu=fileMenu )
1247
        fileMenu.add_command( label="New", font=font, command=self.newTopology )
1248
        fileMenu.add_command( label="Open", font=font, command=self.loadTopology )
1249
        fileMenu.add_command( label="Save", font=font, command=self.saveTopology )
1250
        fileMenu.add_command( label="Export Level 2 Script", font=font, command=self.exportScript )
1251
        fileMenu.add_separator()
1252
        fileMenu.add_command( label='Quit', command=self.quit, font=font )
1253

    
1254
        editMenu = Menu( mbar, tearoff=False )
1255
        mbar.add_cascade( label="Edit", font=font, menu=editMenu )
1256
        editMenu.add_command( label="Cut", font=font,
1257
                              command=lambda: self.deleteSelection( None ) )
1258
        editMenu.add_command( label="Preferences", font=font, command=self.prefDetails)
1259

    
1260
        runMenu = Menu( mbar, tearoff=False )
1261
        mbar.add_cascade( label="Run", font=font, menu=runMenu )
1262
        runMenu.add_command( label="Run", font=font, command=self.doRun )
1263
        runMenu.add_command( label="Stop", font=font, command=self.doStop )
1264
        fileMenu.add_separator()
1265
        runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow )
1266
        runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal )
1267

    
1268
        # Application menu
1269
        appMenu = Menu( mbar, tearoff=False )
1270
        mbar.add_cascade( label="Help", font=font, menu=appMenu )
1271
        appMenu.add_command( label='About MiniEdit', command=self.about,
1272
                             font=font)
1273
    # Canvas
1274

    
1275
    def createCanvas( self ):
1276
        "Create and return our scrolling canvas frame."
1277
        f = Frame( self )
1278

    
1279
        canvas = Canvas( f, width=self.cwidth, height=self.cheight,
1280
                         bg=self.bg )
1281

    
1282
        # Scroll bars
1283
        xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
1284
        ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
1285
        canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
1286

    
1287
        # Resize box
1288
        resize = Label( f, bg='white' )
1289

    
1290
        # Layout
1291
        canvas.grid( row=0, column=1, sticky='nsew')
1292
        ybar.grid( row=0, column=2, sticky='ns')
1293
        xbar.grid( row=1, column=1, sticky='ew' )
1294
        resize.grid( row=1, column=2, sticky='nsew' )
1295

    
1296
        # Resize behavior
1297
        f.rowconfigure( 0, weight=1 )
1298
        f.columnconfigure( 1, weight=1 )
1299
        f.grid( row=0, column=0, sticky='nsew' )
1300
        f.bind( '<Configure>', lambda event: self.updateScrollRegion() )
1301

    
1302
        # Mouse bindings
1303
        canvas.bind( '<ButtonPress-1>', self.clickCanvas )
1304
        canvas.bind( '<B1-Motion>', self.dragCanvas )
1305
        canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
1306

    
1307
        return f, canvas
1308

    
1309
    def updateScrollRegion( self ):
1310
        "Update canvas scroll region to hold everything."
1311
        bbox = self.canvas.bbox( 'all' )
1312
        if bbox is not None:
1313
            self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ],
1314
                                   bbox[ 3 ] ) )
1315

    
1316
    def canvasx( self, x_root ):
1317
        "Convert root x coordinate to canvas coordinate."
1318
        c = self.canvas
1319
        return c.canvasx( x_root ) - c.winfo_rootx()
1320

    
1321
    def canvasy( self, y_root ):
1322
        "Convert root y coordinate to canvas coordinate."
1323
        c = self.canvas
1324
        return c.canvasy( y_root ) - c.winfo_rooty()
1325

    
1326
    # Toolbar
1327

    
1328
    def activate( self, toolName ):
1329
        "Activate a tool and press its button."
1330
        # Adjust button appearance
1331
        if self.active:
1332
            self.buttons[ self.active ].configure( relief='raised' )
1333
        self.buttons[ toolName ].configure( relief='sunken' )
1334
        # Activate dynamic bindings
1335
        self.active = toolName
1336

    
1337

    
1338
    def createToolTip(self, widget, text):
1339
        toolTip = ToolTip(widget)
1340
        def enter(event):
1341
            toolTip.showtip(text)
1342
        def leave(event):
1343
            toolTip.hidetip()
1344
        widget.bind('<Enter>', enter)
1345
        widget.bind('<Leave>', leave)
1346

    
1347
    def createToolbar( self ):
1348
        "Create and return our toolbar frame."
1349

    
1350
        toolbar = Frame( self )
1351

    
1352
        # Tools
1353
        for tool in self.tools:
1354
            cmd = ( lambda t=tool: self.activate( t ) )
1355
            b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
1356
            if tool in self.images:
1357
                b.config( height=35, image=self.images[ tool ] )
1358
                self.createToolTip(b, str(tool))
1359
                # b.config( compound='top' )
1360
            b.pack( fill='x' )
1361
            self.buttons[ tool ] = b
1362
        self.activate( self.tools[ 0 ] )
1363

    
1364
        # Spacer
1365
        Label( toolbar, text='' ).pack()
1366

    
1367
        # Commands
1368
        for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]:
1369
            doCmd = getattr( self, 'do' + cmd )
1370
            b = Button( toolbar, text=cmd, font=self.smallFont,
1371
                        fg=color, command=doCmd )
1372
            b.pack( fill='x', side='bottom' )
1373

    
1374
        return toolbar
1375

    
1376
    def doRun( self ):
1377
        "Run command."
1378
        self.activate( 'Select' )
1379
        for tool in self.tools:
1380
            self.buttons[ tool ].config( state='disabled' )
1381
        self.start()
1382

    
1383
    def doStop( self ):
1384
        "Stop command."
1385
        self.stop()
1386
        for tool in self.tools:
1387
            self.buttons[ tool ].config( state='normal' )
1388

    
1389
    def addNode( self, node, nodeNum, x, y, name=None):
1390
        "Add a new node to our canvas."
1391
        if 'Switch' == node:
1392
            self.switchCount += 1
1393
        if 'Host' == node:
1394
            self.hostCount += 1
1395
        if 'Controller' == node:
1396
            self.controllerCount += 1
1397
        if name is None:
1398
            name = self.nodePrefixes[ node ] + nodeNum
1399
        self.addNamedNode(node, name, x, y)
1400

    
1401
    def addNamedNode( self, node, name, x, y):
1402
        "Add a new node to our canvas."
1403
        c = self.canvas
1404
        icon = self.nodeIcon( node, name )
1405
        item = self.canvas.create_window( x, y, anchor='c', window=icon,
1406
                                          tags=node )
1407
        self.widgetToItem[ icon ] = item
1408
        self.itemToWidget[ item ] = icon
1409
        icon.links = {}
1410

    
1411
    def convertJsonUnicode(self, input):
1412
        "Some part of Mininet don't like Unicode"
1413
        if isinstance(input, dict):
1414
            return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in input.iteritems()}
1415
        elif isinstance(input, list):
1416
            return [self.convertJsonUnicode(element) for element in input]
1417
        elif isinstance(input, unicode):
1418
            return input.encode('utf-8')
1419
        else:
1420
            return input
1421

    
1422
    def loadTopology( self ):
1423
        "Load command."
1424
        c = self.canvas
1425

    
1426
        myFormats = [
1427
            ('Mininet Topology','*.mn'),
1428
            ('All Files','*'),
1429
        ]
1430
        f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
1431
        if f == None:
1432
            return
1433
        self.newTopology()
1434
        loadedTopology = self.convertJsonUnicode(json.load(f))
1435

    
1436
        # Load application preferences
1437
        if 'application' in loadedTopology:
1438
            self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items())
1439
            if "ovsOf10" not in self.appPrefs["openFlowVersions"]:
1440
                self.appPrefs["openFlowVersions"]["ovsOf10"] = '0'
1441
            if "ovsOf11" not in self.appPrefs["openFlowVersions"]:
1442
                self.appPrefs["openFlowVersions"]["ovsOf11"] = '0'
1443
            if "ovsOf12" not in self.appPrefs["openFlowVersions"]:
1444
                self.appPrefs["openFlowVersions"]["ovsOf12"] = '0'
1445
            if "ovsOf13" not in self.appPrefs["openFlowVersions"]:
1446
                self.appPrefs["openFlowVersions"]["ovsOf13"] = '0'
1447
            if "sflow" not in self.appPrefs:
1448
                self.appPrefs["sflow"] = self.sflowDefaults
1449
            if "netflow" not in self.appPrefs:
1450
                self.appPrefs["netflow"] = self.nflowDefaults
1451

    
1452
        # Load controllers
1453
        if ('controllers' in loadedTopology):
1454
            if (loadedTopology['version'] == '1'):
1455
                # This is old location of controller info
1456
                hostname = 'c0'
1457
                self.controllers = {}
1458
                self.controllers[hostname] = loadedTopology['controllers']['c0']
1459
                self.controllers[hostname]['hostname'] = hostname
1460
                self.addNode('Controller', 0, float(30), float(30), name=hostname)
1461
                icon = self.findWidgetByName(hostname)
1462
                icon.bind('<Button-3>', self.do_controllerPopup )
1463
            else:
1464
                controllers = loadedTopology['controllers']
1465
                for controller in controllers:
1466
                    hostname = controller['opts']['hostname']
1467
                    x = controller['x']
1468
                    y = controller['y']
1469
                    self.addNode('Controller', 0, float(x), float(y), name=hostname)
1470
                    self.controllers[hostname] = controller['opts']
1471
                    icon = self.findWidgetByName(hostname)
1472
                    icon.bind('<Button-3>', self.do_controllerPopup )
1473

    
1474

    
1475
        # Load hosts
1476
        hosts = loadedTopology['hosts']
1477
        for host in hosts:
1478
            nodeNum = host['number']
1479
            hostname = 'h'+nodeNum
1480
            if 'hostname' in host['opts']:
1481
                hostname = host['opts']['hostname']
1482
            else:
1483
                host['opts']['hostname'] = hostname
1484
            if 'nodeNum' not in host['opts']:
1485
                host['opts']['nodeNum'] = int(nodeNum)
1486
            x = host['x']
1487
            y = host['y']
1488
            self.addNode('Host', nodeNum, float(x), float(y), name=hostname)
1489

    
1490
            # Fix JSON converting tuple to list when saving
1491
            if 'privateDirectory' in host['opts']:
1492
                newDirList = []
1493
                for privateDir in host['opts']['privateDirectory']:
1494
                    if isinstance( privateDir, list ):
1495
                        newDirList.append((privateDir[0],privateDir[1]))
1496
                    else:
1497
                        newDirList.append(privateDir)
1498
                host['opts']['privateDirectory'] = newDirList
1499
            self.hostOpts[hostname] = host['opts']
1500
            icon = self.findWidgetByName(hostname)
1501
            icon.bind('<Button-3>', self.do_hostPopup )
1502

    
1503
        # Load switches
1504
        switches = loadedTopology['switches']
1505
        for switch in switches:
1506
            nodeNum = switch['number']
1507
            hostname = 's'+nodeNum
1508
            if 'controllers' not in switch['opts']:
1509
                switch['opts']['controllers'] = []
1510
            if 'switchType' not in switch['opts']:
1511
                switch['opts']['switchType'] = 'default'
1512
            if 'hostname' in switch['opts']:
1513
                hostname = switch['opts']['hostname']
1514
            else:
1515
                switch['opts']['hostname'] = hostname
1516
            if 'nodeNum' not in switch['opts']:
1517
                switch['opts']['nodeNum'] = int(nodeNum)
1518
            x = switch['x']
1519
            y = switch['y']
1520
            if switch['opts']['switchType'] == "legacyRouter":
1521
                self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname)
1522
                icon = self.findWidgetByName(hostname)
1523
                icon.bind('<Button-3>', self.do_legacyRouterPopup )
1524
            elif switch['opts']['switchType'] == "legacySwitch":
1525
                self.addNode('LegacySwitch', nodeNum, float(x), float(y), name=hostname)
1526
                icon = self.findWidgetByName(hostname)
1527
                icon.bind('<Button-3>', self.do_legacySwitchPopup )
1528
            else:
1529
                self.addNode('Switch', nodeNum, float(x), float(y), name=hostname)
1530
                icon = self.findWidgetByName(hostname)
1531
                icon.bind('<Button-3>', self.do_switchPopup )
1532
            self.switchOpts[hostname] = switch['opts']
1533

    
1534
            # create links to controllers
1535
            if (int(loadedTopology['version']) > 1):
1536
                controllers = self.switchOpts[hostname]['controllers']
1537
                for controller in controllers:
1538
                    dest = self.findWidgetByName(controller)
1539
                    dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1540
                    self.link = self.canvas.create_line(float(x),
1541
                                                        float(y),
1542
                                                        dx,
1543
                                                        dy,
1544
                                                        width=4,
1545
                                                        fill='red',
1546
                                                        dash=(6, 4, 2, 4),
1547
                                                        tag='link' )
1548
                    c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
1549
                    self.addLink( icon, dest, linktype='control' )
1550
                    self.createControlLinkBindings()
1551
                    self.link = self.linkWidget = None
1552
            else:
1553
                dest = self.findWidgetByName('c0')
1554
                dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1555
                self.link = self.canvas.create_line(float(x),
1556
                                                    float(y),
1557
                                                    dx,
1558
                                                    dy,
1559
                                                    width=4,
1560
                                                    fill='red',
1561
                                                    dash=(6, 4, 2, 4),
1562
                                                    tag='link' )
1563
                c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
1564
                self.addLink( icon, dest, linktype='control' )
1565
                self.createControlLinkBindings()
1566
                self.link = self.linkWidget = None
1567

    
1568
        # Load links
1569
        links = loadedTopology['links']
1570
        for link in links:
1571
            srcNode = link['src']
1572
            src = self.findWidgetByName(srcNode)
1573
            sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
1574

    
1575
            destNode = link['dest']
1576
            dest = self.findWidgetByName(destNode)
1577
            dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
1578

    
1579
            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
1580
                                             fill='blue', tag='link' )
1581
            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
1582
            self.addLink( src, dest, linkopts=link['opts'] )
1583
            self.createDataLinkBindings()
1584
            self.link = self.linkWidget = None
1585

    
1586
        f.close
1587

    
1588
    def findWidgetByName( self, name ):
1589
        for widget in self.widgetToItem:
1590
            if name ==  widget[ 'text' ]:
1591
                return widget
1592

    
1593
    def newTopology( self ):
1594
        "New command."
1595
        for widget in self.widgetToItem.keys():
1596
            self.deleteItem( self.widgetToItem[ widget ] )
1597
        self.hostCount = 0
1598
        self.switchCount = 0
1599
        self.controllerCount = 0
1600
        self.links = {}
1601
        self.hostOpts = {}
1602
        self.switchOpts = {}
1603
        self.controllers = {}
1604
        self.appPrefs["ipBase"]= self.defaultIpBase
1605

    
1606
    def saveTopology( self ):
1607
        "Save command."
1608
        myFormats = [
1609
            ('Mininet Topology','*.mn'),
1610
            ('All Files','*'),
1611
        ]
1612

    
1613
        savingDictionary = {}
1614
        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...")
1615
        if len(fileName ) > 0:
1616
            # Save Application preferences
1617
            savingDictionary['version'] = '2'
1618

    
1619
            # Save Switches and Hosts
1620
            hostsToSave = []
1621
            switchesToSave = []
1622
            controllersToSave = []
1623
            for widget in self.widgetToItem:
1624
                name = widget[ 'text' ]
1625
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1626
                x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] )
1627
                if 'Switch' in tags or 'LegacySwitch' in tags or 'LegacyRouter' in tags:
1628
                    nodeNum = self.switchOpts[name]['nodeNum']
1629
                    nodeToSave = {'number':str(nodeNum),
1630
                                  'x':str(x1),
1631
                                  'y':str(y1),
1632
                                  'opts':self.switchOpts[name] }
1633
                    switchesToSave.append(nodeToSave)
1634
                elif 'Host' in tags:
1635
                    nodeNum = self.hostOpts[name]['nodeNum']
1636
                    nodeToSave = {'number':str(nodeNum),
1637
                                  'x':str(x1),
1638
                                  'y':str(y1),
1639
                                  'opts':self.hostOpts[name] }
1640
                    hostsToSave.append(nodeToSave)
1641
                elif 'Controller' in tags:
1642
                    nodeToSave = {'x':str(x1),
1643
                                  'y':str(y1),
1644
                                  'opts':self.controllers[name] }
1645
                    controllersToSave.append(nodeToSave)
1646
                else:
1647
                    raise Exception( "Cannot create mystery node: " + name )
1648
            savingDictionary['hosts'] = hostsToSave
1649
            savingDictionary['switches'] = switchesToSave
1650
            savingDictionary['controllers'] = controllersToSave
1651

    
1652
            # Save Links
1653
            linksToSave = []
1654
            for link in self.links.values():
1655
                src = link['src']
1656
                dst = link['dest']
1657
                linkopts = link['linkOpts']
1658

    
1659
                srcName, dstName = src[ 'text' ], dst[ 'text' ]
1660
                linkToSave = {'src':srcName,
1661
                              'dest':dstName,
1662
                              'opts':linkopts}
1663
                if link['type'] == 'data':
1664
                    linksToSave.append(linkToSave)
1665
            savingDictionary['links'] = linksToSave
1666

    
1667
            # Save Application preferences
1668
            savingDictionary['application'] = self.appPrefs
1669

    
1670
            try:
1671
                f = open(fileName, 'wb')
1672
                f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': ')))
1673
            except Exception as er:
1674
                print er
1675
            finally:
1676
                f.close()
1677

    
1678
    def exportScript( self ):
1679
        "Export command."
1680
        myFormats = [
1681
            ('Mininet Custom Topology','*.py'),
1682
            ('All Files','*'),
1683
        ]
1684

    
1685
        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...")
1686
        if len(fileName ) > 0:
1687
            #print "Now saving under %s" % fileName
1688
            f = open(fileName, 'wb')
1689

    
1690
            f.write("#!/usr/bin/python\n")
1691
            f.write("\n")
1692
            f.write("from mininet.net import Mininet\n")
1693
            f.write("from mininet.node import Controller, RemoteController, OVSController\n")
1694
            f.write("from mininet.node import CPULimitedHost, Host, Node\n")
1695
            f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n")
1696
            if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
1697
                f.write("from mininet.node import IVSSwitch\n")
1698
            f.write("from mininet.cli import CLI\n")
1699
            f.write("from mininet.log import setLogLevel, info\n")
1700
            f.write("from mininet.link import TCLink, Intf\n")
1701
            f.write("from subprocess import call\n")
1702

    
1703
            inBandCtrl = False
1704
            hasLegacySwitch = False
1705
            for widget in self.widgetToItem:
1706
                name = widget[ 'text' ]
1707
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1708

    
1709
                if 'Controller' in tags:
1710
                    opts = self.controllers[name]
1711
                    controllerType = opts['controllerType']
1712
                    if controllerType == 'inband':
1713
                        inBandCtrl = True
1714

    
1715
            if inBandCtrl == True:
1716
                f.write("\n")
1717
                f.write("class InbandController( RemoteController ):\n")
1718
                f.write("\n")
1719
                f.write("    def checkListening( self ):\n")
1720
                f.write("        \"Overridden to do nothing.\"\n")
1721
                f.write("        return\n")
1722

    
1723
            f.write("\n")
1724
            f.write("def myNetwork():\n")
1725
            f.write("\n")
1726
            f.write("    net = Mininet( topo=None,\n")
1727
            if len(self.appPrefs['dpctl']) > 0:
1728
                f.write("                   listenPort="+self.appPrefs['dpctl']+",\n")
1729
            f.write("                   build=False,\n")
1730
            f.write("                   ipBase='"+self.appPrefs['ipBase']+"')\n")
1731
            f.write("\n")
1732
            f.write("    info( '*** Adding controller\\n' )\n")
1733
            for widget in self.widgetToItem:
1734
                name = widget[ 'text' ]
1735
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1736
    
1737
                if 'Controller' in tags:
1738
                    opts = self.controllers[name]
1739
                    controllerType = opts['controllerType']
1740
                    if 'controllerProtocol' in opts:
1741
                        controllerProtocol = opts['controllerProtocol']
1742
                    else:
1743
                        controllerProtocol = 'tcp'
1744
                    controllerIP = opts['remoteIP']
1745
                    controllerPort = opts['remotePort']
1746

    
1747
    
1748
                    f.write("    "+name+"=net.addController(name='"+name+"',\n")
1749
        
1750
                    if controllerType == 'remote':
1751
                        f.write("                      controller=RemoteController,\n")
1752
                        f.write("                      ip='"+controllerIP+"',\n")
1753
                    elif controllerType == 'inband':
1754
                        f.write("                      controller=InbandController,\n")
1755
                        f.write("                      ip='"+controllerIP+"',\n")
1756
                    elif controllerType == 'ovsc':
1757
                        f.write("                      controller=OVSController,\n")
1758
                    else:
1759
                        f.write("                      controller=Controller,\n")
1760
        
1761
                    f.write("                      protocol='"+controllerProtocol+"',\n")
1762
                    f.write("                      port="+str(controllerPort)+")\n")
1763
                    f.write("\n")
1764

    
1765
            # Save Switches and Hosts
1766
            f.write("    info( '*** Add switches\\n')\n")
1767
            for widget in self.widgetToItem:
1768
                name = widget[ 'text' ]
1769
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1770
                if 'LegacyRouter' in tags:
1771
                    f.write("    "+name+" = net.addHost('"+name+"', cls=Node, ip='0.0.0.0')\n")
1772
                    f.write("    "+name+".cmd('sysctl -w net.ipv4.ip_forward=1')\n")
1773
                if 'LegacySwitch' in tags:
1774
                    f.write("    "+name+" = net.addSwitch('"+name+"', cls=OVSKernelSwitch, failMode='standalone')\n")
1775
                if 'Switch' in tags:
1776
                    opts = self.switchOpts[name]
1777
                    nodeNum = opts['nodeNum']
1778
                    f.write("    "+name+" = net.addSwitch('"+name+"'")
1779
                    if opts['switchType'] == 'default':
1780
                        if self.appPrefs['switchType'] == 'ivs':
1781
                            f.write(", cls=IVSSwitch")
1782
                        elif self.appPrefs['switchType'] == 'user':
1783
                            f.write(", cls=UserSwitch")
1784
                        elif self.appPrefs['switchType'] == 'userns':
1785
                            f.write(", cls=UserSwitch, inNamespace=True")
1786
                        else:
1787
                            f.write(", cls=OVSKernelSwitch")
1788
                    elif opts['switchType'] == 'ivs':
1789
                        f.write(", cls=IVSSwitch")
1790
                    elif opts['switchType'] == 'user':
1791
                        f.write(", cls=UserSwitch")
1792
                    elif opts['switchType'] == 'userns':
1793
                        f.write(", cls=UserSwitch, inNamespace=True")
1794
                    else:
1795
                        f.write(", cls=OVSKernelSwitch")
1796
                    if 'dpctl' in opts:
1797
                        f.write(", listenPort="+opts['dpctl'])
1798
                    if 'dpid' in opts:
1799
                        f.write(", dpid='"+opts['dpid']+"'")
1800
                    f.write(")\n")
1801
                    if ('externalInterfaces' in opts):
1802
                        for extInterface in opts['externalInterfaces']:
1803
                            f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
1804

    
1805
            f.write("\n")
1806
            f.write("    info( '*** Add hosts\\n')\n")
1807
            for widget in self.widgetToItem:
1808
                name = widget[ 'text' ]
1809
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1810
                if 'Host' in tags:
1811
                    opts = self.hostOpts[name]
1812
                    ip = None
1813
                    defaultRoute = None
1814
                    if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
1815
                        defaultRoute = "'via "+opts['defaultRoute']+"'"
1816
                    else:
1817
                        defaultRoute = 'None'
1818
                    if 'ip' in opts and len(opts['ip']) > 0:
1819
                        ip = opts['ip']
1820
                    else:
1821
                        nodeNum = self.hostOpts[name]['nodeNum']
1822
                        ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
1823
                        ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
1824

    
1825
                    if 'cores' in opts or 'cpu' in opts:
1826
                        f.write("    "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
1827
                        if 'cores' in opts:
1828
                            f.write("    "+name+".setCPUs(cores='"+opts['cores']+"')\n")
1829
                        if 'cpu' in opts:
1830
                            f.write("    "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n")
1831
                    else:
1832
                        f.write("    "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
1833
                    if ('externalInterfaces' in opts):
1834
                        for extInterface in opts['externalInterfaces']:
1835
                            f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
1836
            f.write("\n")
1837

    
1838
            # Save Links
1839
            f.write("    info( '*** Add links\\n')\n")
1840
            for key,linkDetail in self.links.iteritems():
1841
              tags = self.canvas.gettags(key)
1842
              if 'data' in tags:
1843
                optsExist = False
1844
                src = linkDetail['src']
1845
                dst = linkDetail['dest']
1846
                linkopts = linkDetail['linkOpts']
1847
                srcName, dstName = src[ 'text' ], dst[ 'text' ]
1848
                bw = ''
1849
                delay = ''
1850
                loss = ''
1851
                max_queue_size = ''
1852
                linkOpts = "{"
1853
                if 'bw' in linkopts:
1854
                    bw =  linkopts['bw']
1855
                    linkOpts = linkOpts + "'bw':"+str(bw)
1856
                    optsExist = True
1857
                if 'delay' in linkopts:
1858
                    delay =  linkopts['delay']
1859
                    if optsExist:
1860
                        linkOpts = linkOpts + ","
1861
                    linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'"
1862
                    optsExist = True
1863
                if 'loss' in linkopts:
1864
                    if optsExist:
1865
                        linkOpts = linkOpts + ","
1866
                    linkOpts = linkOpts + "'loss':"+str(linkopts['loss'])
1867
                    optsExist = True
1868
                if 'max_queue_size' in linkopts:
1869
                    if optsExist:
1870
                        linkOpts = linkOpts + ","
1871
                    linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size'])
1872
                    optsExist = True
1873
                if 'jitter' in linkopts:
1874
                    if optsExist:
1875
                        linkOpts = linkOpts + ","
1876
                    linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'"
1877
                    optsExist = True
1878
                if 'speedup' in linkopts:
1879
                    if optsExist:
1880
                        linkOpts = linkOpts + ","
1881
                    linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup'])
1882
                    optsExist = True
1883

    
1884
                linkOpts = linkOpts + "}"
1885
                if optsExist:
1886
                    f.write("    "+srcName+dstName+" = "+linkOpts+"\n")
1887
                f.write("    net.addLink("+srcName+", "+dstName)
1888
                if optsExist:
1889
                    f.write(", cls=TCLink , **"+srcName+dstName)
1890
                f.write(")\n")
1891

    
1892
            f.write("\n")
1893
            f.write("    info( '*** Starting network\\n')\n")
1894
            f.write("    net.build()\n")
1895

    
1896
            f.write("    info( '*** Starting controllers\\n')\n")
1897
            f.write("    for controller in net.controllers:\n")
1898
            f.write("        controller.start()\n")
1899
            f.write("\n")
1900

    
1901
            f.write("    info( '*** Starting switches\\n')\n")
1902
            for widget in self.widgetToItem:
1903
                name = widget[ 'text' ]
1904
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1905
                if 'Switch' in tags or 'LegacySwitch' in tags:
1906
                    opts = self.switchOpts[name]
1907
                    ctrlList = ",".join(opts['controllers'])
1908
                    f.write("    net.get('"+name+"').start(["+ctrlList+"])\n")
1909

    
1910
            f.write("\n")
1911

    
1912
            f.write("    info( '*** Post configure switches and hosts\\n')\n")
1913
            for widget in self.widgetToItem:
1914
                name = widget[ 'text' ]
1915
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1916
                if 'Switch' in tags:
1917
                    opts = self.switchOpts[name]
1918
                    if opts['switchType'] == 'default':
1919
                        if self.appPrefs['switchType'] == 'user':
1920
                            if ('switchIP' in opts):
1921
                                if (len(opts['switchIP'])>0):
1922
                                    f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1923
                        elif self.appPrefs['switchType'] == 'userns':
1924
                            if ('switchIP' in opts):
1925
                                if (len(opts['switchIP'])>0):
1926
                                    f.write("    "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n")
1927
                        elif self.appPrefs['switchType'] == 'ovs':
1928
                            if ('switchIP' in opts):
1929
                                if (len(opts['switchIP'])>0):
1930
                                    f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1931
                    elif opts['switchType'] == 'user':
1932
                        if ('switchIP' in opts):
1933
                            if (len(opts['switchIP'])>0):
1934
                                f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1935
                    elif opts['switchType'] == 'userns':
1936
                        if ('switchIP' in opts):
1937
                            if (len(opts['switchIP'])>0):
1938
                                f.write("    "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n")
1939
                    elif opts['switchType'] == 'ovs':
1940
                        if ('switchIP' in opts):
1941
                            if (len(opts['switchIP'])>0):
1942
                                f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1943
            for widget in self.widgetToItem:
1944
                name = widget[ 'text' ]
1945
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1946
                if 'Host' in tags:
1947
                    opts = self.hostOpts[name]
1948
                    # Attach vlan interfaces
1949
                    if ('vlanInterfaces' in opts):
1950
                        for vlanInterface in opts['vlanInterfaces']:
1951
                            f.write("    "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n")
1952
                            f.write("    "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n")
1953
                    # Run User Defined Start Command
1954
                    if ('startCommand' in opts):
1955
                        f.write("    "+name+".cmdPrint('"+opts['startCommand']+"')\n")
1956
                if 'Switch' in tags:
1957
                    opts = self.switchOpts[name]
1958
                    # Run User Defined Start Command
1959
                    if ('startCommand' in opts):
1960
                        f.write("    "+name+".cmdPrint('"+opts['startCommand']+"')\n")
1961

    
1962
            # Configure NetFlow
1963
            nflowValues = self.appPrefs['netflow']
1964
            if len(nflowValues['nflowTarget']) > 0:
1965
                nflowEnabled = False
1966
                nflowSwitches = ''
1967
                for widget in self.widgetToItem:
1968
                    name = widget[ 'text' ]
1969
                    tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1970
    
1971
                    if 'Switch' in tags:
1972
                        opts = self.switchOpts[name]
1973
                        if 'netflow' in opts:
1974
                            if opts['netflow'] == '1':
1975
                                nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF'
1976
                                nflowEnabled=True
1977
                if nflowEnabled:
1978
                    nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout']
1979
                    if nflowValues['nflowAddId'] == '1':
1980
                        nflowCmd = nflowCmd + ' add_id_to_interface=true'
1981
                    else:
1982
                        nflowCmd = nflowCmd + ' add_id_to_interface=false'
1983
                    f.write("    \n")
1984
                    f.write("    call('"+nflowCmd+nflowSwitches+"', shell=True)\n")
1985

    
1986
            # Configure sFlow
1987
            sflowValues = self.appPrefs['sflow']
1988
            if len(sflowValues['sflowTarget']) > 0:
1989
                sflowEnabled = False
1990
                sflowSwitches = ''
1991
                for widget in self.widgetToItem:
1992
                    name = widget[ 'text' ]
1993
                    tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1994
    
1995
                    if 'Switch' in tags:
1996
                        opts = self.switchOpts[name]
1997
                        if 'sflow' in opts:
1998
                            if opts['sflow'] == '1':
1999
                                sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
2000
                                sflowEnabled=True
2001
                if sflowEnabled:
2002
                    sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling']
2003
                    f.write("    \n")
2004
                    f.write("    call('"+sflowCmd+sflowSwitches+"', shell=True)\n")
2005

    
2006
            f.write("\n")
2007
            f.write("    CLI(net)\n")
2008
            for widget in self.widgetToItem:
2009
                name = widget[ 'text' ]
2010
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2011
                if 'Host' in tags:
2012
                    opts = self.hostOpts[name]
2013
                    # Run User Defined Stop Command
2014
                    if ('stopCommand' in opts):
2015
                        f.write("    "+name+".cmdPrint('"+opts['stopCommand']+"')\n")
2016
                if 'Switch' in tags:
2017
                    opts = self.switchOpts[name]
2018
                    # Run User Defined Stop Command
2019
                    if ('stopCommand' in opts):
2020
                        f.write("    "+name+".cmdPrint('"+opts['stopCommand']+"')\n")
2021

    
2022
            f.write("    net.stop()\n")
2023
            f.write("\n")
2024
            f.write("if __name__ == '__main__':\n")
2025
            f.write("    setLogLevel( 'info' )\n")
2026
            f.write("    myNetwork()\n")
2027
            f.write("\n")
2028

    
2029

    
2030
            f.close()
2031

    
2032

    
2033
    # Generic canvas handler
2034
    #
2035
    # We could have used bindtags, as in nodeIcon, but
2036
    # the dynamic approach used here
2037
    # may actually require less code. In any case, it's an
2038
    # interesting introspection-based alternative to bindtags.
2039

    
2040
    def canvasHandle( self, eventName, event ):
2041
        "Generic canvas event handler"
2042
        if self.active is None:
2043
            return
2044
        toolName = self.active
2045
        handler = getattr( self, eventName + toolName, None )
2046
        if handler is not None:
2047
            handler( event )
2048

    
2049
    def clickCanvas( self, event ):
2050
        "Canvas click handler."
2051
        self.canvasHandle( 'click', event )
2052

    
2053
    def dragCanvas( self, event ):
2054
        "Canvas drag handler."
2055
        self.canvasHandle( 'drag', event )
2056

    
2057
    def releaseCanvas( self, event ):
2058
        "Canvas mouse up handler."
2059
        self.canvasHandle( 'release', event )
2060

    
2061
    # Currently the only items we can select directly are
2062
    # links. Nodes are handled by bindings in the node icon.
2063

    
2064
    def findItem( self, x, y ):
2065
        "Find items at a location in our canvas."
2066
        items = self.canvas.find_overlapping( x, y, x, y )
2067
        if len( items ) == 0:
2068
            return None
2069
        else:
2070
            return items[ 0 ]
2071

    
2072
    # Canvas bindings for Select, Host, Switch and Link tools
2073

    
2074
    def clickSelect( self, event ):
2075
        "Select an item."
2076
        self.selectItem( self.findItem( event.x, event.y ) )
2077

    
2078
    def deleteItem( self, item ):
2079
        "Delete an item."
2080
        # Don't delete while network is running
2081
        if self.buttons[ 'Select' ][ 'state' ] == 'disabled':
2082
            return
2083
        # Delete from model
2084
        if item in self.links:
2085
            self.deleteLink( item )
2086
        if item in self.itemToWidget:
2087
            self.deleteNode( item )
2088
        # Delete from view
2089
        self.canvas.delete( item )
2090

    
2091
    def deleteSelection( self, _event ):
2092
        "Delete the selected item."
2093
        if self.selection is not None:
2094
            self.deleteItem( self.selection )
2095
        self.selectItem( None )
2096

    
2097
    def nodeIcon( self, node, name ):
2098
        "Create a new node icon."
2099
        icon = Button( self.canvas, image=self.images[ node ],
2100
                       text=name, compound='top' )
2101
        # Unfortunately bindtags wants a tuple
2102
        bindtags = [ str( self.nodeBindings ) ]
2103
        bindtags += list( icon.bindtags() )
2104
        icon.bindtags( tuple( bindtags ) )
2105
        return icon
2106

    
2107
    def newNode( self, node, event ):
2108
        "Add a new node to our canvas."
2109
        c = self.canvas
2110
        x, y = c.canvasx( event.x ), c.canvasy( event.y )
2111
        name = self.nodePrefixes[ node ]
2112
        if 'Switch' == node:
2113
            self.switchCount += 1
2114
            name = self.nodePrefixes[ node ] + str( self.switchCount )
2115
            self.switchOpts[name] = {}
2116
            self.switchOpts[name]['nodeNum']=self.switchCount
2117
            self.switchOpts[name]['hostname']=name
2118
            self.switchOpts[name]['switchType']='default'
2119
            self.switchOpts[name]['controllers']=[]
2120
        if 'LegacyRouter' == node:
2121
            self.switchCount += 1
2122
            name = self.nodePrefixes[ node ] + str( self.switchCount )
2123
            self.switchOpts[name] = {}
2124
            self.switchOpts[name]['nodeNum']=self.switchCount
2125
            self.switchOpts[name]['hostname']=name
2126
            self.switchOpts[name]['switchType']='legacyRouter'
2127
        if 'LegacySwitch' == node:
2128
            self.switchCount += 1
2129
            name = self.nodePrefixes[ node ] + str( self.switchCount )
2130
            self.switchOpts[name] = {}
2131
            self.switchOpts[name]['nodeNum']=self.switchCount
2132
            self.switchOpts[name]['hostname']=name
2133
            self.switchOpts[name]['switchType']='legacySwitch'
2134
            self.switchOpts[name]['controllers']=[]
2135
        if 'Host' == node:
2136
            self.hostCount += 1
2137
            name = self.nodePrefixes[ node ] + str( self.hostCount )
2138
            self.hostOpts[name] = {'sched':'host'}
2139
            self.hostOpts[name]['nodeNum']=self.hostCount
2140
            self.hostOpts[name]['hostname']=name
2141
        if 'Controller' == node:
2142
            name = self.nodePrefixes[ node ] + str( self.controllerCount )
2143
            ctrlr = { 'controllerType': 'ref',
2144
                      'hostname': name,
2145
                      'controllerProtocol': 'tcp',
2146
                      'remoteIP': '127.0.0.1',
2147
                      'remotePort': 6633}
2148
            self.controllers[name] = ctrlr
2149
            # We want to start controller count at 0
2150
            self.controllerCount += 1
2151

    
2152
        icon = self.nodeIcon( node, name )
2153
        item = self.canvas.create_window( x, y, anchor='c', window=icon,
2154
                                          tags=node )
2155
        self.widgetToItem[ icon ] = item
2156
        self.itemToWidget[ item ] = icon
2157
        self.selectItem( item )
2158
        icon.links = {}
2159
        if 'Switch' == node:
2160
            icon.bind('<Button-3>', self.do_switchPopup )
2161
        if 'LegacyRouter' == node:
2162
            icon.bind('<Button-3>', self.do_legacyRouterPopup )
2163
        if 'LegacySwitch' == node:
2164
            icon.bind('<Button-3>', self.do_legacySwitchPopup )
2165
        if 'Host' == node:
2166
            icon.bind('<Button-3>', self.do_hostPopup )
2167
        if 'Controller' == node:
2168
            icon.bind('<Button-3>', self.do_controllerPopup )
2169

    
2170
    def clickController( self, event ):
2171
        "Add a new Controller to our canvas."
2172
        self.newNode( 'Controller', event )
2173

    
2174
    def clickHost( self, event ):
2175
        "Add a new host to our canvas."
2176
        self.newNode( 'Host', event )
2177

    
2178
    def clickLegacyRouter( self, event ):
2179
        "Add a new switch to our canvas."
2180
        self.newNode( 'LegacyRouter', event )
2181

    
2182
    def clickLegacySwitch( self, event ):
2183
        "Add a new switch to our canvas."
2184
        self.newNode( 'LegacySwitch', event )
2185

    
2186
    def clickSwitch( self, event ):
2187
        "Add a new switch to our canvas."
2188
        self.newNode( 'Switch', event )
2189

    
2190
    def dragNetLink( self, event ):
2191
        "Drag a link's endpoint to another node."
2192
        if self.link is None:
2193
            return
2194
        # Since drag starts in widget, we use root coords
2195
        x = self.canvasx( event.x_root )
2196
        y = self.canvasy( event.y_root )
2197
        c = self.canvas
2198
        c.coords( self.link, self.linkx, self.linky, x, y )
2199

    
2200
    def releaseNetLink( self, _event ):
2201
        "Give up on the current link."
2202
        if self.link is not None:
2203
            self.canvas.delete( self.link )
2204
        self.linkWidget = self.linkItem = self.link = None
2205

    
2206
    # Generic node handlers
2207

    
2208
    def createNodeBindings( self ):
2209
        "Create a set of bindings for nodes."
2210
        bindings = {
2211
            '<ButtonPress-1>': self.clickNode,
2212
            '<B1-Motion>': self.dragNode,
2213
            '<ButtonRelease-1>': self.releaseNode,
2214
            '<Enter>': self.enterNode,
2215
            '<Leave>': self.leaveNode
2216
        }
2217
        l = Label()  # lightweight-ish owner for bindings
2218
        for event, binding in bindings.items():
2219
            l.bind( event, binding )
2220
        return l
2221

    
2222
    def selectItem( self, item ):
2223
        "Select an item and remember old selection."
2224
        self.lastSelection = self.selection
2225
        self.selection = item
2226

    
2227
    def enterNode( self, event ):
2228
        "Select node on entry."
2229
        self.selectNode( event )
2230

    
2231
    def leaveNode( self, _event ):
2232
        "Restore old selection on exit."
2233
        self.selectItem( self.lastSelection )
2234

    
2235
    def clickNode( self, event ):
2236
        "Node click handler."
2237
        if self.active is 'NetLink':
2238
            self.startLink( event )
2239
        else:
2240
            self.selectNode( event )
2241
        return 'break'
2242

    
2243
    def dragNode( self, event ):
2244
        "Node drag handler."
2245
        if self.active is 'NetLink':
2246
            self.dragNetLink( event )
2247
        else:
2248
            self.dragNodeAround( event )
2249

    
2250
    def releaseNode( self, event ):
2251
        "Node release handler."
2252
        if self.active is 'NetLink':
2253
            self.finishLink( event )
2254

    
2255
    # Specific node handlers
2256

    
2257
    def selectNode( self, event ):
2258
        "Select the node that was clicked on."
2259
        item = self.widgetToItem.get( event.widget, None )
2260
        self.selectItem( item )
2261

    
2262
    def dragNodeAround( self, event ):
2263
        "Drag a node around on the canvas."
2264
        c = self.canvas
2265
        # Convert global to local coordinates;
2266
        # Necessary since x, y are widget-relative
2267
        x = self.canvasx( event.x_root )
2268
        y = self.canvasy( event.y_root )
2269
        w = event.widget
2270
        # Adjust node position
2271
        item = self.widgetToItem[ w ]
2272
        c.coords( item, x, y )
2273
        # Adjust link positions
2274
        for dest in w.links:
2275
            link = w.links[ dest ]
2276
            item = self.widgetToItem[ dest ]
2277
            x1, y1 = c.coords( item )
2278
            c.coords( link, x, y, x1, y1 )
2279
        self.updateScrollRegion()
2280

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

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

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

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

    
2301
        self.canvas.tag_bind( self.link, '<Enter>', highlight )
2302
        self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
2303
        self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
2304

    
2305
    def createDataLinkBindings( self ):
2306
        "Create a set of bindings for nodes."
2307
        # Link bindings
2308
        # Selection still needs a bit of work overall
2309
        # Callbacks ignore event
2310

    
2311
        def select( _event, link=self.link ):
2312
            "Select item on mouse entry."
2313
            self.selectItem( link )
2314

    
2315
        def highlight( _event, link=self.link ):
2316
            "Highlight item on mouse entry."
2317
            self.selectItem( link )
2318
            self.canvas.itemconfig( link, fill='green' )
2319

    
2320
        def unhighlight( _event, link=self.link ):
2321
            "Unhighlight item on mouse exit."
2322
            self.canvas.itemconfig( link, fill='blue' )
2323
            #self.selectItem( None )
2324

    
2325
        self.canvas.tag_bind( self.link, '<Enter>', highlight )
2326
        self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
2327
        self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
2328
        self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup )
2329

    
2330

    
2331
    def startLink( self, event ):
2332
        "Start a new link."
2333
        if event.widget not in self.widgetToItem:
2334
            # Didn't click on a node
2335
            return
2336

    
2337
        w = event.widget
2338
        item = self.widgetToItem[ w ]
2339
        x, y = self.canvas.coords( item )
2340
        self.link = self.canvas.create_line( x, y, x, y, width=4,
2341
                                             fill='blue', tag='link' )
2342
        self.linkx, self.linky = x, y
2343
        self.linkWidget = w
2344
        self.linkItem = item
2345

    
2346

    
2347
    def finishLink( self, event ):
2348
        "Finish creating a link"
2349
        if self.link is None:
2350
            return
2351
        source = self.linkWidget
2352
        c = self.canvas
2353
        # Since we dragged from the widget, use root coords
2354
        x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root )
2355
        target = self.findItem( x, y )
2356
        dest = self.itemToWidget.get( target, None )
2357
        if ( source is None or dest is None or source == dest
2358
                or dest in source.links or source in dest.links ):
2359
            self.releaseNetLink( event )
2360
            return
2361
        # For now, don't allow hosts to be directly linked
2362
        stags = self.canvas.gettags( self.widgetToItem[ source ] )
2363
        dtags = self.canvas.gettags( target )
2364
        if (('Host' in stags and 'Host' in dtags) or
2365
           ('Controller' in dtags and 'LegacyRouter' in stags) or
2366
           ('Controller' in stags and 'LegacyRouter' in dtags) or
2367
           ('Controller' in dtags and 'LegacySwitch' in stags) or
2368
           ('Controller' in stags and 'LegacySwitch' in dtags) or
2369
           ('Controller' in dtags and 'Host' in stags) or
2370
           ('Controller' in stags and 'Host' in dtags) or
2371
           ('Controller' in stags and 'Controller' in dtags)):
2372
            self.releaseNetLink( event )
2373
            return
2374

    
2375
        # Set link type
2376
        linkType='data'
2377
        if 'Controller' in stags or 'Controller' in dtags:
2378
            linkType='control'
2379
            c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red')
2380
            self.createControlLinkBindings()
2381
        else:
2382
            linkType='data'
2383
            self.createDataLinkBindings()
2384
        c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,))
2385

    
2386
        x, y = c.coords( target )
2387
        c.coords( self.link, self.linkx, self.linky, x, y )
2388
        self.addLink( source, dest, linktype=linkType )
2389
        if linkType == 'control':
2390
            controllerName = ''
2391
            switchName = ''
2392
            if 'Controller' in stags:
2393
                controllerName = source[ 'text' ]
2394
                switchName = dest[ 'text' ]
2395
            else:
2396
                controllerName = dest[ 'text' ]
2397
                switchName = source[ 'text' ]
2398

    
2399
            self.switchOpts[switchName]['controllers'].append(controllerName)
2400

    
2401
        # We're done
2402
        self.link = self.linkWidget = None
2403

    
2404
    # Menu handlers
2405

    
2406
    def about( self ):
2407
        "Display about box."
2408
        about = self.aboutBox
2409
        if about is None:
2410
            bg = 'white'
2411
            about = Toplevel( bg='white' )
2412
            about.title( 'About' )
2413
            info = self.appName + ': a simple network editor for MiniNet'
2414
            version = 'MiniEdit '+MINIEDIT_VERSION
2415
            author = 'Originally by: Bob Lantz <rlantz@cs>, April 2010'
2416
            enhancements = 'Enhancements by: Gregory Gee, Since July 2013'
2417
            www = 'http://gregorygee.wordpress.com/category/miniedit/'
2418
            line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg )
2419
            line2 = Label( about, text=version, font='Helvetica 9', bg=bg )
2420
            line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
2421
            line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg )
2422
            line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER )
2423
            line5.insert(0, www)
2424
            line5.configure(state='readonly')
2425
            line1.pack( padx=20, pady=10 )
2426
            line2.pack(pady=10 )
2427
            line3.pack(pady=10 )
2428
            line4.pack(pady=10 )
2429
            line5.pack(pady=10 )
2430
            hide = ( lambda about=about: about.withdraw() )
2431
            self.aboutBox = about
2432
            # Hide on close rather than destroying window
2433
            Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
2434
        # Show (existing) window
2435
        about.deiconify()
2436

    
2437
    def createToolImages( self ):
2438
        "Create toolbar (and icon) images."
2439

    
2440
    def checkIntf( self, intf ):
2441
        "Make sure intf exists and is not configured."
2442
        if ( ' %s:' % intf ) not in quietRun( 'ip link show' ):
2443
            showerror(title="Error",
2444
                      message='External interface ' +intf + ' does not exist! Skipping.')
2445
            return False
2446
        ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) )
2447
        if ips:
2448
            showerror(title="Error",
2449
                      message= intf + ' has an IP address and is probably in use! Skipping.' )
2450
            return False
2451
        return True
2452

    
2453
    def hostDetails( self, _ignore=None ):
2454
        if ( self.selection is None or
2455
             self.net is not None or
2456
             self.selection not in self.itemToWidget ):
2457
            return
2458
        widget = self.itemToWidget[ self.selection ]
2459
        name = widget[ 'text' ]
2460
        tags = self.canvas.gettags( self.selection )
2461
        if 'Host' not in tags:
2462
            return
2463

    
2464
        prefDefaults = self.hostOpts[name]
2465
        hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults)
2466
        self.master.wait_window(hostBox.top)
2467
        if hostBox.result:
2468
            newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']}
2469
            newHostOpts['sched'] = hostBox.result['sched']
2470
            if len(hostBox.result['startCommand']) > 0:
2471
                newHostOpts['startCommand'] = hostBox.result['startCommand']
2472
            if len(hostBox.result['stopCommand']) > 0:
2473
                newHostOpts['stopCommand'] = hostBox.result['stopCommand']
2474
            if len(hostBox.result['cpu']) > 0:
2475
                newHostOpts['cpu'] = float(hostBox.result['cpu'])
2476
            if len(hostBox.result['cores']) > 0:
2477
                newHostOpts['cores'] = hostBox.result['cores']
2478
            if len(hostBox.result['hostname']) > 0:
2479
                newHostOpts['hostname'] = hostBox.result['hostname']
2480
                name = hostBox.result['hostname']
2481
                widget[ 'text' ] = name
2482
            if len(hostBox.result['defaultRoute']) > 0:
2483
                newHostOpts['defaultRoute'] = hostBox.result['defaultRoute']
2484
            if len(hostBox.result['ip']) > 0:
2485
                newHostOpts['ip'] = hostBox.result['ip']
2486
            if len(hostBox.result['externalInterfaces']) > 0:
2487
                newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces']
2488
            if len(hostBox.result['vlanInterfaces']) > 0:
2489
                newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces']
2490
            if len(hostBox.result['privateDirectory']) > 0:
2491
                newHostOpts['privateDirectory'] = hostBox.result['privateDirectory']
2492
            self.hostOpts[name] = newHostOpts
2493
            print 'New host details for ' + name + ' = ' + str(newHostOpts)
2494

    
2495
    def switchDetails( self, _ignore=None ):
2496
        if ( self.selection is None or
2497
             self.net is not None or
2498
             self.selection not in self.itemToWidget ):
2499
            return
2500
        widget = self.itemToWidget[ self.selection ]
2501
        name = widget[ 'text' ]
2502
        tags = self.canvas.gettags( self.selection )
2503
        if 'Switch' not in tags:
2504
            return
2505

    
2506
        prefDefaults = self.switchOpts[name]
2507
        switchBox = SwitchDialog(self, title='Switch Details', prefDefaults=prefDefaults)
2508
        self.master.wait_window(switchBox.top)
2509
        if switchBox.result:
2510
            newSwitchOpts = {'nodeNum':self.switchOpts[name]['nodeNum']}
2511
            newSwitchOpts['switchType'] = switchBox.result['switchType']
2512
            newSwitchOpts['controllers'] = self.switchOpts[name]['controllers']
2513
            if len(switchBox.result['startCommand']) > 0:
2514
                newSwitchOpts['startCommand'] = switchBox.result['startCommand']
2515
            if len(switchBox.result['stopCommand']) > 0:
2516
                newSwitchOpts['stopCommand'] = switchBox.result['stopCommand']
2517
            if len(switchBox.result['dpctl']) > 0:
2518
                newSwitchOpts['dpctl'] = switchBox.result['dpctl']
2519
            if len(switchBox.result['dpid']) > 0:
2520
                newSwitchOpts['dpid'] = switchBox.result['dpid']
2521
            if len(switchBox.result['hostname']) > 0:
2522
                newSwitchOpts['hostname'] = switchBox.result['hostname']
2523
                name = switchBox.result['hostname']
2524
                widget[ 'text' ] = name
2525
            if len(switchBox.result['externalInterfaces']) > 0:
2526
                newSwitchOpts['externalInterfaces'] = switchBox.result['externalInterfaces']
2527
            newSwitchOpts['switchIP'] = switchBox.result['switchIP']
2528
            newSwitchOpts['sflow'] = switchBox.result['sflow']
2529
            newSwitchOpts['netflow'] = switchBox.result['netflow']
2530
            self.switchOpts[name] = newSwitchOpts
2531
            print 'New switch details for ' + name + ' = ' + str(newSwitchOpts)
2532

    
2533
    def linkUp( self ):
2534
        if ( self.selection is None or
2535
             self.net is None):
2536
            return
2537
        link = self.selection
2538
        linkDetail =  self.links[link]
2539
        src = linkDetail['src']
2540
        dst = linkDetail['dest']
2541
        srcName, dstName = src[ 'text' ], dst[ 'text' ]
2542
        self.net.configLinkStatus(srcName, dstName, 'up')
2543
        self.canvas.itemconfig(link, dash=())
2544

    
2545
    def linkDown( self ):
2546
        if ( self.selection is None or
2547
             self.net is None):
2548
            return
2549
        link = self.selection
2550
        linkDetail =  self.links[link]
2551
        src = linkDetail['src']
2552
        dst = linkDetail['dest']
2553
        srcName, dstName = src[ 'text' ], dst[ 'text' ]
2554
        self.net.configLinkStatus(srcName, dstName, 'down')
2555
        self.canvas.itemconfig(link, dash=(4, 4))
2556

    
2557
    def linkDetails( self, _ignore=None ):
2558
        if ( self.selection is None or
2559
             self.net is not None):
2560
            return
2561
        link = self.selection
2562

    
2563
        linkDetail =  self.links[link]
2564
        src = linkDetail['src']
2565
        dest = linkDetail['dest']
2566
        linkopts = linkDetail['linkOpts']
2567
        linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
2568
        if linkBox.result is not None:
2569
            linkDetail['linkOpts'] = linkBox.result
2570
            print 'New link details = ' + str(linkBox.result)
2571

    
2572
    def prefDetails( self ):
2573
        prefDefaults = self.appPrefs
2574
        prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults)
2575
        print 'New Prefs = ' + str(prefBox.result)
2576
        if prefBox.result:
2577
            self.appPrefs = prefBox.result
2578

    
2579

    
2580
    def controllerDetails( self ):
2581
        if ( self.selection is None or
2582
             self.net is not None or
2583
             self.selection not in self.itemToWidget ):
2584
            return
2585
        widget = self.itemToWidget[ self.selection ]
2586
        name = widget[ 'text' ]
2587
        tags = self.canvas.gettags( self.selection )
2588
        oldName = name
2589
        if 'Controller' not in tags:
2590
            return
2591

    
2592
        ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name])
2593
        if ctrlrBox.result:
2594
            #print 'Controller is ' + ctrlrBox.result[0]
2595
            if len(ctrlrBox.result['hostname']) > 0:
2596
                name = ctrlrBox.result['hostname']
2597
                widget[ 'text' ] = name
2598
            else:
2599
                ctrlrBox.result['hostname'] = name
2600
            self.controllers[name] = ctrlrBox.result
2601
            print 'New controller details for ' + name + ' = ' + str(self.controllers[name])
2602
            # Find references to controller and change name
2603
            if oldName != name:
2604
                for widget in self.widgetToItem:
2605
                    switchName = widget[ 'text' ]
2606
                    tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2607
                    if 'Switch' in tags:
2608
                        switch = self.switchOpts[switchName]
2609
                        if oldName in switch['controllers']:
2610
                            switch['controllers'].remove(oldName)
2611
                            switch['controllers'].append(name)
2612

    
2613

    
2614
    def listBridge( self, _ignore=None ):
2615
        if ( self.selection is None or
2616
             self.net is None or
2617
             self.selection not in self.itemToWidget ):
2618
            return
2619
        name = self.itemToWidget[ self.selection ][ 'text' ]
2620
        tags = self.canvas.gettags( self.selection )
2621

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

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

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

    
2633
    # Model interface
2634
    #
2635
    # Ultimately we will either want to use a topo or
2636
    # mininet object here, probably.
2637

    
2638
    def addLink( self, source, dest, linktype='data', linkopts={} ):
2639
        "Add link to model."
2640
        source.links[ dest ] = self.link
2641
        dest.links[ source ] = self.link
2642
        self.links[ self.link ] = {'type' :linktype,
2643
                                   'src':source,
2644
                                   'dest':dest,
2645
                                   'linkOpts':linkopts}
2646

    
2647
    def deleteLink( self, link ):
2648
        "Delete link from model."
2649
        pair = self.links.get( link, None )
2650
        if pair is not None:
2651
            source=pair['src']
2652
            dest=pair['dest']
2653
            del source.links[ dest ]
2654
            del dest.links[ source ]
2655
            stags = self.canvas.gettags( self.widgetToItem[ source ] )
2656
            dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
2657
            ltags = self.canvas.gettags( link )
2658

    
2659
            if 'control' in ltags:
2660
                controllerName = ''
2661
                switchName = ''
2662
                if 'Controller' in stags:
2663
                    controllerName = source[ 'text' ]
2664
                    switchName = dest[ 'text' ]
2665
                else:
2666
                    controllerName = dest[ 'text' ]
2667
                    switchName = source[ 'text' ]
2668
    
2669
                if controllerName in self.switchOpts[switchName]['controllers']:
2670
                    self.switchOpts[switchName]['controllers'].remove(controllerName)
2671

    
2672

    
2673
        if link is not None:
2674
            del self.links[ link ]
2675

    
2676
    def deleteNode( self, item ):
2677
        "Delete node (and its links) from model."
2678

    
2679
        widget = self.itemToWidget[ item ]
2680
        tags = self.canvas.gettags(item)
2681
        if 'Controller' in tags:
2682
            # remove from switch controller lists
2683
            for serachwidget in self.widgetToItem:
2684
                name = serachwidget[ 'text' ]
2685
                tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] )
2686
                if 'Switch' in tags:
2687
                    if widget['text'] in self.switchOpts[name]['controllers']:
2688
                        self.switchOpts[name]['controllers'].remove(widget['text'])
2689
            
2690
        for link in widget.links.values():
2691
            # Delete from view and model
2692
            self.deleteItem( link )
2693
        del self.itemToWidget[ item ]
2694
        del self.widgetToItem[ widget ]
2695

    
2696
    def buildNodes( self, net):
2697
        # Make nodes
2698
        print "Getting Hosts and Switches."
2699
        for widget in self.widgetToItem:
2700
            name = widget[ 'text' ]
2701
            tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2702
            #print name+' has '+str(tags)
2703

    
2704
            if 'Switch' in tags:
2705
                opts = self.switchOpts[name]
2706
                #print str(opts)
2707

    
2708
                # Create the correct switch class
2709
                switchClass = customOvs
2710
                switchParms={}
2711
                if 'dpctl' in opts:
2712
                    switchParms['listenPort']=int(opts['dpctl'])
2713
                if 'dpid' in opts:
2714
                    switchParms['dpid']=opts['dpid']
2715
                if opts['switchType'] == 'default':
2716
                    if self.appPrefs['switchType'] == 'ivs':
2717
                        switchClass = IVSSwitch
2718
                    elif self.appPrefs['switchType'] == 'user':
2719
                        switchClass = CustomUserSwitch
2720
                    elif self.appPrefs['switchType'] == 'userns':
2721
                        switchParms['inNamespace'] = True
2722
                        switchClass = CustomUserSwitch
2723
                    else:
2724
                        switchClass = customOvs
2725
                elif opts['switchType'] == 'user':
2726
                    switchClass = CustomUserSwitch
2727
                elif opts['switchType'] == 'userns':
2728
                    switchClass = CustomUserSwitch
2729
                    switchParms['inNamespace'] = True
2730
                elif opts['switchType'] == 'ivs':
2731
                    switchClass = IVSSwitch
2732
                else:
2733
                    switchClass = customOvs
2734

    
2735
                if switchClass == customOvs:
2736
                    # Set OpenFlow versions
2737
                    self.openFlowVersions = []
2738
                    if self.appPrefs['openFlowVersions']['ovsOf10'] == '1':
2739
                        self.openFlowVersions.append('OpenFlow10')
2740
                    if self.appPrefs['openFlowVersions']['ovsOf11'] == '1':
2741
                        self.openFlowVersions.append('OpenFlow11')
2742
                    if self.appPrefs['openFlowVersions']['ovsOf12'] == '1':
2743
                        self.openFlowVersions.append('OpenFlow12')
2744
                    if self.appPrefs['openFlowVersions']['ovsOf13'] == '1':
2745
                        self.openFlowVersions.append('OpenFlow13')
2746
                    protoList = ",".join(self.openFlowVersions)
2747
                    switchParms['protocols'] = protoList
2748
                newSwitch = net.addSwitch( name , cls=switchClass, **switchParms)
2749

    
2750
                # Some post startup config
2751
                if switchClass == CustomUserSwitch:
2752
                    if ('switchIP' in opts):
2753
                        if (len(opts['switchIP']) > 0):
2754
                            newSwitch.setSwitchIP(opts['switchIP'])
2755
                if switchClass == customOvs:
2756
                    if ('switchIP' in opts):
2757
                        if (len(opts['switchIP']) > 0):
2758
                            newSwitch.setSwitchIP(opts['switchIP'])
2759

    
2760
                # Attach external interfaces
2761
                if ('externalInterfaces' in opts):
2762
                    for extInterface in opts['externalInterfaces']:
2763
                        if self.checkIntf(extInterface):
2764
                           Intf( extInterface, node=newSwitch )
2765

    
2766
            elif 'LegacySwitch' in tags:
2767
                newSwitch = net.addSwitch( name , cls=LegacySwitch)
2768
            elif 'LegacyRouter' in tags:
2769
                newSwitch = net.addHost( name , cls=LegacyRouter)
2770
            elif 'Host' in tags:
2771
                opts = self.hostOpts[name]
2772
                #print str(opts)
2773
                ip = None
2774
                defaultRoute = None
2775
                if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
2776
                    defaultRoute = 'via '+opts['defaultRoute']
2777
                if 'ip' in opts and len(opts['ip']) > 0:
2778
                    ip = opts['ip']
2779
                else:
2780
                    nodeNum = self.hostOpts[name]['nodeNum']
2781
                    ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
2782
                    ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
2783

    
2784
                # Create the correct host class
2785
                if 'cores' in opts or 'cpu' in opts:
2786
                    if ('privateDirectory' in opts):
2787
                        hostCls = partial( CPULimitedHostWithPrivateDirs,
2788
                                           privateDirs=opts['privateDirectory'] )
2789
                    else:
2790
                        hostCls=CPULimitedHost
2791
                else:
2792
                    if ('privateDirectory' in opts):
2793
                        hostCls = partial( HostWithPrivateDirs,
2794
                                           privateDirs=opts['privateDirectory'] )
2795
                    else:
2796
                        hostCls=Host
2797
                newHost = net.addHost( name,
2798
                                       cls=hostCls,
2799
                                       ip=ip,
2800
                                       defaultRoute=defaultRoute
2801
                                      )
2802

    
2803
                # Set the CPULimitedHost specific options
2804
                if 'cores' in opts:
2805
                    newHost.setCPUs(cores = opts['cores'])
2806
                if 'cpu' in opts:
2807
                    newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched'])
2808

    
2809
                # Attach external interfaces
2810
                if ('externalInterfaces' in opts):
2811
                    for extInterface in opts['externalInterfaces']:
2812
                        if self.checkIntf(extInterface):
2813
                           Intf( extInterface, node=newHost )
2814
                if ('vlanInterfaces' in opts):
2815
                    if len(opts['vlanInterfaces']) > 0:
2816
                        print 'Checking that OS is VLAN prepared'
2817
                        self.pathCheck('vconfig', moduleName='vlan package')
2818
                        moduleDeps( add='8021q' )
2819
            elif 'Controller' in tags:
2820
                opts = self.controllers[name]
2821

    
2822
                # Get controller info from panel
2823
                controllerType = opts['controllerType']
2824
                if 'controllerProtocol' in opts:
2825
                    controllerProtocol = opts['controllerProtocol']
2826
                else:
2827
                    controllerProtocol = 'tcp'
2828
                    opts['controllerProtocol'] = 'tcp'
2829
                controllerIP = opts['remoteIP']
2830
                controllerPort = opts['remotePort']
2831

    
2832
                # Make controller
2833
                print 'Getting controller selection:'+controllerType
2834
                if controllerType == 'remote':
2835
                    net.addController(name=name,
2836
                                      controller=RemoteController,
2837
                                      ip=controllerIP,
2838
                                      protocol=controllerProtocol,
2839
                                      port=controllerPort)
2840
                elif controllerType == 'inband':
2841
                    net.addController(name=name,
2842
                                      controller=InbandController,
2843
                                      ip=controllerIP,
2844
                                      protocol=controllerProtocol,
2845
                                      port=controllerPort)
2846
                elif controllerType == 'ovsc':
2847
                    net.addController(name=name,
2848
                                      controller=OVSController,
2849
                                      protocol=controllerProtocol,
2850
                                      port=controllerPort)
2851
                else:
2852
                    net.addController(name=name,
2853
                                      controller=Controller,
2854
                                      protocol=controllerProtocol,
2855
                                      port=controllerPort)
2856

    
2857
            else:
2858
                raise Exception( "Cannot create mystery node: " + name )
2859

    
2860
    def pathCheck( self, *args, **kwargs ):
2861
        "Make sure each program in *args can be found in $PATH."
2862
        moduleName = kwargs.get( 'moduleName', 'it' )
2863
        for arg in args:
2864
            if not quietRun( 'which ' + arg ):
2865
                showerror(title="Error",
2866
                      message= 'Cannot find required executable %s.\n' % arg +
2867
                       'Please make sure that %s is installed ' % moduleName +
2868
                       'and available in your $PATH.' )
2869

    
2870
    def buildLinks( self, net):
2871
        # Make links
2872
        print "Getting Links."
2873
        for key,link in self.links.iteritems():
2874
            tags = self.canvas.gettags(key)
2875
            if 'data' in tags:
2876
                src=link['src']
2877
                dst=link['dest']
2878
                linkopts=link['linkOpts']
2879
                srcName, dstName = src[ 'text' ], dst[ 'text' ]
2880
                srcNode, dstNode = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
2881
                if linkopts:
2882
                    net.addLink(srcNode, dstNode, cls=TCLink, **linkopts)
2883
                else:
2884
                    #print str(srcNode)
2885
                    #print str(dstNode)
2886
                    net.addLink(srcNode, dstNode)
2887
                self.canvas.itemconfig(key, dash=())
2888

    
2889

    
2890
    def build( self ):
2891
        print "Build network based on our topology."
2892

    
2893
        dpctl = None
2894
        if len(self.appPrefs['dpctl']) > 0:
2895
            dpctl = int(self.appPrefs['dpctl'])
2896
        net = Mininet( topo=None,
2897
                       listenPort=dpctl,
2898
                       build=False,
2899
                       ipBase=self.appPrefs['ipBase'] )
2900

    
2901
        self.buildNodes(net)
2902
        self.buildLinks(net)
2903

    
2904
        # Build network (we have to do this separately at the moment )
2905
        net.build()
2906

    
2907
        return net
2908

    
2909

    
2910
    def postStartSetup( self ):
2911

    
2912
        # Setup host details
2913
        for widget in self.widgetToItem:
2914
            name = widget[ 'text' ]
2915
            tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2916
            if 'Host' in tags:
2917
                newHost = self.net.get(name)
2918
                opts = self.hostOpts[name]
2919
                # Attach vlan interfaces
2920
                if ('vlanInterfaces' in opts):
2921
                    for vlanInterface in opts['vlanInterfaces']:
2922
                        print 'adding vlan interface '+vlanInterface[1]
2923
                        newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0])
2924
                # Run User Defined Start Command
2925
                if ('startCommand' in opts):
2926
                    newHost.cmdPrint(opts['startCommand'])
2927
            if 'Switch' in tags:
2928
                newNode = self.net.get(name)
2929
                opts = self.switchOpts[name]
2930
                # Run User Defined Start Command
2931
                if ('startCommand' in opts):
2932
                    newNode.cmdPrint(opts['startCommand'])
2933

    
2934

    
2935
        # Configure NetFlow
2936
        nflowValues = self.appPrefs['netflow']
2937
        if len(nflowValues['nflowTarget']) > 0:
2938
            nflowEnabled = False
2939
            nflowSwitches = ''
2940
            for widget in self.widgetToItem:
2941
                name = widget[ 'text' ]
2942
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2943
    
2944
                if 'Switch' in tags:
2945
                    opts = self.switchOpts[name]
2946
                    if 'netflow' in opts:
2947
                        if opts['netflow'] == '1':
2948
                            print name+' has Netflow enabled'
2949
                            nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF'
2950
                            nflowEnabled=True
2951
            if nflowEnabled:
2952
                nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout']
2953
                if nflowValues['nflowAddId'] == '1':
2954
                    nflowCmd = nflowCmd + ' add_id_to_interface=true'
2955
                else:
2956
                    nflowCmd = nflowCmd + ' add_id_to_interface=false'
2957
                print 'cmd = '+nflowCmd+nflowSwitches
2958
                call(nflowCmd+nflowSwitches, shell=True)
2959

    
2960
            else:
2961
                print 'No switches with Netflow'
2962
        else:
2963
            print 'No NetFlow targets specified.'
2964

    
2965
        # Configure sFlow
2966
        sflowValues = self.appPrefs['sflow']
2967
        if len(sflowValues['sflowTarget']) > 0:
2968
            sflowEnabled = False
2969
            sflowSwitches = ''
2970
            for widget in self.widgetToItem:
2971
                name = widget[ 'text' ]
2972
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2973
    
2974
                if 'Switch' in tags:
2975
                    opts = self.switchOpts[name]
2976
                    if 'sflow' in opts:
2977
                        if opts['sflow'] == '1':
2978
                            print name+' has sflow enabled'
2979
                            sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
2980
                            sflowEnabled=True
2981
            if sflowEnabled:
2982
                sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling']
2983
                print 'cmd = '+sflowCmd+sflowSwitches
2984
                call(sflowCmd+sflowSwitches, shell=True)
2985

    
2986
            else:
2987
                print 'No switches with sflow'
2988
        else:
2989
            print 'No sFlow targets specified.'
2990

    
2991
        ## NOTE: MAKE SURE THIS IS LAST THING CALLED
2992
        # Start the CLI if enabled
2993
        if self.appPrefs['startCLI'] == '1':
2994
            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")
2995
            CLI(self.net)
2996

    
2997
    def start( self ):
2998
        "Start network."
2999
        if self.net is None:
3000
            self.net = self.build()
3001

    
3002
            # Since I am going to inject per switch controllers.
3003
            # I can't call net.start().  I have to replicate what it
3004
            # does and add the controller options.
3005
            #self.net.start()
3006
            info( '**** Starting %s controllers\n' % len( self.net.controllers ) )
3007
            for controller in self.net.controllers:
3008
                info( str(controller) + ' ')
3009
                controller.start()
3010
            info('\n')
3011
            info( '**** Starting %s switches\n' % len( self.net.switches ) )
3012
            #for switch in self.net.switches:
3013
            #    info( switch.name + ' ')
3014
            #    switch.start( self.net.controllers )
3015
            for widget in self.widgetToItem:
3016
                name = widget[ 'text' ]
3017
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
3018
                if 'Switch' in tags:
3019
                    opts = self.switchOpts[name]
3020
                    switchControllers = []
3021
                    for ctrl in opts['controllers']:
3022
                        switchControllers.append(self.net.get(ctrl))
3023
                    info( name + ' ')
3024
                    # Figure out what controllers will manage this switch
3025
                    self.net.get(name).start( switchControllers )
3026
                if 'LegacySwitch' in tags:
3027
                    self.net.get(name).start( [] )
3028
                    info( name + ' ')
3029
            info('\n')
3030

    
3031
            self.postStartSetup()
3032

    
3033
    def stop( self ):
3034
        "Stop network."
3035
        if self.net is not None:
3036
            # Stop host details
3037
            for widget in self.widgetToItem:
3038
                name = widget[ 'text' ]
3039
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
3040
                if 'Host' in tags:
3041
                    newHost = self.net.get(name)
3042
                    opts = self.hostOpts[name]
3043
                    # Run User Defined Stop Command
3044
                    if ('stopCommand' in opts):
3045
                        newHost.cmdPrint(opts['stopCommand'])
3046
                if 'Switch' in tags:
3047
                    newNode = self.net.get(name)
3048
                    opts = self.switchOpts[name]
3049
                    # Run User Defined Stop Command
3050
                    if ('stopCommand' in opts):
3051
                        newNode.cmdPrint(opts['stopCommand'])
3052

    
3053
            self.net.stop()
3054
        cleanUpScreens()
3055
        self.net = None
3056

    
3057
    def do_linkPopup(self, event):
3058
        # display the popup menu
3059
        if ( self.net is None ):
3060
            try:
3061
                self.linkPopup.tk_popup(event.x_root, event.y_root, 0)
3062
            finally:
3063
                # make sure to release the grab (Tk 8.0a1 only)
3064
                self.linkPopup.grab_release()
3065
        else:
3066
            try:
3067
                self.linkRunPopup.tk_popup(event.x_root, event.y_root, 0)
3068
            finally:
3069
                # make sure to release the grab (Tk 8.0a1 only)
3070
                self.linkRunPopup.grab_release()
3071

    
3072
    def do_controllerPopup(self, event):
3073
        # display the popup menu
3074
        if ( self.net is None ):
3075
            try:
3076
                self.controllerPopup.tk_popup(event.x_root, event.y_root, 0)
3077
            finally:
3078
                # make sure to release the grab (Tk 8.0a1 only)
3079
                self.controllerPopup.grab_release()
3080

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

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

    
3105
    def do_legacySwitchPopup(self, event):
3106
        # display the popup menu
3107
        if ( self.net is not None ):
3108
            try:
3109
                self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
3110
            finally:
3111
                # make sure to release the grab (Tk 8.0a1 only)
3112
                self.switchRunPopup.grab_release()
3113

    
3114
    def do_switchPopup(self, event):
3115
        # display the popup menu
3116
        if ( self.net is None ):
3117
            try:
3118
                self.switchPopup.tk_popup(event.x_root, event.y_root, 0)
3119
            finally:
3120
                # make sure to release the grab (Tk 8.0a1 only)
3121
                self.switchPopup.grab_release()
3122
        else:
3123
            try:
3124
                self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
3125
            finally:
3126
                # make sure to release the grab (Tk 8.0a1 only)
3127
                self.switchRunPopup.grab_release()
3128

    
3129
    def xterm( self, _ignore=None ):
3130
        "Make an xterm when a button is pressed."
3131
        if ( self.selection is None or
3132
             self.net is None or
3133
             self.selection not in self.itemToWidget ):
3134
            return
3135
        name = self.itemToWidget[ self.selection ][ 'text' ]
3136
        if name not in self.net.nameToNode:
3137
            return
3138
        term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] )
3139
        if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
3140
            self.net.terms += term
3141
        else:
3142
            self.net.terms.append(term)
3143

    
3144
    def iperf( self, _ignore=None ):
3145
        "Make an xterm when a button is pressed."
3146
        if ( self.selection is None or
3147
             self.net is None or
3148
             self.selection not in self.itemToWidget ):
3149
            return
3150
        name = self.itemToWidget[ self.selection ][ 'text' ]
3151
        if name not in self.net.nameToNode:
3152
            return
3153
        self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' )
3154

    
3155
    """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """
3156

    
3157
    def parseArgs( self ):
3158
        """Parse command-line args and return options object.
3159
           returns: opts parse options dict"""
3160

    
3161
        if '--custom' in sys.argv:
3162
            index = sys.argv.index( '--custom' )
3163
            if len( sys.argv ) > index + 1:
3164
                filename = sys.argv[ index + 1 ]
3165
                self.parseCustomFile( filename )
3166
            else:
3167
                raise Exception( 'Custom file name not found' )
3168

    
3169
        desc = ( "The %prog utility creates Mininet network from the\n"
3170
                 "command line. It can create parametrized topologies,\n"
3171
                 "invoke the Mininet CLI, and run tests." )
3172

    
3173
        usage = ( '%prog [options]\n'
3174
                  '(type %prog -h for details)' )
3175

    
3176
        opts = OptionParser( description=desc, usage=usage )
3177

    
3178
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
3179
        addDictOption( opts, LINKS, LINKDEF, 'link' )
3180

    
3181
        opts.add_option( '--custom', type='string', default=None,
3182
                         help='read custom topo and node params from .py' +
3183
                         'file' )
3184

    
3185
        self.options, self.args = opts.parse_args()
3186
        # We don't accept extra arguments after the options
3187
        if self.args:
3188
            opts.print_help()
3189
            exit()
3190

    
3191
    def setCustom( self, name, value ):
3192
        "Set custom parameters for MininetRunner."
3193
        if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
3194
            # Update dictionaries
3195
            param = name.upper()
3196
            globals()[ param ].update( value )
3197
        elif name == 'validate':
3198
            # Add custom validate function
3199
            self.validate = value
3200
        else:
3201
            # Add or modify global variable or class
3202
            globals()[ name ] = value
3203

    
3204
    def parseCustomFile( self, fileName ):
3205
        "Parse custom file and add params before parsing cmd-line options."
3206
        customs = {}
3207
        if os.path.isfile( fileName ):
3208
            execfile( fileName, customs, customs )
3209
            for name, val in customs.iteritems():
3210
                self.setCustom( name, val )
3211
        else:
3212
            raise Exception( 'could not find custom file: %s' % fileName )
3213

    
3214
    def importTopo( self ):
3215
        print 'topo='+self.options.topo
3216
        if self.options.topo == 'none':
3217
            return
3218
        self.newTopology()
3219
        topo = buildTopo( TOPOS, self.options.topo )
3220
        link = customConstructor( LINKS, self.options.link )
3221
        importNet = Mininet(topo=topo, build=False, link=link)
3222
        importNet.build()
3223

    
3224
        c = self.canvas
3225
        rowIncrement = 100
3226
        currentY = 100
3227

    
3228
        # Add Controllers
3229
        print 'controllers:'+str(len(importNet.controllers))
3230
        for controller in importNet.controllers:
3231
            name = controller.name
3232
            x = self.controllerCount*100+100
3233
            self.addNode('Controller', self.controllerCount,
3234
                 float(x), float(currentY), name=name)
3235
            icon = self.findWidgetByName(name)
3236
            icon.bind('<Button-3>', self.do_controllerPopup )
3237
            ctrlr = { 'controllerType': 'ref',
3238
                      'hostname': name,
3239
                      'controllerProtocol': controller.protocol,
3240
                      'remoteIP': controller.ip,
3241
                      'remotePort': controller.port}
3242
            self.controllers[name] = ctrlr
3243

    
3244

    
3245

    
3246
        currentY = currentY + rowIncrement
3247

    
3248
        # Add switches
3249
        print 'switches:'+str(len(importNet.switches))
3250
        columnCount = 0
3251
        for switch in importNet.switches:
3252
            name = switch.name
3253
            self.switchOpts[name] = {}
3254
            self.switchOpts[name]['nodeNum']=self.switchCount
3255
            self.switchOpts[name]['hostname']=name
3256
            self.switchOpts[name]['switchType']='default'
3257
            self.switchOpts[name]['controllers']=[]
3258

    
3259
            x = columnCount*100+100
3260
            self.addNode('Switch', self.switchCount,
3261
                 float(x), float(currentY), name=name)
3262
            icon = self.findWidgetByName(name)
3263
            icon.bind('<Button-3>', self.do_switchPopup )
3264
            # Now link to controllers
3265
            for controller in importNet.controllers:
3266
                self.switchOpts[name]['controllers'].append(controller.name)
3267
                dest = self.findWidgetByName(controller.name)
3268
                dx, dy = c.coords( self.widgetToItem[ dest ] )
3269
                self.link = c.create_line(float(x),
3270
                                          float(currentY),
3271
                                          dx,
3272
                                          dy,
3273
                                          width=4,
3274
                                          fill='red',
3275
                                          dash=(6, 4, 2, 4),
3276
                                          tag='link' )
3277
                c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
3278
                self.addLink( icon, dest, linktype='control' )
3279
                self.createControlLinkBindings()
3280
                self.link = self.linkWidget = None
3281
            if columnCount == 9:
3282
                columnCount = 0
3283
                currentY = currentY + rowIncrement
3284
            else:
3285
                columnCount =columnCount+1
3286

    
3287

    
3288
        currentY = currentY + rowIncrement
3289
        # Add hosts
3290
        print 'hosts:'+str(len(importNet.hosts))
3291
        columnCount = 0
3292
        for host in importNet.hosts:
3293
            name = host.name
3294
            self.hostOpts[name] = {'sched':'host'}
3295
            self.hostOpts[name]['nodeNum']=self.hostCount
3296
            self.hostOpts[name]['hostname']=name
3297
            self.hostOpts[name]['ip']=host.IP()
3298

    
3299
            x = columnCount*100+100
3300
            self.addNode('Host', self.hostCount,
3301
                 float(x), float(currentY), name=name)
3302
            icon = self.findWidgetByName(name)
3303
            icon.bind('<Button-3>', self.do_hostPopup )
3304
            if columnCount == 9:
3305
                columnCount = 0
3306
                currentY = currentY + rowIncrement
3307
            else:
3308
                columnCount =columnCount+1
3309

    
3310
        print 'links:'+str(len(topo.links()))
3311
        #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')]
3312
        for link in topo.links():
3313
            print str(link)
3314
            srcNode = link[0]
3315
            src = self.findWidgetByName(srcNode)
3316
            sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
3317

    
3318
            destNode = link[1]
3319
            dest = self.findWidgetByName(destNode)
3320
            dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
3321

    
3322
            params = topo.linkInfo( srcNode, destNode )
3323
            print 'Link Parameters='+str(params)
3324

    
3325
            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
3326
                                             fill='blue', tag='link' )
3327
            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
3328
            self.addLink( src, dest, linkopts=params )
3329
            self.createDataLinkBindings()
3330
            self.link = self.linkWidget = None
3331

    
3332
        importNet.stop()
3333

    
3334
def miniEditImages():
3335
    "Create and return images for MiniEdit."
3336

    
3337
    # Image data. Git will be unhappy. However, the alternative
3338
    # is to keep track of separate binary files, which is also
3339
    # unappealing.
3340

    
3341
    return {
3342
        'Select': BitmapImage(
3343
            file='/usr/include/X11/bitmaps/left_ptr' ),
3344

    
3345
        'Switch': PhotoImage( data=r"""
3346
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
3377
            """),
3378

    
3379
        'LegacySwitch': PhotoImage( data=r"""
3380
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
3411
4BE2eIRYeHAEIBwBP0Y4Qn41YWRSCQgAOw==
3412
            """),
3413

    
3414
        'LegacyRouter': PhotoImage( data=r"""
3415
R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+3QFq1DmL3wJMmAMzZZW11dnZ
3416
2SFrtyNdmTSO6gIZMUKa8gJVqEOHzR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq
3417
6ymF4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9vwNgvwJZsX+69gsXJQFH
3418
jTtjizF0tvHx8VOm9z2V736Dhz2N3QM2acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtg
3419
tktjfQFu3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh312Gt+VGm/AQIDTmB
3420
yAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i56gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia
3421
7DpeggFt2QNPm97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er/yVVhwJJktPh
3422
70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVVhQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18
3423
zKfP9wwcLAMHCwFFiS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh7cve8pG/
3424
7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR1RMjNTF3vU2X4TZupwRSolNne4nB+T+L
3425
2YGz4zJ/zYe99YGHjRdDcT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6WlpW2t
3426
7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90uvPz8wIVKBp42SV5zbfT7wtXpStV
3427
fwFWrBVvyTt3swFz5kGBv2+1/QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t
3428
u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T5yH5BAEAAAAALAAAAAAyABgA
3429
Bwj/AAEIHEiQYJY7Qwg9UsTplRIbENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4c
3430
HeoIabJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPATqEBoRB9gVJsxRlhPwHI
3431
0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkS
3432
rtwADuxCG/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmDjhTMmseoKQIFDx7R
3433
oxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq
3434
9dGvv09RHFhcIUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p9HEUFhxgMSAv
3435
jbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CF
3436
oVggmEgCyRf01WcFCYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57AgyZckpKKP
3437
GFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemMIQggeaJywSQ/wgHOAmJskQEfWqBlFBEH
3438
1P/QaGY3QOpDZXA2+A6m7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlBpZdi
3439
isd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtChRmVPNWgpr+Be+Nc9icARww9TkIEu
3440
DAsQ0O7DzGIQzD2QdDEJHTsIAROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB
3441
xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE42Q9jtFIp8z0Dy1jQMA1AGzi
3442
z9VoW7310V0znYDTGMQgwUDXLDBO2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOM
3443
LQkcjvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZMDHKCTwI8EcQFHBBAAFc
3444
gGPLHwLwcMIo12Qxu0ABAQA7
3445
            """),
3446

    
3447
        'Controller': PhotoImage( data=r"""
3448
            R0lGODlhMAAwAPcAAAEBAWfNAYWFhcfHx+3t6/f390lJUaWlpfPz8/Hx72lpaZGRke/v77m5uc0B
3449
            AeHh4e/v7WNjY3t7e5eXlyMjI4mJidPT0+3t7f///09PT7Ozs/X19fHx8ZWTk8HBwX9/fwAAAAAA
3450
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3451
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3452
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3453
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3454
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3455
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3456
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3457
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3458
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3459
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3460
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3461
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAwADAA
3462
            Bwj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAEIeMCxo8ePHwVkBGABg8mTKFOmtDByAIYN
3463
            MGPCRCCzQIENNzEMGOkBAwIKQIMKpYCgKAIHCDB4GNkAA4OnUJ9++CDhQ1QGFzA0GKkBA4GvYMOK
3464
            BYtBA1cNaNOqXcuWq8q3b81m7Cqzbk2bMMu6/Tl0qFEEAZLKxdj1KlSqVA3rnet1rOOwiwmznUzZ
3465
            LdzLJgdfpIv3pmebN2Pm1GyRbocNp1PLNMDaAM3Im1/alQk4gO28pCt2RdCBt+/eRg8IP1AUdmmf
3466
            f5MrL56bYlcOvaP7Xo6Ag3HdGDho3869u/YE1507t+3AgLz58ujPMwg/sTBUCAzgy49PH0LW5u0x
3467
            XFiwvz////5dcJ9bjxVIAHsSdUXAAgs2yOCDDn6FYEQaFGDgYxNCpEFfHHKIX4IDhCjiiCSS+CGF
3468
            FlCmogYpcnVABTDGKGOMAlRQYwUHnKjhAjX2aOOPN8LImgAL6PiQBhLMqCSNAThQgQRGOqRBBD1W
3469
            aaOVAggnQARRNqRBBxmEKeaYZIrZQZcMKbDiigqM5OabcMYp55x01ilnQAA7
3470
            """),
3471

    
3472
        'Host': PhotoImage( data=r"""
3473
            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
3474
            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
3475
            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
3476
            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
3477
            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
3478
            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
3479
            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
3480
            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
3481
            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
3482
            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
3483
            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
3484
            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
3485
            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
3486
            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
3487
            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
3488
            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
3489
            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
3490