Statistics
| Branch: | Tag: | Revision:

mininet / examples / miniedit.py @ 9aefda7c

History | View | Annotate | Download (122 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
"""
14

    
15
MINIEDIT_VERSION = '2.1.0.7.1'
16

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

    
31
if 'PYTHONPATH' in os.environ:
32
    sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
33

    
34
# someday: from ttk import *
35

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

    
50
print 'MiniEdit running against MiniNet '+VERSION
51
if StrictVersion(VERSION) > StrictVersion('2.0'):
52
    from mininet.node import IVSSwitch
53

    
54
TOPODEF = 'none'
55
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
56
          'linear': LinearTopo,
57
          'reversed': SingleSwitchReversedTopo,
58
          'single': SingleSwitchTopo,
59
          'none': None,
60
          'tree': TreeTopo }
61
CONTROLLERDEF = 'ref'
62
CONTROLLERS = { 'ref': Controller,
63
                'ovsc': OVSController,
64
                'nox': NOX,
65
                'remote': RemoteController,
66
                'none': lambda name: None }
67

    
68
class InbandController( RemoteController ):
69

    
70
    def checkListening( self ):
71
        "Overridden to do nothing."
72
        return
73

    
74
class customOvs(OVSSwitch):
75

    
76
    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
77
        OVSSwitch.__init__( self, name, **params )
78
        self.switchIP = None
79

    
80
    def getSwitchIP(self):
81
        return self.switchIP
82

    
83
    def setSwitchIP(self, ip):
84
        self.switchIP = ip
85

    
86
    def getOpenFlowVersion(self):
87
        return self.openFlowVersions
88

    
89
    def setOpenFlowVersion(self, versions):
90
        self.openFlowVersions = []
91
        if versions['ovsOf10'] == '1':
92
            self.openFlowVersions.append('OpenFlow10')
93
        if versions['ovsOf11'] == '1':
94
            self.openFlowVersions.append('OpenFlow11')
95
        if versions['ovsOf12'] == '1':
96
            self.openFlowVersions.append('OpenFlow12')
97
        if versions['ovsOf13'] == '1':
98
            self.openFlowVersions.append('OpenFlow13')
99

    
100
    def configureOpenFlowVersion(self):
101
        if not ( 'OpenFlow11' in self.openFlowVersions or
102
                 'OpenFlow12' in self.openFlowVersions or
103
                 'OpenFlow13' in self.openFlowVersions ):
104
            return
105

    
106
        protoList = ",".join(self.openFlowVersions)
107
        print 'Configuring OpenFlow to '+protoList
108
        self.cmd( 'ovs-vsctl -- set bridge', self, 'protocols='+protoList)
109

    
110
    def start( self, controllers ):
111
        # Call superclass constructor
112
        OVSSwitch.start( self, controllers )
113
        # Set OpenFlow Versions
114
        self.configureOpenFlowVersion()
115
        # Set Switch IP address
116
        if (self.switchIP is not None):
117
            self.cmd( 'ifconfig', self, self.switchIP )
118

    
119
class PrefsDialog(tkSimpleDialog.Dialog):
120

    
121
        def __init__(self, parent, title, prefDefaults):
122

    
123
            self.prefValues = prefDefaults
124

    
125
            tkSimpleDialog.Dialog.__init__(self, parent, title)
126

    
127
        def body(self, master):
128
            self.rootFrame = master
129
            self.leftfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
130
            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2)
131
            self.rightfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
132
            self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2)
133

    
134

    
135
            # Field for Base IP
136
            Label(self.leftfieldFrame, text="IP Base:").grid(row=0, sticky=E)
137
            self.ipEntry = Entry(self.leftfieldFrame)
138
            self.ipEntry.grid(row=0, column=1)
139
            ipBase =  self.prefValues['ipBase']
140
            self.ipEntry.insert(0, ipBase)
141

    
142
            # Selection of terminal type
143
            Label(self.leftfieldFrame, text="Default Terminal:").grid(row=1, sticky=E)
144
            self.terminalVar = StringVar(self.leftfieldFrame)
145
            self.terminalOption = OptionMenu(self.leftfieldFrame, self.terminalVar, "xterm", "gterm")
146
            self.terminalOption.grid(row=1, column=1, sticky=W)
147
            terminalType = self.prefValues['terminalType']
148
            self.terminalVar.set(terminalType)
149

    
150
            # Field for CLI
151
            Label(self.leftfieldFrame, text="Start CLI:").grid(row=2, sticky=E)
152
            self.cliStart = IntVar()
153
            self.cliButton = Checkbutton(self.leftfieldFrame, variable=self.cliStart)
154
            self.cliButton.grid(row=2, column=1, sticky=W)
155
            if self.prefValues['startCLI'] == '0':
156
                self.cliButton.deselect()
157
            else:
158
                self.cliButton.select()
159

    
160
            # Selection of switch type
161
            Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E)
162
            self.switchType = StringVar(self.leftfieldFrame)
163
            self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch", "Indigo Virtual Switch")
164
            self.switchTypeMenu.grid(row=3, column=1, sticky=W)
165
            switchTypePref = self.prefValues['switchType']
166
            if switchTypePref == 'ivs':
167
                self.switchType.set("Indigo Virtual Switch")
168
            else:
169
                self.switchType.set("Open vSwitch")
170

    
171

    
172
            # Fields for OVS OpenFlow version
173
            ovsFrame= LabelFrame(self.leftfieldFrame, text='Open vSwitch', padx=5, pady=5)
174
            ovsFrame.grid(row=4, column=0, columnspan=2, sticky=EW)
175
            Label(ovsFrame, text="OpenFlow 1.0:").grid(row=0, sticky=E)
176
            Label(ovsFrame, text="OpenFlow 1.1:").grid(row=1, sticky=E)
177
            Label(ovsFrame, text="OpenFlow 1.2:").grid(row=2, sticky=E)
178
            Label(ovsFrame, text="OpenFlow 1.3:").grid(row=3, sticky=E)
179

    
180
            self.ovsOf10 = IntVar()
181
            self.covsOf10 = Checkbutton(ovsFrame, variable=self.ovsOf10)
182
            self.covsOf10.grid(row=0, column=1, sticky=W)
183
            if self.prefValues['openFlowVersions']['ovsOf10'] == '0':
184
                self.covsOf10.deselect()
185
            else:
186
                self.covsOf10.select()
187

    
188
            self.ovsOf11 = IntVar()
189
            self.covsOf11 = Checkbutton(ovsFrame, variable=self.ovsOf11)
190
            self.covsOf11.grid(row=1, column=1, sticky=W)
191
            if self.prefValues['openFlowVersions']['ovsOf11'] == '0':
192
                self.covsOf11.deselect()
193
            else:
194
                self.covsOf11.select()
195

    
196
            self.ovsOf12 = IntVar()
197
            self.covsOf12 = Checkbutton(ovsFrame, variable=self.ovsOf12)
198
            self.covsOf12.grid(row=2, column=1, sticky=W)
199
            if self.prefValues['openFlowVersions']['ovsOf12'] == '0':
200
                self.covsOf12.deselect()
201
            else:
202
                self.covsOf12.select()
203

    
204
            self.ovsOf13 = IntVar()
205
            self.covsOf13 = Checkbutton(ovsFrame, variable=self.ovsOf13)
206
            self.covsOf13.grid(row=3, column=1, sticky=W)
207
            if self.prefValues['openFlowVersions']['ovsOf13'] == '0':
208
                self.covsOf13.deselect()
209
            else:
210
                self.covsOf13.select()
211

    
212
            # Field for DPCTL listen port
213
            Label(self.leftfieldFrame, text="dpctl port:").grid(row=5, sticky=E)
214
            self.dpctlEntry = Entry(self.leftfieldFrame)
215
            self.dpctlEntry.grid(row=5, column=1)
216
            if 'dpctl' in self.prefValues:
217
                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
218

    
219
            # sFlow
220
            sflowValues = self.prefValues['sflow']
221
            self.sflowFrame= LabelFrame(self.rightfieldFrame, text='sFlow Profile for Open vSwitch', padx=5, pady=5)
222
            self.sflowFrame.grid(row=0, column=0, columnspan=2, sticky=EW)
223

    
224
            Label(self.sflowFrame, text="Target:").grid(row=0, sticky=E)
225
            self.sflowTarget = Entry(self.sflowFrame)
226
            self.sflowTarget.grid(row=0, column=1)
227
            self.sflowTarget.insert(0, sflowValues['sflowTarget'])
228

    
229
            Label(self.sflowFrame, text="Sampling:").grid(row=1, sticky=E)
230
            self.sflowSampling = Entry(self.sflowFrame)
231
            self.sflowSampling.grid(row=1, column=1)
232
            self.sflowSampling.insert(0, sflowValues['sflowSampling'])
233

    
234
            Label(self.sflowFrame, text="Header:").grid(row=2, sticky=E)
235
            self.sflowHeader = Entry(self.sflowFrame)
236
            self.sflowHeader.grid(row=2, column=1)
237
            self.sflowHeader.insert(0, sflowValues['sflowHeader'])
238

    
239
            Label(self.sflowFrame, text="Polling:").grid(row=3, sticky=E)
240
            self.sflowPolling = Entry(self.sflowFrame)
241
            self.sflowPolling.grid(row=3, column=1)
242
            self.sflowPolling.insert(0, sflowValues['sflowPolling'])
243

    
244
            # NetFlow
245
            nflowValues = self.prefValues['netflow']
246
            self.nFrame= LabelFrame(self.rightfieldFrame, text='NetFlow Profile for Open vSwitch', padx=5, pady=5)
247
            self.nFrame.grid(row=1, column=0, columnspan=2, sticky=EW)
248

    
249
            Label(self.nFrame, text="Target:").grid(row=0, sticky=E)
250
            self.nflowTarget = Entry(self.nFrame)
251
            self.nflowTarget.grid(row=0, column=1)
252
            self.nflowTarget.insert(0, nflowValues['nflowTarget'])
253

    
254
            Label(self.nFrame, text="Active Timeout:").grid(row=1, sticky=E)
255
            self.nflowTimeout = Entry(self.nFrame)
256
            self.nflowTimeout.grid(row=1, column=1)
257
            self.nflowTimeout.insert(0, nflowValues['nflowTimeout'])
258

    
259
            Label(self.nFrame, text="Add ID to Interface:").grid(row=2, sticky=E)
260
            self.nflowAddId = IntVar()
261
            self.nflowAddIdButton = Checkbutton(self.nFrame, variable=self.nflowAddId)
262
            self.nflowAddIdButton.grid(row=2, column=1, sticky=W)
263
            if nflowValues['nflowAddId'] == '0':
264
                self.nflowAddIdButton.deselect()
265
            else:
266
                self.nflowAddIdButton.select()
267

    
268
            # initial focus
269
            return self.ipEntry
270

    
271
        def apply(self):
272
            ipBase = self.ipEntry.get()
273
            terminalType = self.terminalVar.get()
274
            startCLI = str(self.cliStart.get())
275
            sw = self.switchType.get()
276
            dpctl = self.dpctlEntry.get()
277

    
278
            ovsOf10 = str(self.ovsOf10.get())
279
            ovsOf11 = str(self.ovsOf11.get())
280
            ovsOf12 = str(self.ovsOf12.get())
281
            ovsOf13 = str(self.ovsOf13.get())
282

    
283
            sflowValues = {'sflowTarget':self.sflowTarget.get(),
284
                           'sflowSampling':self.sflowSampling.get(),
285
                           'sflowHeader':self.sflowHeader.get(),
286
                           'sflowPolling':self.sflowPolling.get()}
287
            nflowvalues = {'nflowTarget':self.nflowTarget.get(),
288
                           'nflowTimeout':self.nflowTimeout.get(),
289
                           'nflowAddId':str(self.nflowAddId.get())}
290
            self.result = {'ipBase':ipBase,
291
                           'terminalType':terminalType,
292
                           'dpctl':dpctl,
293
                           'sflow':sflowValues,
294
                           'netflow':nflowvalues,
295
                           'startCLI':startCLI}
296
            if sw == 'Indigo Virtual Switch':
297
                self.result['switchType'] = 'ivs'
298
                if StrictVersion(VERSION) < StrictVersion('2.1'):
299
                    self.ovsOk = False
300
                    showerror(title="Error",
301
                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
302
            else:
303
                self.result['switchType'] = 'ovs'
304

    
305
            self.ovsOk = True
306
            if ovsOf11 == "1":
307
                ovsVer = self.getOvsVersion()
308
                if StrictVersion(ovsVer) < StrictVersion('2.0'):
309
                    self.ovsOk = False
310
                    showerror(title="Error",
311
                              message='Open vSwitch version 2.0+ required. You have '+ovsVer+'.')
312
            if ovsOf12 == "1" or ovsOf13 == "1":
313
                ovsVer = self.getOvsVersion()
314
                if StrictVersion(ovsVer) < StrictVersion('1.10'):
315
                    self.ovsOk = False
316
                    showerror(title="Error",
317
                              message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.')
318

    
319
            if self.ovsOk:
320
                self.result['openFlowVersions']={'ovsOf10':ovsOf10,
321
                                                 'ovsOf11':ovsOf11,
322
                                                 'ovsOf12':ovsOf12,
323
                                                 'ovsOf13':ovsOf13}
324
            else:
325
                self.result = None
326

    
327
        def getOvsVersion(self):
328
            outp = quietRun("ovs-vsctl show")
329
            r = r'ovs_version: "(.*)"'
330
            m = re.search(r, outp)
331
            if m is None:
332
                print 'Version check failed'
333
                return None
334
            else:
335
                print 'Open vSwitch version is '+m.group(1)
336
                return m.group(1)
337

    
338

    
339
class CustomDialog(object):
340

    
341
        # TODO: Fix button placement and Title and window focus lock
342
        def __init__(self, master, title):
343
            self.top=Toplevel(master)
344

    
345
            self.bodyFrame = Frame(self.top)
346
            self.bodyFrame.grid(row=0, column=0, sticky='nswe')
347
            self.body(self.bodyFrame)
348

    
349
            #return self.b # initial focus
350
            buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey')
351
            buttonFrame.grid(row=1 , column=0, sticky='nswe')
352

    
353
            okButton = Button(buttonFrame, width=8, text='OK', relief='groove',
354
                       bd=4, command=self.okAction)
355
            okButton.grid(row=0, column=0, sticky=E)
356

    
357
            canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove',
358
                        bd=4, command=self.cancelAction)
359
            canlceButton.grid(row=0, column=1, sticky=W)
360

    
361
        def body(self, master):
362
            self.rootFrame = master
363

    
364
        def apply(self):
365
            self.top.destroy()
366

    
367
        def cancelAction(self):
368
            self.top.destroy()
369

    
370
        def okAction(self):
371
            self.apply()
372
            self.top.destroy()
373

    
374
class HostDialog(CustomDialog):
375

    
376
        def __init__(self, master, title, prefDefaults):
377

    
378
            self.prefValues = prefDefaults
379
            self.result = None
380

    
381
            CustomDialog.__init__(self, master, title)
382

    
383
        def body(self, master):
384
            self.rootFrame = master
385
            self.leftfieldFrame = Frame(self.rootFrame)
386
            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2)
387
            self.rightfieldFrame = Frame(self.rootFrame)
388
            self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2)
389

    
390
            # Field for Hostname
391
            Label(self.leftfieldFrame, text="Hostname:").grid(row=0, sticky=E)
392
            self.hostnameEntry = Entry(self.leftfieldFrame)
393
            self.hostnameEntry.grid(row=0, column=1)
394
            if 'hostname' in self.prefValues:
395
                self.hostnameEntry.insert(0, self.prefValues['hostname'])
396

    
397
            # Field for Switch IP
398
            Label(self.leftfieldFrame, text="IP Address:").grid(row=1, sticky=E)
399
            self.ipEntry = Entry(self.leftfieldFrame)
400
            self.ipEntry.grid(row=1, column=1)
401
            if 'ip' in self.prefValues:
402
                self.ipEntry.insert(0, self.prefValues['ip'])
403

    
404
            # Field for default route
405
            Label(self.leftfieldFrame, text="Default Route:").grid(row=2, sticky=E)
406
            self.routeEntry = Entry(self.leftfieldFrame)
407
            self.routeEntry.grid(row=2, column=1)
408
            if 'defaultRoute' in self.prefValues:
409
                self.routeEntry.insert(0, self.prefValues['defaultRoute'])
410

    
411
            # Field for CPU
412
            Label(self.rightfieldFrame, text="Amount CPU:").grid(row=0, sticky=E)
413
            self.cpuEntry = Entry(self.rightfieldFrame)
414
            self.cpuEntry.grid(row=0, column=1)
415
            if 'cpu' in self.prefValues:
416
                self.cpuEntry.insert(0, str(self.prefValues['cpu']))
417
            # Selection of Scheduler
418
            if 'sched' in self.prefValues:
419
                sched =  self.prefValues['sched']
420
            else:
421
                sched = 'host'
422
            self.schedVar = StringVar(self.rightfieldFrame)
423
            self.schedOption = OptionMenu(self.rightfieldFrame, self.schedVar, "host", "cfs", "rt")
424
            self.schedOption.grid(row=0, column=2, sticky=W)
425
            self.schedVar.set(sched)
426

    
427
            # Selection of Cores
428
            Label(self.rightfieldFrame, text="Cores:").grid(row=1, sticky=E)
429
            self.coreEntry = Entry(self.rightfieldFrame)
430
            self.coreEntry.grid(row=1, column=1)
431
            if 'cores' in self.prefValues:
432
                self.coreEntry.insert(1, self.prefValues['cores'])
433

    
434
            # External Interfaces
435
            self.externalInterfaces = 0
436
            Label(self.rootFrame, text="External Interface:").grid(row=1, column=0, sticky=E)
437
            self.b = Button( self.rootFrame, text='Add', command=self.addInterface)
438
            self.b.grid(row=1, column=1)
439

    
440
            self.interfaceFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=1, title='External Interfaces')
441
            self.interfaceFrame.grid(row=2, column=0, sticky='nswe', columnspan=2)
442
            self.tableFrame = self.interfaceFrame.interior
443
            self.tableFrame.addRow(value=['Interface Name'], readonly=True)
444

    
445
            # Add defined interfaces
446
            externalInterfaces = []
447
            if 'externalInterfaces' in self.prefValues:
448
                externalInterfaces = self.prefValues['externalInterfaces']
449

    
450
            for externalInterface in externalInterfaces:
451
                self.tableFrame.addRow(value=[externalInterface])
452

    
453
            # VLAN Interfaces
454
            self.vlanInterfaces = 0
455
            Label(self.rootFrame, text="VLAN Interface:").grid(row=1, column=2, sticky=E)
456
            self.vlanButton = Button( self.rootFrame, text='Add', command=self.addVlanInterface)
457
            self.vlanButton.grid(row=1, column=3)
458

    
459
            self.vlanFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=2, title='VLAN Interfaces')
460
            self.vlanFrame.grid(row=2, column=2, sticky='nswe', columnspan=2)
461
            self.vlanTableFrame = self.vlanFrame.interior
462
            self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True)
463

    
464
            vlanInterfaces = []
465
            if 'vlanInterfaces' in self.prefValues:
466
                vlanInterfaces = self.prefValues['vlanInterfaces']
467
            for vlanInterface in vlanInterfaces:
468
                self.vlanTableFrame.addRow(value=vlanInterface)
469

    
470
        def addVlanInterface( self ):
471
            self.vlanTableFrame.addRow()
472

    
473
        def addInterface( self ):
474
            self.tableFrame.addRow()
475

    
476
        def apply(self):
477
            externalInterfaces = []
478
            for row in range(self.tableFrame.rows):
479
                if (len(self.tableFrame.get(row, 0)) > 0 and
480
                    row > 0):
481
                    externalInterfaces.append(self.tableFrame.get(row, 0))
482
            vlanInterfaces = []
483
            for row in range(self.vlanTableFrame.rows):
484
                if (len(self.vlanTableFrame.get(row, 0)) > 0 and
485
                    len(self.vlanTableFrame.get(row, 1)) > 0 and
486
                    row > 0):
487
                    vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)])
488

    
489
            results = {'cpu': self.cpuEntry.get(),
490
                       'cores':self.coreEntry.get(),
491
                       'sched':self.schedVar.get(),
492
                       'hostname':self.hostnameEntry.get(),
493
                       'ip':self.ipEntry.get(),
494
                       'defaultRoute':self.routeEntry.get(),
495
                       'externalInterfaces':externalInterfaces,
496
                       'vlanInterfaces':vlanInterfaces}
497
            self.result = results
498

    
499
class SwitchDialog(CustomDialog):
500

    
501
        def __init__(self, master, title, prefDefaults):
502

    
503
            self.prefValues = prefDefaults
504
            self.result = None
505
            CustomDialog.__init__(self, master, title)
506

    
507
        def body(self, master):
508
            self.rootFrame = master
509

    
510
            rowCount = 0
511
            externalInterfaces = []
512
            if 'externalInterfaces' in self.prefValues:
513
                externalInterfaces = self.prefValues['externalInterfaces']
514

    
515
            self.fieldFrame = Frame(self.rootFrame)
516
            self.fieldFrame.grid(row=0, column=0, sticky='nswe')
517

    
518
            # Field for Hostname
519
            Label(self.fieldFrame, text="Hostname:").grid(row=rowCount, sticky=E)
520
            self.hostnameEntry = Entry(self.fieldFrame)
521
            self.hostnameEntry.grid(row=rowCount, column=1)
522
            self.hostnameEntry.insert(0, self.prefValues['hostname'])
523
            rowCount+=1
524

    
525
            # Field for DPID
526
            Label(self.fieldFrame, text="DPID:").grid(row=rowCount, sticky=E)
527
            self.dpidEntry = Entry(self.fieldFrame)
528
            self.dpidEntry.grid(row=rowCount, column=1)
529
            if 'dpid' in self.prefValues:
530
                self.dpidEntry.insert(0, self.prefValues['dpid'])
531
            rowCount+=1
532

    
533
            # Field for Netflow
534
            Label(self.fieldFrame, text="Enable NetFlow:").grid(row=rowCount, sticky=E)
535
            self.nflow = IntVar()
536
            self.nflowButton = Checkbutton(self.fieldFrame, variable=self.nflow)
537
            self.nflowButton.grid(row=rowCount, column=1, sticky=W)
538
            if 'netflow' in self.prefValues:
539
                if self.prefValues['netflow'] == '0':
540
                    self.nflowButton.deselect()
541
                else:
542
                    self.nflowButton.select()
543
            else:
544
                self.nflowButton.deselect()
545
            rowCount+=1
546

    
547
            # Field for sflow
548
            Label(self.fieldFrame, text="Enable sFlow:").grid(row=rowCount, sticky=E)
549
            self.sflow = IntVar()
550
            self.sflowButton = Checkbutton(self.fieldFrame, variable=self.sflow)
551
            self.sflowButton.grid(row=rowCount, column=1, sticky=W)
552
            if 'sflow' in self.prefValues:
553
                if self.prefValues['sflow'] == '0':
554
                    self.sflowButton.deselect()
555
                else:
556
                    self.sflowButton.select()
557
            else:
558
                self.sflowButton.deselect()
559
            rowCount+=1
560

    
561
            # Selection of switch type
562
            Label(self.fieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E)
563
            self.switchType = StringVar(self.fieldFrame)
564
            self.switchTypeMenu = OptionMenu(self.fieldFrame, self.switchType, "Default", "Open vSwitch", "Indigo Virtual Switch")
565
            self.switchTypeMenu.grid(row=rowCount, column=1, sticky=W)
566
            if 'switchType' in self.prefValues:
567
                switchTypePref = self.prefValues['switchType']
568
                if switchTypePref == 'ivs':
569
                    self.switchType.set("Indigo Virtual Switch")
570
                elif switchTypePref == 'ovs':
571
                    self.switchType.set("Open vSwitch")
572
                else:
573
                    self.switchType.set("Default")
574
            else:
575
                self.switchType.set("Default")
576
            rowCount+=1
577

    
578
            # Field for Switch IP
579
            Label(self.fieldFrame, text="IP Address:").grid(row=rowCount, sticky=E)
580
            self.ipEntry = Entry(self.fieldFrame)
581
            self.ipEntry.grid(row=rowCount, column=1)
582
            if 'switchIP' in self.prefValues:
583
                self.ipEntry.insert(0, self.prefValues['switchIP'])
584
            rowCount+=1
585

    
586
            # Field for DPCTL port
587
            Label(self.fieldFrame, text="DPCTL port:").grid(row=rowCount, sticky=E)
588
            self.dpctlEntry = Entry(self.fieldFrame)
589
            self.dpctlEntry.grid(row=rowCount, column=1)
590
            if 'dpctl' in self.prefValues:
591
                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
592
            rowCount+=1
593

    
594
            # External Interfaces
595
            Label(self.fieldFrame, text="External Interface:").grid(row=rowCount, sticky=E)
596
            self.b = Button( self.fieldFrame, text='Add', command=self.addInterface)
597
            self.b.grid(row=rowCount, column=1)
598

    
599
            self.interfaceFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=1, title='External Interfaces')
600
            self.interfaceFrame.grid(row=2, column=0, sticky='nswe')
601
            self.tableFrame = self.interfaceFrame.interior
602

    
603
            # Add defined interfaces
604
            for externalInterface in externalInterfaces:
605
                self.tableFrame.addRow(value=[externalInterface])
606
            rowCount+=1
607

    
608
        def addInterface( self ):
609
            self.tableFrame.addRow()
610

    
611
        def defaultDpid( self ,name):
612
            "Derive dpid from switch name, s1 -> 1"
613
            try:
614
                dpid = int( re.findall( r'\d+', name )[ 0 ] )
615
                dpid = hex( dpid )[ 2: ]
616
                return dpid
617
            except IndexError:
618
                return None
619
                #raise Exception( 'Unable to derive default datapath ID - '
620
                #                 'please either specify a dpid or use a '
621
                #                 'canonical switch name such as s23.' )
622

    
623
        def apply(self):
624
            externalInterfaces = []
625
            for row in range(self.tableFrame.rows):
626
                #print 'Interface is ' + self.tableFrame.get(row, 0)
627
                if (len(self.tableFrame.get(row, 0)) > 0):
628
                    externalInterfaces.append(self.tableFrame.get(row, 0))
629

    
630
            dpid = self.dpidEntry.get()
631
            if (self.defaultDpid(self.hostnameEntry.get()) is None
632
               and len(dpid) == 0):
633
                showerror(title="Error",
634
                              message= 'Unable to derive default datapath ID - '
635
                                 'please either specify a DPID or use a '
636
                                 'canonical switch name such as s23.' )
637

    
638
            
639
            results = {'externalInterfaces':externalInterfaces,
640
                       'hostname':self.hostnameEntry.get(),
641
                       'dpid':dpid,
642
                       'sflow':str(self.sflow.get()),
643
                       'netflow':str(self.nflow.get()),
644
                       'dpctl':self.dpctlEntry.get(),
645
                       'switchIP':self.ipEntry.get()}
646
            sw = self.switchType.get()
647
            if sw == 'Indigo Virtual Switch':
648
                results['switchType'] = 'ivs'
649
                if StrictVersion(VERSION) < StrictVersion('2.1'):
650
                    self.ovsOk = False
651
                    showerror(title="Error",
652
                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
653
            elif sw == 'Open vSwitch':
654
                results['switchType'] = 'ovs'
655
            else:
656
                results['switchType'] = 'default'
657
            self.result = results
658

    
659

    
660
class VerticalScrolledTable(LabelFrame):
661
    """A pure Tkinter scrollable frame that actually works!
662

663
    * Use the 'interior' attribute to place widgets inside the scrollable frame
664
    * Construct and pack/place/grid normally
665
    * This frame only allows vertical scrolling
666
    
667
    """
668
    def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw):
669
        LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw)            
670

    
671
        # create a canvas object and a vertical scrollbar for scrolling it
672
        vscrollbar = Scrollbar(self, orient=VERTICAL)
673
        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
674
        canvas = Canvas(self, bd=0, highlightthickness=0,
675
                        yscrollcommand=vscrollbar.set)
676
        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
677
        vscrollbar.config(command=canvas.yview)
678

    
679
        # reset the view
680
        canvas.xview_moveto(0)
681
        canvas.yview_moveto(0)
682

    
683
        # create a frame inside the canvas which will be scrolled with it
684
        self.interior = interior = TableFrame(canvas, rows=rows, columns=columns)
685
        interior_id = canvas.create_window(0, 0, window=interior,
686
                                           anchor=NW)
687

    
688
        # track changes to the canvas and frame width and sync them,
689
        # also updating the scrollbar
690
        def _configure_interior(event):
691
            # update the scrollbars to match the size of the inner frame
692
            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
693
            canvas.config(scrollregion="0 0 %s %s" % size)
694
            if interior.winfo_reqwidth() != canvas.winfo_width():
695
                # update the canvas's width to fit the inner frame
696
                canvas.config(width=interior.winfo_reqwidth())
697
        interior.bind('<Configure>', _configure_interior)
698

    
699
        def _configure_canvas(event):
700
            if interior.winfo_reqwidth() != canvas.winfo_width():
701
                # update the inner frame's width to fill the canvas
702
                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
703
        canvas.bind('<Configure>', _configure_canvas)
704

    
705
        return
706

    
707
class TableFrame(Frame):
708
    def __init__(self, parent, rows=2, columns=2):
709

    
710
        Frame.__init__(self, parent, background="black")
711
        self._widgets = []
712
        self.rows = rows
713
        self.columns = columns
714
        for row in range(rows):
715
            current_row = []
716
            for column in range(columns):
717
                label = Entry(self, borderwidth=0)
718
                label.grid(row=row, column=column, sticky="wens", padx=1, pady=1)
719
                current_row.append(label)
720
            self._widgets.append(current_row)
721

    
722
    def set(self, row, column, value):
723
        widget = self._widgets[row][column]
724
        widget.insert(0, value)
725

    
726
    def get(self, row, column):
727
        widget = self._widgets[row][column]
728
        return widget.get()
729

    
730
    def addRow( self, value=None, readonly=False ):
731
        #print "Adding row " + str(self.rows +1)
732
        current_row = []
733
        for column in range(self.columns):
734
            label = Entry(self, borderwidth=0)
735
            label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1)
736
            if value is not None:
737
                label.insert(0, value[column])
738
            if (readonly == True):
739
                label.configure(state='readonly')
740
            current_row.append(label)
741
        self._widgets.append(current_row)
742
        self.update_idletasks()
743
        self.rows += 1
744

    
745
class LinkDialog(tkSimpleDialog.Dialog):
746

    
747
        def __init__(self, parent, title, linkDefaults):
748

    
749
            self.linkValues = linkDefaults
750

    
751
            tkSimpleDialog.Dialog.__init__(self, parent, title)
752

    
753
        def body(self, master):
754

    
755
            self.var = StringVar(master)
756
            Label(master, text="Bandwidth:").grid(row=0, sticky=E)
757
            self.e1 = Entry(master)
758
            self.e1.grid(row=0, column=1)
759
            Label(master, text="Mbit").grid(row=0, column=2, sticky=W)
760
            if 'bw' in self.linkValues:
761
                self.e1.insert(0,str(self.linkValues['bw']))
762

    
763
            Label(master, text="Delay:").grid(row=1, sticky=E)
764
            self.e2 = Entry(master)
765
            self.e2.grid(row=1, column=1)
766
            if 'delay' in self.linkValues:
767
                self.e2.insert(0, self.linkValues['delay'])
768

    
769
            Label(master, text="Loss:").grid(row=2, sticky=E)
770
            self.e3 = Entry(master)
771
            self.e3.grid(row=2, column=1)
772
            Label(master, text="%").grid(row=2, column=2, sticky=W)
773
            if 'loss' in self.linkValues:
774
                self.e3.insert(0, str(self.linkValues['loss']))
775

    
776
            Label(master, text="Max Queue size:").grid(row=3, sticky=E)
777
            self.e4 = Entry(master)
778
            self.e4.grid(row=3, column=1)
779
            if 'max_queue_size' in self.linkValues:
780
                self.e4.insert(0, str(self.linkValues['max_queue_size']))
781

    
782
            Label(master, text="Jitter:").grid(row=4, sticky=E)
783
            self.e5 = Entry(master)
784
            self.e5.grid(row=4, column=1)
785
            if 'jitter' in self.linkValues:
786
                self.e5.insert(0, self.linkValues['jitter'])
787

    
788
            Label(master, text="Speedup:").grid(row=5, sticky=E)
789
            self.e6 = Entry(master)
790
            self.e6.grid(row=5, column=1)
791
            if 'speedup' in self.linkValues:
792
                self.e6.insert(0, str(self.linkValues['speedup']))
793

    
794
            return self.e1 # initial focus
795

    
796
        def apply(self):
797
            self.result = {}
798
            if (len(self.e1.get()) > 0):
799
                self.result['bw'] = int(self.e1.get())
800
            if (len(self.e2.get()) > 0):
801
                self.result['delay'] = self.e2.get()
802
            if (len(self.e3.get()) > 0):
803
                self.result['loss'] = int(self.e3.get())
804
            if (len(self.e4.get()) > 0):
805
                self.result['max_queue_size'] = int(self.e4.get())
806
            if (len(self.e5.get()) > 0):
807
                self.result['jitter'] = self.e5.get()
808
            if (len(self.e6.get()) > 0):
809
                self.result['speedup'] = int(self.e6.get())
810

    
811
class ControllerDialog(tkSimpleDialog.Dialog):
812

    
813
        def __init__(self, parent, title, ctrlrDefaults=None):
814

    
815
            if ctrlrDefaults:
816
                self.ctrlrValues = ctrlrDefaults
817

    
818
            tkSimpleDialog.Dialog.__init__(self, parent, title)
819

    
820
        def body(self, master):
821

    
822
            self.var = StringVar(master)
823

    
824
            rowCount=0
825
            # Field for Hostname
826
            Label(master, text="Name:").grid(row=rowCount, sticky=E)
827
            self.hostnameEntry = Entry(master)
828
            self.hostnameEntry.grid(row=rowCount, column=1)
829
            self.hostnameEntry.insert(0, self.ctrlrValues['hostname'])
830
            rowCount+=1
831

    
832
            # Field for Remove Controller Port
833
            Label(master, text="Controller Port:").grid(row=rowCount, sticky=E)
834
            self.e2 = Entry(master)
835
            self.e2.grid(row=rowCount, column=1)
836
            self.e2.insert(0, self.ctrlrValues['remotePort'])
837
            rowCount+=1
838

    
839
            # Field for Controller Type
840
            Label(master, text="Controller Type:").grid(row=rowCount, sticky=E)
841
            controllerType = self.ctrlrValues['controllerType']
842
            self.o1 = OptionMenu(master, self.var, "Remote Controller", "In-Band Controller", "OpenFlow Reference", "OVS Controller")
843
            self.o1.grid(row=rowCount, column=1, sticky=W)
844
            if controllerType == 'ref':
845
                self.var.set("OpenFlow Reference")
846
            elif controllerType == 'inband':
847
                self.var.set("In-Band Controller")
848
            elif controllerType == 'remote':
849
                self.var.set("Remote Controller")
850
            else:
851
                self.var.set("OVS Controller")
852
            rowCount+=1
853

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

    
858
            Label(remoteFrame, text="IP Address:").grid(row=0, sticky=E)
859
            self.e1 = Entry(remoteFrame)
860
            self.e1.grid(row=0, column=1)
861
            self.e1.insert(0, self.ctrlrValues['remoteIP'])
862
            rowCount+=1
863

    
864
            return self.hostnameEntry # initial focus
865

    
866
        def apply(self):
867
            hostname = self.hostnameEntry.get()
868
            controllerType = self.var.get()
869
            remoteIP = self.e1.get()
870
            controllerPort = int(self.e2.get())
871
            self.result = { 'hostname': hostname,
872
                            'remoteIP': remoteIP,
873
                            'remotePort': controllerPort}
874

    
875
            if controllerType == 'Remote Controller':
876
                self.result['controllerType'] = 'remote'
877
            elif controllerType == 'In-Band Controller':
878
                self.result['controllerType'] = 'inband'
879
            elif controllerType == 'OpenFlow Reference':
880
                self.result['controllerType'] = 'ref'
881
            else:
882
                self.result['controllerType'] = 'ovsc'
883

    
884
class MiniEdit( Frame ):
885

    
886
    "A simple network editor for Mininet."
887

    
888
    def __init__( self, parent=None, cheight=400, cwidth=800 ):
889

    
890
        self.defaultIpBase='10.0.0.0/8'
891

    
892
        self.nflowDefaults = {'nflowTarget':'',
893
                              'nflowTimeout':'600',
894
                              'nflowAddId':'0'}
895
        self.sflowDefaults = {'sflowTarget':'',
896
                              'sflowSampling':'400',
897
                              'sflowHeader':'128',
898
                              'sflowPolling':'30'}
899

    
900
        self.appPrefs={
901
            "ipBase": self.defaultIpBase,
902
            "startCLI": "0",
903
            "terminalType": 'xterm',
904
            "switchType": 'ovs',
905
            "dpctl": '',
906
            'sflow':self.sflowDefaults,
907
            'netflow':self.nflowDefaults,
908
            'openFlowVersions':{'ovsOf10':'1',
909
                                'ovsOf11':'0',
910
                                'ovsOf12':'0',
911
                                'ovsOf13':'0'}
912

    
913
        }
914

    
915

    
916
        Frame.__init__( self, parent )
917
        self.action = None
918
        self.appName = 'MiniEdit'
919
        self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" )
920

    
921
        # Style
922
        self.font = ( 'Geneva', 9 )
923
        self.smallFont = ( 'Geneva', 7 )
924
        self.bg = 'white'
925

    
926
        # Title
927
        self.top = self.winfo_toplevel()
928
        self.top.title( self.appName )
929

    
930
        # Menu bar
931
        self.createMenubar()
932

    
933
        # Editing canvas
934
        self.cheight, self.cwidth = cheight, cwidth
935
        self.cframe, self.canvas = self.createCanvas()
936

    
937
        # Toolbar
938
        self.controllers = {}
939

    
940
        # Toolbar
941
        self.images = miniEditImages()
942
        self.buttons = {}
943
        self.active = None
944
        self.tools = ( 'Select', 'Host', 'Switch', 'Link', 'Controller' )
945
        self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
946
        self.toolbar = self.createToolbar()
947

    
948
        # Layout
949
        self.toolbar.grid( column=0, row=0, sticky='nsew')
950
        self.cframe.grid( column=1, row=0 )
951
        self.columnconfigure( 1, weight=1 )
952
        self.rowconfigure( 0, weight=1 )
953
        self.pack( expand=True, fill='both' )
954

    
955
        # About box
956
        self.aboutBox = None
957

    
958
        # Initialize node data
959
        self.nodeBindings = self.createNodeBindings()
960
        self.nodePrefixes = { 'Switch': 's', 'Host': 'h' , 'Controller': 'c'}
961
        self.widgetToItem = {}
962
        self.itemToWidget = {}
963

    
964
        # Initialize link tool
965
        self.link = self.linkWidget = None
966

    
967
        # Selection support
968
        self.selection = None
969

    
970
        # Keyboard bindings
971
        self.bind( '<Control-q>', lambda event: self.quit() )
972
        self.bind( '<KeyPress-Delete>', self.deleteSelection )
973
        self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
974
        self.focus()
975

    
976
        self.hostPopup = Menu(self.top, tearoff=0)
977
        self.hostPopup.add_command(label='Host Options', font=self.font)
978
        self.hostPopup.add_separator()
979
        self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails )
980

    
981
        self.hostRunPopup = Menu(self.top, tearoff=0)
982
        self.hostRunPopup.add_command(label='Host Options', font=self.font)
983
        self.hostRunPopup.add_separator()
984
        self.hostRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
985

    
986
        self.switchPopup = Menu(self.top, tearoff=0)
987
        self.switchPopup.add_command(label='Swtich Options', font=self.font)
988
        self.switchPopup.add_separator()
989
        self.switchPopup.add_command(label='Properties', font=self.font, command=self.switchDetails )
990

    
991
        self.switchRunPopup = Menu(self.top, tearoff=0)
992
        self.switchRunPopup.add_command(label='Swtich Options', font=self.font)
993
        self.switchRunPopup.add_separator()
994
        self.switchRunPopup.add_command(label='List bridge details', font=self.font, command=self.listBridge )
995

    
996
        self.linkPopup = Menu(self.top, tearoff=0)
997
        self.linkPopup.add_command(label='Link Options', font=self.font)
998
        self.linkPopup.add_separator()
999
        self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails )
1000

    
1001
        self.linkRunPopup = Menu(self.top, tearoff=0)
1002
        self.linkRunPopup.add_command(label='Link Options', font=self.font)
1003
        self.linkRunPopup.add_separator()
1004
        self.linkRunPopup.add_command(label='Link Up', font=self.font, command=self.linkUp )
1005
        self.linkRunPopup.add_command(label='Link Down', font=self.font, command=self.linkDown )
1006

    
1007
        self.controllerPopup = Menu(self.top, tearoff=0)
1008
        self.controllerPopup.add_command(label='Controller Options', font=self.font)
1009
        self.controllerPopup.add_separator()
1010
        self.controllerPopup.add_command(label='Properties', font=self.font, command=self.controllerDetails )
1011

    
1012

    
1013
        # Event handling initalization
1014
        self.linkx = self.linky = self.linkItem = None
1015
        self.lastSelection = None
1016

    
1017
        # Model initialization
1018
        self.links = {}
1019
        self.hostOpts = {}
1020
        self.switchOpts = {}
1021
        self.hostCount = 0
1022
        self.switchCount = 0
1023
        self.controllerCount = 0
1024
        self.net = None
1025

    
1026
        # Close window gracefully
1027
        Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
1028

    
1029
    def quit( self ):
1030
        "Stop our network, if any, then quit."
1031
        self.stop()
1032
        Frame.quit( self )
1033

    
1034
    def createMenubar( self ):
1035
        "Create our menu bar."
1036

    
1037
        font = self.font
1038

    
1039
        mbar = Menu( self.top, font=font )
1040
        self.top.configure( menu=mbar )
1041

    
1042

    
1043
        fileMenu = Menu( mbar, tearoff=False )
1044
        mbar.add_cascade( label="File", font=font, menu=fileMenu )
1045
        fileMenu.add_command( label="New", font=font, command=self.newTopology )
1046
        fileMenu.add_command( label="Open", font=font, command=self.loadTopology )
1047
        fileMenu.add_command( label="Save", font=font, command=self.saveTopology )
1048
        fileMenu.add_command( label="Export", font=font, command=self.exportTopology )
1049
        fileMenu.add_separator()
1050
        fileMenu.add_command( label='Quit', command=self.quit, font=font )
1051

    
1052
        editMenu = Menu( mbar, tearoff=False )
1053
        mbar.add_cascade( label="Edit", font=font, menu=editMenu )
1054
        editMenu.add_command( label="Cut", font=font,
1055
                              command=lambda: self.deleteSelection( None ) )
1056
        editMenu.add_command( label="Preferences", font=font, command=self.prefDetails)
1057

    
1058
        runMenu = Menu( mbar, tearoff=False )
1059
        mbar.add_cascade( label="Run", font=font, menu=runMenu )
1060
        runMenu.add_command( label="Run", font=font, command=self.doRun )
1061
        runMenu.add_command( label="Stop", font=font, command=self.doStop )
1062
        fileMenu.add_separator()
1063
        runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow )
1064
        runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal )
1065

    
1066
        # Application menu
1067
        appMenu = Menu( mbar, tearoff=False )
1068
        mbar.add_cascade( label="Help", font=font, menu=appMenu )
1069
        appMenu.add_command( label='About MiniEdit', command=self.about,
1070
                             font=font)
1071
    # Canvas
1072

    
1073
    def createCanvas( self ):
1074
        "Create and return our scrolling canvas frame."
1075
        f = Frame( self )
1076

    
1077
        canvas = Canvas( f, width=self.cwidth, height=self.cheight,
1078
                         bg=self.bg )
1079

    
1080
        # Scroll bars
1081
        xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
1082
        ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
1083
        canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
1084

    
1085
        # Resize box
1086
        resize = Label( f, bg='white' )
1087

    
1088
        # Layout
1089
        canvas.grid( row=0, column=1, sticky='nsew')
1090
        ybar.grid( row=0, column=2, sticky='ns')
1091
        xbar.grid( row=1, column=1, sticky='ew' )
1092
        resize.grid( row=1, column=2, sticky='nsew' )
1093

    
1094
        # Resize behavior
1095
        f.rowconfigure( 0, weight=1 )
1096
        f.columnconfigure( 1, weight=1 )
1097
        f.grid( row=0, column=0, sticky='nsew' )
1098
        f.bind( '<Configure>', lambda event: self.updateScrollRegion() )
1099

    
1100
        # Mouse bindings
1101
        canvas.bind( '<ButtonPress-1>', self.clickCanvas )
1102
        canvas.bind( '<B1-Motion>', self.dragCanvas )
1103
        canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
1104

    
1105
        return f, canvas
1106

    
1107
    def updateScrollRegion( self ):
1108
        "Update canvas scroll region to hold everything."
1109
        bbox = self.canvas.bbox( 'all' )
1110
        if bbox is not None:
1111
            self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ],
1112
                                   bbox[ 3 ] ) )
1113

    
1114
    def canvasx( self, x_root ):
1115
        "Convert root x coordinate to canvas coordinate."
1116
        c = self.canvas
1117
        return c.canvasx( x_root ) - c.winfo_rootx()
1118

    
1119
    def canvasy( self, y_root ):
1120
        "Convert root y coordinate to canvas coordinate."
1121
        c = self.canvas
1122
        return c.canvasy( y_root ) - c.winfo_rooty()
1123

    
1124
    # Toolbar
1125

    
1126
    def activate( self, toolName ):
1127
        "Activate a tool and press its button."
1128
        # Adjust button appearance
1129
        if self.active:
1130
            self.buttons[ self.active ].configure( relief='raised' )
1131
        self.buttons[ toolName ].configure( relief='sunken' )
1132
        # Activate dynamic bindings
1133
        self.active = toolName
1134

    
1135
    def createToolbar( self ):
1136
        "Create and return our toolbar frame."
1137

    
1138
        toolbar = Frame( self )
1139

    
1140
        # Tools
1141
        for tool in self.tools:
1142
            cmd = ( lambda t=tool: self.activate( t ) )
1143
            b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
1144
            if tool in self.images:
1145
                b.config( height=35, image=self.images[ tool ] )
1146
                # b.config( compound='top' )
1147
            b.pack( fill='x' )
1148
            self.buttons[ tool ] = b
1149
        self.activate( self.tools[ 0 ] )
1150

    
1151
        # Spacer
1152
        Label( toolbar, text='' ).pack()
1153

    
1154
        # Commands
1155
        for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]:
1156
            doCmd = getattr( self, 'do' + cmd )
1157
            b = Button( toolbar, text=cmd, font=self.smallFont,
1158
                        fg=color, command=doCmd )
1159
            b.pack( fill='x', side='bottom' )
1160

    
1161
        return toolbar
1162

    
1163
    def doRun( self ):
1164
        "Run command."
1165
        self.activate( 'Select' )
1166
        for tool in self.tools:
1167
            self.buttons[ tool ].config( state='disabled' )
1168
        self.start()
1169

    
1170
    def doStop( self ):
1171
        "Stop command."
1172
        self.stop()
1173
        for tool in self.tools:
1174
            self.buttons[ tool ].config( state='normal' )
1175

    
1176
    def addNode( self, node, nodeNum, x, y, name=None):
1177
        "Add a new node to our canvas."
1178
        if 'Switch' == node:
1179
            self.switchCount += 1
1180
        if 'Host' == node:
1181
            self.hostCount += 1
1182
        if 'Controller' == node:
1183
            self.controllerCount += 1
1184
        if name is None:
1185
            name = self.nodePrefixes[ node ] + nodeNum
1186
        self.addNamedNode(node, name, x, y)
1187

    
1188
    def addNamedNode( self, node, name, x, y):
1189
        "Add a new node to our canvas."
1190
        c = self.canvas
1191
        icon = self.nodeIcon( node, name )
1192
        item = self.canvas.create_window( x, y, anchor='c', window=icon,
1193
                                          tags=node )
1194
        self.widgetToItem[ icon ] = item
1195
        self.itemToWidget[ item ] = icon
1196
        icon.links = {}
1197

    
1198
    def loadTopology( self ):
1199
        "Load command."
1200
        c = self.canvas
1201

    
1202
        myFormats = [
1203
            ('Mininet Topology','*.mn'),
1204
            ('All Files','*'),
1205
        ]
1206
        f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
1207
        if f == None:
1208
            return
1209
        self.newTopology()
1210
        loadedTopology = eval(f.read())
1211

    
1212
        # Load application preferences
1213
        if 'application' in loadedTopology:
1214
            self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items())
1215
            if "ovsOf10" not in self.appPrefs["openFlowVersions"]:
1216
                self.appPrefs["openFlowVersions"]["ovsOf10"] = '0'
1217
            if "ovsOf11" not in self.appPrefs["openFlowVersions"]:
1218
                self.appPrefs["openFlowVersions"]["ovsOf11"] = '0'
1219
            if "ovsOf12" not in self.appPrefs["openFlowVersions"]:
1220
                self.appPrefs["openFlowVersions"]["ovsOf12"] = '0'
1221
            if "ovsOf13" not in self.appPrefs["openFlowVersions"]:
1222
                self.appPrefs["openFlowVersions"]["ovsOf13"] = '0'
1223
            if "sflow" not in self.appPrefs:
1224
                self.appPrefs["sflow"] = self.sflowDefaults
1225
            if "netflow" not in self.appPrefs:
1226
                self.appPrefs["netflow"] = self.nflowDefaults
1227

    
1228
        # Load controllers
1229
        if ('controllers' in loadedTopology):
1230
            if (loadedTopology['version'] == '1'):
1231
                # This is old location of controller info
1232
                hostname = 'c0'
1233
                self.controllers = {}
1234
                self.controllers[hostname] = loadedTopology['controllers']['c0']
1235
                self.controllers[hostname]['hostname'] = hostname
1236
                self.addNode('Controller', 0, float(30), float(30), name=hostname)
1237
                icon = self.findWidgetByName(hostname)
1238
                icon.bind('<Button-3>', self.do_controllerPopup )
1239
            else:
1240
                controllers = loadedTopology['controllers']
1241
                for controller in controllers:
1242
                    hostname = controller['opts']['hostname']
1243
                    x = controller['x']
1244
                    y = controller['y']
1245
                    self.addNode('Controller', 0, float(x), float(y), name=hostname)
1246
                    self.controllers[hostname] = controller['opts']
1247
                    icon = self.findWidgetByName(hostname)
1248
                    icon.bind('<Button-3>', self.do_controllerPopup )
1249

    
1250

    
1251
        # Load hosts
1252
        hosts = loadedTopology['hosts']
1253
        for host in hosts:
1254
            nodeNum = host['number']
1255
            hostname = 'h'+nodeNum
1256
            if 'hostname' in host['opts']:
1257
                hostname = host['opts']['hostname']
1258
            else:
1259
                host['opts']['hostname'] = hostname
1260
            if 'nodeNum' not in host['opts']:
1261
                host['opts']['nodeNum'] = int(nodeNum)
1262
            x = host['x']
1263
            y = host['y']
1264
            self.addNode('Host', nodeNum, float(x), float(y), name=hostname)
1265
            self.hostOpts[hostname] = host['opts']
1266
            icon = self.findWidgetByName(hostname)
1267
            icon.bind('<Button-3>', self.do_hostPopup )
1268

    
1269
        # Load switches
1270
        switches = loadedTopology['switches']
1271
        for switch in switches:
1272
            nodeNum = switch['number']
1273
            hostname = 's'+nodeNum
1274
            if 'controllers' not in switch['opts']:
1275
                switch['opts']['controllers'] = []
1276
            if 'switchType' not in switch['opts']:
1277
                switch['opts']['switchType'] = 'default'
1278
            if 'hostname' in switch['opts']:
1279
                hostname = switch['opts']['hostname']
1280
            else:
1281
                switch['opts']['hostname'] = hostname
1282
            if 'nodeNum' not in switch['opts']:
1283
                switch['opts']['nodeNum'] = int(nodeNum)
1284
            x = switch['x']
1285
            y = switch['y']
1286
            self.addNode('Switch', nodeNum, float(x), float(y), name=hostname)
1287
            self.switchOpts[hostname] = switch['opts']
1288
            icon = self.findWidgetByName(hostname)
1289
            icon.bind('<Button-3>', self.do_switchPopup )
1290

    
1291
            # create links to controllers
1292
            if (int(loadedTopology['version']) > 1):
1293
                controllers = self.switchOpts[hostname]['controllers']
1294
                for controller in controllers:
1295
                    dest = self.findWidgetByName(controller)
1296
                    dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1297
                    self.link = self.canvas.create_line(float(x),
1298
                                                        float(y),
1299
                                                        dx,
1300
                                                        dy,
1301
                                                        width=4,
1302
                                                        fill='red',
1303
                                                        dash=(6, 4, 2, 4),
1304
                                                        tag='link' )
1305
                    c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
1306
                    self.addLink( icon, dest, linktype='control' )
1307
                    self.createControlLinkBindings()
1308
                    self.link = self.linkWidget = None
1309
            else:
1310
                dest = self.findWidgetByName('c0')
1311
                dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1312
                self.link = self.canvas.create_line(float(x),
1313
                                                    float(y),
1314
                                                    dx,
1315
                                                    dy,
1316
                                                    width=4,
1317
                                                    fill='red',
1318
                                                    dash=(6, 4, 2, 4),
1319
                                                    tag='link' )
1320
                c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
1321
                self.addLink( icon, dest, linktype='control' )
1322
                self.createControlLinkBindings()
1323
                self.link = self.linkWidget = None
1324

    
1325
        # Load links
1326
        links = loadedTopology['links']
1327
        for link in links:
1328
            srcNode = link['src']
1329
            src = self.findWidgetByName(srcNode)
1330
            sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
1331

    
1332
            destNode = link['dest']
1333
            dest = self.findWidgetByName(destNode)
1334
            dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
1335

    
1336
            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
1337
                                             fill='blue', tag='link' )
1338
            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
1339
            self.addLink( src, dest, linkopts=link['opts'] )
1340
            self.createDataLinkBindings()
1341
            self.link = self.linkWidget = None
1342

    
1343
        f.close
1344

    
1345
    def findWidgetByName( self, name ):
1346
        for widget in self.widgetToItem:
1347
            if name ==  widget[ 'text' ]:
1348
                return widget
1349

    
1350
    def newTopology( self ):
1351
        "New command."
1352
        for widget in self.widgetToItem.keys():
1353
            self.deleteItem( self.widgetToItem[ widget ] )
1354
        self.hostCount = 0
1355
        self.switchCount = 0
1356
        self.controllerCount = 0
1357
        self.links = {}
1358
        self.hostOpts = {}
1359
        self.switchOpts = {}
1360
        self.controllers = {}
1361
        self.appPrefs["ipBase"]= self.defaultIpBase
1362

    
1363
    def saveTopology( self ):
1364
        "Save command."
1365
        myFormats = [
1366
            ('Mininet Topology','*.mn'),
1367
            ('All Files','*'),
1368
        ]
1369

    
1370
        savingDictionary = {}
1371
        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...")
1372
        if len(fileName ) > 0:
1373
            # Save Application preferences
1374
            savingDictionary['version'] = '2'
1375

    
1376
            # Save Switches and Hosts
1377
            hostsToSave = []
1378
            switchesToSave = []
1379
            controllersToSave = []
1380
            for widget in self.widgetToItem:
1381
                name = widget[ 'text' ]
1382
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1383
                x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] )
1384
                if 'Switch' in tags:
1385
                    nodeNum = self.switchOpts[name]['nodeNum']
1386
                    nodeToSave = {'number':str(nodeNum),
1387
                                  'x':str(x1),
1388
                                  'y':str(y1),
1389
                                  'opts':self.switchOpts[name] }
1390
                    switchesToSave.append(nodeToSave)
1391
                elif 'Host' in tags:
1392
                    nodeNum = self.hostOpts[name]['nodeNum']
1393
                    nodeToSave = {'number':str(nodeNum),
1394
                                  'x':str(x1),
1395
                                  'y':str(y1),
1396
                                  'opts':self.hostOpts[name] }
1397
                    hostsToSave.append(nodeToSave)
1398
                elif 'Controller' in tags:
1399
                    nodeToSave = {'x':str(x1),
1400
                                  'y':str(y1),
1401
                                  'opts':self.controllers[name] }
1402
                    controllersToSave.append(nodeToSave)
1403
                else:
1404
                    raise Exception( "Cannot create mystery node: " + name )
1405
            savingDictionary['hosts'] = hostsToSave
1406
            savingDictionary['switches'] = switchesToSave
1407
            savingDictionary['controllers'] = controllersToSave
1408

    
1409
            # Save Links
1410
            linksToSave = []
1411
            for link in self.links.values():
1412
                src = link['src']
1413
                dst = link['dest']
1414
                linkopts = link['linkOpts']
1415

    
1416
                srcName, dstName = src[ 'text' ], dst[ 'text' ]
1417
                linkToSave = {'src':srcName,
1418
                              'dest':dstName,
1419
                              'opts':linkopts}
1420
                if link['type'] == 'data':
1421
                    linksToSave.append(linkToSave)
1422
            savingDictionary['links'] = linksToSave
1423

    
1424
            # Save Application preferences
1425
            savingDictionary['application'] = self.appPrefs
1426

    
1427
            try:
1428
                f = open(fileName, 'wb')
1429
                #f.write(str(savingDictionary))
1430
                f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': ')))
1431
            except Exception as er:
1432
                print er
1433
            finally:
1434
                f.close()
1435

    
1436
    def exportTopology( self ):
1437
        "Export command."
1438
        myFormats = [
1439
            ('Mininet Custom Topology','*.py'),
1440
            ('All Files','*'),
1441
        ]
1442

    
1443
        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...")
1444
        if len(fileName ) > 0:
1445
            #print "Now saving under %s" % fileName
1446
            f = open(fileName, 'wb')
1447

    
1448
            f.write("#!/usr/bin/python\n")
1449
            f.write("\n")
1450
            f.write("from mininet.net import Mininet\n")
1451
            f.write("from mininet.node import Controller, RemoteController, OVSController\n")
1452
            f.write("from mininet.node import CPULimitedHost, Host\n")
1453
            f.write("from mininet.node import OVSKernelSwitch\n")
1454
            if StrictVersion(VERSION) > StrictVersion('2.0'):
1455
                f.write("from mininet.node import IVSSwitch\n")
1456
            f.write("from mininet.cli import CLI\n")
1457
            f.write("from mininet.log import setLogLevel, info\n")
1458
            f.write("from mininet.link import TCLink, Intf\n")
1459

    
1460
            inBandCtrl = False
1461
            for widget in self.widgetToItem:
1462
                name = widget[ 'text' ]
1463
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1464

    
1465
                if 'Controller' in tags:
1466
                    opts = self.controllers[name]
1467
                    controllerType = opts['controllerType']
1468
                    if controllerType == 'inband':
1469
                        inBandCtrl = True
1470

    
1471
            if inBandCtrl == True:
1472
                f.write("\n")
1473
                f.write("class InbandController( RemoteController ):\n")
1474
                f.write("\n")
1475
                f.write("    def checkListening( self ):\n")
1476
                f.write("        \"Overridden to do nothing.\"\n")
1477
                f.write("        return\n")
1478

    
1479
            f.write("\n")
1480
            f.write("def myNetwork():\n")
1481
            f.write("\n")
1482
            f.write("    net = Mininet( topo=None,\n")
1483
            if len(self.appPrefs['dpctl']) > 0:
1484
                f.write("                   listenPort="+self.appPrefs['dpctl']+",\n")
1485
            f.write("                   build=False,\n")
1486
            f.write("                   ipBase='"+self.appPrefs['ipBase']+"',\n")
1487
            f.write("                   link=TCLink)\n")
1488
            f.write("\n")
1489
            f.write("    info( '*** Adding controller\\n' )\n")
1490
            for widget in self.widgetToItem:
1491
                name = widget[ 'text' ]
1492
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1493
    
1494
                if 'Controller' in tags:
1495
                    opts = self.controllers[name]
1496
                    controllerType = opts['controllerType']
1497
                    controllerIP = opts['remoteIP']
1498
                    controllerPort = opts['remotePort']
1499

    
1500
    
1501
                    f.write("    "+name+"=net.addController(name='"+name+"',\n")
1502
        
1503
                    if controllerType == 'remote':
1504
                        f.write("                      controller=RemoteController,\n")
1505
                        f.write("                      ip='"+controllerIP+"',\n")
1506
                    elif controllerType == 'inband':
1507
                        f.write("                      controller=InbandController,\n")
1508
                        f.write("                      ip='"+controllerIP+"',\n")
1509
                    elif controllerType == 'ovsc':
1510
                        f.write("                      controller=OVSController,\n")
1511
                    else:
1512
                        f.write("                      controller=Controller,\n")
1513
        
1514
                    f.write("                      port="+str(controllerPort)+")\n")
1515
                    f.write("\n")
1516

    
1517
            # Save Switches and Hosts
1518
            f.write("    info( '*** Add switches\\n')\n")
1519
            for widget in self.widgetToItem:
1520
                name = widget[ 'text' ]
1521
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1522
                if 'Switch' in tags:
1523
                    opts = self.switchOpts[name]
1524
                    nodeNum = opts['nodeNum']
1525
                    f.write("    "+name+" = net.addSwitch('"+name+"'")
1526
                    if opts['switchType'] == 'default':
1527
                        if self.appPrefs['switchType'] == 'ivs':
1528
                            f.write(", cls=IVSSwitch")
1529
                        else:
1530
                            f.write(", cls=OVSKernelSwitch")
1531
                    elif opts['switchType'] == 'ivs':
1532
                        f.write(", cls=IVSSwitch")
1533
                    else:
1534
                        f.write(", cls=OVSKernelSwitch")
1535
                    if 'dpctl' in opts:
1536
                        f.write(", listenPort="+opts['dpctl'])
1537
                    if 'dpid' in opts:
1538
                        f.write(", dpid='"+opts['dpid']+"'")
1539
                    f.write(")\n")
1540
                    if ('externalInterfaces' in opts):
1541
                        for extInterface in opts['externalInterfaces']:
1542
                            f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
1543

    
1544
            f.write("\n")
1545
            f.write("    info( '*** Add hosts\\n')\n")
1546
            for widget in self.widgetToItem:
1547
                name = widget[ 'text' ]
1548
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1549
                if 'Host' in tags:
1550
                    opts = self.hostOpts[name]
1551
                    ip = None
1552
                    defaultRoute = None
1553
                    if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
1554
                        defaultRoute = "'via "+opts['defaultRoute']+"'"
1555
                    else:
1556
                        defaultRoute = 'None'
1557
                    if 'ip' in opts and len(opts['ip']) > 0:
1558
                        ip = opts['ip']
1559
                    else:
1560
                        nodeNum = self.hostOpts[name]['nodeNum']
1561
                        ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
1562
                        ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
1563

    
1564
                    if 'cores' in opts or 'cpu' in opts:
1565
                        f.write("    "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
1566
                        if 'cores' in opts:
1567
                            f.write("    "+name+".setCPUs(cores='"+opts['cores']+"')\n")
1568
                        if 'cpu' in opts:
1569
                            f.write("    "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n")
1570
                    else:
1571
                        f.write("    "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
1572
                    if ('externalInterfaces' in opts):
1573
                        for extInterface in opts['externalInterfaces']:
1574
                            f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
1575
            f.write("\n")
1576

    
1577
            # Save Links
1578
            f.write("    info( '*** Add links\\n')\n")
1579
            for key,linkDetail in self.links.iteritems():
1580
              tags = self.canvas.gettags(key)
1581
              if 'data' in tags:
1582
                optsExist = False
1583
                src = linkDetail['src']
1584
                dst = linkDetail['dest']
1585
                linkopts = linkDetail['linkOpts']
1586
                srcName, dstName = src[ 'text' ], dst[ 'text' ]
1587
                bw = ''
1588
                delay = ''
1589
                loss = ''
1590
                max_queue_size = ''
1591
                linkOpts = "{"
1592
                if 'bw' in linkopts:
1593
                    bw =  linkopts['bw']
1594
                    linkOpts = linkOpts + "'bw':"+str(bw)
1595
                    optsExist = True
1596
                if 'delay' in linkopts:
1597
                    delay =  linkopts['delay']
1598
                    if optsExist:
1599
                        linkOpts = linkOpts + ","
1600
                    linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'"
1601
                    optsExist = True
1602
                if 'loss' in linkopts:
1603
                    if optsExist:
1604
                        linkOpts = linkOpts + ","
1605
                    linkOpts = linkOpts + "'loss':"+str(linkopts['loss'])
1606
                    optsExist = True
1607
                if 'max_queue_size' in linkopts:
1608
                    if optsExist:
1609
                        linkOpts = linkOpts + ","
1610
                    linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size'])
1611
                    optsExist = True
1612
                if 'jitter' in linkopts:
1613
                    if optsExist:
1614
                        linkOpts = linkOpts + ","
1615
                    linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'"
1616
                    optsExist = True
1617
                if 'speedup' in linkopts:
1618
                    if optsExist:
1619
                        linkOpts = linkOpts + ","
1620
                    linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup'])
1621
                    optsExist = True
1622

    
1623
                linkOpts = linkOpts + "}"
1624
                if optsExist:
1625
                    f.write("    "+srcName+dstName+" = "+linkOpts+"\n")
1626
                f.write("    net.addLink("+srcName+", "+dstName)
1627
                if optsExist:
1628
                    f.write(", **"+srcName+dstName)
1629
                f.write(")\n")
1630

    
1631
            f.write("\n")
1632
            f.write("    info( '*** Starting network\\n')\n")
1633
            f.write("    net.build()\n")
1634

    
1635
            f.write("    info( '*** Starting controllers\\n')\n")
1636
            f.write("    for controller in net.controllers:\n")
1637
            f.write("        controller.start()\n")
1638
            f.write("\n")
1639

    
1640
            f.write("    info( '*** Starting switches\\n')\n")
1641
            for widget in self.widgetToItem:
1642
                name = widget[ 'text' ]
1643
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1644
                if 'Switch' in tags:
1645
                    opts = self.switchOpts[name]
1646
                    ctrlList = ",".join(opts['controllers'])
1647
                    f.write("    net.get('"+name+"').start(["+ctrlList+"])\n")
1648

    
1649
            f.write("\n")
1650

    
1651
            f.write("    info( '*** Configuring switches\\n')\n")
1652
            for widget in self.widgetToItem:
1653
                name = widget[ 'text' ]
1654
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1655
                if 'Switch' in tags:
1656
                    opts = self.switchOpts[name]
1657
                    if ('switchIP' in opts):
1658
                        if (len(opts['switchIP'])>0):
1659
                            f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
1660
            for widget in self.widgetToItem:
1661
                name = widget[ 'text' ]
1662
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1663
                if 'Host' in tags:
1664
                    opts = self.hostOpts[name]
1665
                    # Attach vlan interfaces
1666
                    if ('vlanInterfaces' in opts):
1667
                        for vlanInterface in opts['vlanInterfaces']:
1668
                            f.write("    "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n")
1669
                            f.write("    "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n")
1670

    
1671

    
1672
            f.write("\n")
1673
            f.write("    CLI(net)\n")
1674
            f.write("    net.stop()\n")
1675
            f.write("\n")
1676
            f.write("if __name__ == '__main__':\n")
1677
            f.write("    setLogLevel( 'info' )\n")
1678
            f.write("    myNetwork()\n")
1679
            f.write("\n")
1680

    
1681

    
1682
            f.close()
1683

    
1684

    
1685
    # Generic canvas handler
1686
    #
1687
    # We could have used bindtags, as in nodeIcon, but
1688
    # the dynamic approach used here
1689
    # may actually require less code. In any case, it's an
1690
    # interesting introspection-based alternative to bindtags.
1691

    
1692
    def canvasHandle( self, eventName, event ):
1693
        "Generic canvas event handler"
1694
        if self.active is None:
1695
            return
1696
        toolName = self.active
1697
        handler = getattr( self, eventName + toolName, None )
1698
        if handler is not None:
1699
            handler( event )
1700

    
1701
    def clickCanvas( self, event ):
1702
        "Canvas click handler."
1703
        self.canvasHandle( 'click', event )
1704

    
1705
    def dragCanvas( self, event ):
1706
        "Canvas drag handler."
1707
        self.canvasHandle( 'drag', event )
1708

    
1709
    def releaseCanvas( self, event ):
1710
        "Canvas mouse up handler."
1711
        self.canvasHandle( 'release', event )
1712

    
1713
    # Currently the only items we can select directly are
1714
    # links. Nodes are handled by bindings in the node icon.
1715

    
1716
    def findItem( self, x, y ):
1717
        "Find items at a location in our canvas."
1718
        items = self.canvas.find_overlapping( x, y, x, y )
1719
        if len( items ) == 0:
1720
            return None
1721
        else:
1722
            return items[ 0 ]
1723

    
1724
    # Canvas bindings for Select, Host, Switch and Link tools
1725

    
1726
    def clickSelect( self, event ):
1727
        "Select an item."
1728
        self.selectItem( self.findItem( event.x, event.y ) )
1729

    
1730
    def deleteItem( self, item ):
1731
        "Delete an item."
1732
        # Don't delete while network is running
1733
        if self.buttons[ 'Select' ][ 'state' ] == 'disabled':
1734
            return
1735
        # Delete from model
1736
        if item in self.links:
1737
            self.deleteLink( item )
1738
        if item in self.itemToWidget:
1739
            self.deleteNode( item )
1740
        # Delete from view
1741
        self.canvas.delete( item )
1742

    
1743
    def deleteSelection( self, _event ):
1744
        "Delete the selected item."
1745
        if self.selection is not None:
1746
            self.deleteItem( self.selection )
1747
        self.selectItem( None )
1748

    
1749
    def nodeIcon( self, node, name ):
1750
        "Create a new node icon."
1751
        icon = Button( self.canvas, image=self.images[ node ],
1752
                       text=name, compound='top' )
1753
        # Unfortunately bindtags wants a tuple
1754
        bindtags = [ str( self.nodeBindings ) ]
1755
        bindtags += list( icon.bindtags() )
1756
        icon.bindtags( tuple( bindtags ) )
1757
        return icon
1758

    
1759
    def newNode( self, node, event ):
1760
        "Add a new node to our canvas."
1761
        c = self.canvas
1762
        x, y = c.canvasx( event.x ), c.canvasy( event.y )
1763
        name = self.nodePrefixes[ node ]
1764
        if 'Switch' == node:
1765
            self.switchCount += 1
1766
            name = self.nodePrefixes[ node ] + str( self.switchCount )
1767
            self.switchOpts[name] = {}
1768
            self.switchOpts[name]['nodeNum']=self.switchCount
1769
            self.switchOpts[name]['hostname']=name
1770
            self.switchOpts[name]['switchType']='default'
1771
            self.switchOpts[name]['controllers']=[]
1772
        if 'Host' == node:
1773
            self.hostCount += 1
1774
            name = self.nodePrefixes[ node ] + str( self.hostCount )
1775
            self.hostOpts[name] = {'sched':'host'}
1776
            self.hostOpts[name]['nodeNum']=self.hostCount
1777
            self.hostOpts[name]['hostname']=name
1778
        if 'Controller' == node:
1779
            name = self.nodePrefixes[ node ] + str( self.controllerCount )
1780
            ctrlr = { 'controllerType': 'ref',
1781
                      'hostname': name,
1782
                      'remoteIP': '127.0.0.1',
1783
                      'remotePort': 6633}
1784
            self.controllers[name] = ctrlr
1785
            # We want to start controller count at 0
1786
            self.controllerCount += 1
1787

    
1788
        icon = self.nodeIcon( node, name )
1789
        item = self.canvas.create_window( x, y, anchor='c', window=icon,
1790
                                          tags=node )
1791
        self.widgetToItem[ icon ] = item
1792
        self.itemToWidget[ item ] = icon
1793
        self.selectItem( item )
1794
        icon.links = {}
1795
        if 'Switch' == node:
1796
            icon.bind('<Button-3>', self.do_switchPopup )
1797
        if 'Host' == node:
1798
            icon.bind('<Button-3>', self.do_hostPopup )
1799
        if 'Controller' == node:
1800
            icon.bind('<Button-3>', self.do_controllerPopup )
1801

    
1802
    def clickController( self, event ):
1803
        "Add a new Controller to our canvas."
1804
        self.newNode( 'Controller', event )
1805

    
1806
    def clickHost( self, event ):
1807
        "Add a new host to our canvas."
1808
        self.newNode( 'Host', event )
1809

    
1810
    def clickSwitch( self, event ):
1811
        "Add a new switch to our canvas."
1812
        self.newNode( 'Switch', event )
1813

    
1814
    def dragLink( self, event ):
1815
        "Drag a link's endpoint to another node."
1816
        if self.link is None:
1817
            return
1818
        # Since drag starts in widget, we use root coords
1819
        x = self.canvasx( event.x_root )
1820
        y = self.canvasy( event.y_root )
1821
        c = self.canvas
1822
        c.coords( self.link, self.linkx, self.linky, x, y )
1823

    
1824
    def releaseLink( self, _event ):
1825
        "Give up on the current link."
1826
        if self.link is not None:
1827
            self.canvas.delete( self.link )
1828
        self.linkWidget = self.linkItem = self.link = None
1829

    
1830
    # Generic node handlers
1831

    
1832
    def createNodeBindings( self ):
1833
        "Create a set of bindings for nodes."
1834
        bindings = {
1835
            '<ButtonPress-1>': self.clickNode,
1836
            '<B1-Motion>': self.dragNode,
1837
            '<ButtonRelease-1>': self.releaseNode,
1838
            '<Enter>': self.enterNode,
1839
            '<Leave>': self.leaveNode
1840
        }
1841
        l = Label()  # lightweight-ish owner for bindings
1842
        for event, binding in bindings.items():
1843
            l.bind( event, binding )
1844
        return l
1845

    
1846
    def selectItem( self, item ):
1847
        "Select an item and remember old selection."
1848
        self.lastSelection = self.selection
1849
        self.selection = item
1850

    
1851
    def enterNode( self, event ):
1852
        "Select node on entry."
1853
        self.selectNode( event )
1854

    
1855
    def leaveNode( self, _event ):
1856
        "Restore old selection on exit."
1857
        self.selectItem( self.lastSelection )
1858

    
1859
    def clickNode( self, event ):
1860
        "Node click handler."
1861
        if self.active is 'Link':
1862
            self.startLink( event )
1863
        else:
1864
            self.selectNode( event )
1865
        return 'break'
1866

    
1867
    def dragNode( self, event ):
1868
        "Node drag handler."
1869
        if self.active is 'Link':
1870
            self.dragLink( event )
1871
        else:
1872
            self.dragNodeAround( event )
1873

    
1874
    def releaseNode( self, event ):
1875
        "Node release handler."
1876
        if self.active is 'Link':
1877
            self.finishLink( event )
1878

    
1879
    # Specific node handlers
1880

    
1881
    def selectNode( self, event ):
1882
        "Select the node that was clicked on."
1883
        item = self.widgetToItem.get( event.widget, None )
1884
        self.selectItem( item )
1885

    
1886
    def dragNodeAround( self, event ):
1887
        "Drag a node around on the canvas."
1888
        c = self.canvas
1889
        # Convert global to local coordinates;
1890
        # Necessary since x, y are widget-relative
1891
        x = self.canvasx( event.x_root )
1892
        y = self.canvasy( event.y_root )
1893
        w = event.widget
1894
        # Adjust node position
1895
        item = self.widgetToItem[ w ]
1896
        c.coords( item, x, y )
1897
        # Adjust link positions
1898
        for dest in w.links:
1899
            link = w.links[ dest ]
1900
            item = self.widgetToItem[ dest ]
1901
            x1, y1 = c.coords( item )
1902
            c.coords( link, x, y, x1, y1 )
1903
        self.updateScrollRegion()
1904

    
1905
    def createControlLinkBindings( self ):
1906
        "Create a set of bindings for nodes."
1907
        # Link bindings
1908
        # Selection still needs a bit of work overall
1909
        # Callbacks ignore event
1910

    
1911
        def select( _event, link=self.link ):
1912
            "Select item on mouse entry."
1913
            self.selectItem( link )
1914

    
1915
        def highlight( _event, link=self.link ):
1916
            "Highlight item on mouse entry."
1917
            self.selectItem( link )
1918
            self.canvas.itemconfig( link, fill='green' )
1919

    
1920
        def unhighlight( _event, link=self.link ):
1921
            "Unhighlight item on mouse exit."
1922
            self.canvas.itemconfig( link, fill='red' )
1923
            #self.selectItem( None )
1924

    
1925
        self.canvas.tag_bind( self.link, '<Enter>', highlight )
1926
        self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
1927
        self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
1928

    
1929
    def createDataLinkBindings( self ):
1930
        "Create a set of bindings for nodes."
1931
        # Link bindings
1932
        # Selection still needs a bit of work overall
1933
        # Callbacks ignore event
1934

    
1935
        def select( _event, link=self.link ):
1936
            "Select item on mouse entry."
1937
            self.selectItem( link )
1938

    
1939
        def highlight( _event, link=self.link ):
1940
            "Highlight item on mouse entry."
1941
            self.selectItem( link )
1942
            self.canvas.itemconfig( link, fill='green' )
1943

    
1944
        def unhighlight( _event, link=self.link ):
1945
            "Unhighlight item on mouse exit."
1946
            self.canvas.itemconfig( link, fill='blue' )
1947
            #self.selectItem( None )
1948

    
1949
        self.canvas.tag_bind( self.link, '<Enter>', highlight )
1950
        self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
1951
        self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
1952
        self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup )
1953

    
1954

    
1955
    def startLink( self, event ):
1956
        "Start a new link."
1957
        if event.widget not in self.widgetToItem:
1958
            # Didn't click on a node
1959
            return
1960

    
1961
        w = event.widget
1962
        item = self.widgetToItem[ w ]
1963
        x, y = self.canvas.coords( item )
1964
        self.link = self.canvas.create_line( x, y, x, y, width=4,
1965
                                             fill='blue', tag='link' )
1966
        self.linkx, self.linky = x, y
1967
        self.linkWidget = w
1968
        self.linkItem = item
1969

    
1970

    
1971
    def finishLink( self, event ):
1972
        "Finish creating a link"
1973
        if self.link is None:
1974
            return
1975
        source = self.linkWidget
1976
        c = self.canvas
1977
        # Since we dragged from the widget, use root coords
1978
        x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root )
1979
        target = self.findItem( x, y )
1980
        dest = self.itemToWidget.get( target, None )
1981
        if ( source is None or dest is None or source == dest
1982
                or dest in source.links or source in dest.links ):
1983
            self.releaseLink( event )
1984
            return
1985
        # For now, don't allow hosts to be directly linked
1986
        stags = self.canvas.gettags( self.widgetToItem[ source ] )
1987
        dtags = self.canvas.gettags( target )
1988
        if (('Host' in stags and 'Host' in dtags) or
1989
           ('Controller' in stags and 'Host' in dtags) or
1990
           ('Controller' in dtags and 'Host' in stags) or
1991
           ('Controller' in stags and 'Controller' in dtags)):
1992
            self.releaseLink( event )
1993
            return
1994

    
1995
        # Set link type
1996
        linkType='data'
1997
        if 'Controller' in stags or 'Controller' in dtags:
1998
            linkType='control'
1999
            c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red')
2000
            self.createControlLinkBindings()
2001
        else:
2002
            linkType='data'
2003
            self.createDataLinkBindings()
2004
        c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,))
2005

    
2006
        x, y = c.coords( target )
2007
        c.coords( self.link, self.linkx, self.linky, x, y )
2008
        self.addLink( source, dest, linktype=linkType )
2009
        if linkType == 'control':
2010
            controllerName = ''
2011
            switchName = ''
2012
            if 'Controller' in stags:
2013
                controllerName = source[ 'text' ]
2014
                switchName = dest[ 'text' ]
2015
            else:
2016
                controllerName = dest[ 'text' ]
2017
                switchName = source[ 'text' ]
2018

    
2019
            self.switchOpts[switchName]['controllers'].append(controllerName)
2020

    
2021
        # We're done
2022
        self.link = self.linkWidget = None
2023

    
2024
    # Menu handlers
2025

    
2026
    def about( self ):
2027
        "Display about box."
2028
        about = self.aboutBox
2029
        if about is None:
2030
            bg = 'white'
2031
            about = Toplevel( bg='white' )
2032
            about.title( 'About' )
2033
            info = self.appName + ': a simple network editor for MiniNet'
2034
            version = 'MiniEdit '+MINIEDIT_VERSION
2035
            author = 'Originally by: Bob Lantz <rlantz@cs>, April 2010'
2036
            enhancements = 'Enhancements by: Gregory Gee, Since July 2013'
2037
            www = 'http://gregorygee.wordpress.com/category/miniedit/'
2038
            line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg )
2039
            line2 = Label( about, text=version, font='Helvetica 9', bg=bg )
2040
            line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
2041
            line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg )
2042
            line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER )
2043
            line5.insert(0, www)
2044
            line5.configure(state='readonly')
2045
            line1.pack( padx=20, pady=10 )
2046
            line2.pack(pady=10 )
2047
            line3.pack(pady=10 )
2048
            line4.pack(pady=10 )
2049
            line5.pack(pady=10 )
2050
            hide = ( lambda about=about: about.withdraw() )
2051
            self.aboutBox = about
2052
            # Hide on close rather than destroying window
2053
            Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
2054
        # Show (existing) window
2055
        about.deiconify()
2056

    
2057
    def createToolImages( self ):
2058
        "Create toolbar (and icon) images."
2059

    
2060
    def checkIntf( self, intf ):
2061
        "Make sure intf exists and is not configured."
2062
        if ( ' %s:' % intf ) not in quietRun( 'ip link show' ):
2063
            showerror(title="Error",
2064
                      message='External interface ' +intf + ' does not exist! Skipping.')
2065
            return False
2066
        ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) )
2067
        if ips:
2068
            showerror(title="Error",
2069
                      message= intf + ' has an IP address and is probably in use! Skipping.' )
2070
            return False
2071
        return True
2072

    
2073
    def hostDetails( self, _ignore=None ):
2074
        if ( self.selection is None or
2075
             self.net is not None or
2076
             self.selection not in self.itemToWidget ):
2077
            return
2078
        widget = self.itemToWidget[ self.selection ]
2079
        name = widget[ 'text' ]
2080
        tags = self.canvas.gettags( self.selection )
2081
        if 'Host' not in tags:
2082
            return
2083

    
2084
        prefDefaults = self.hostOpts[name]
2085
        hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults)
2086
        self.master.wait_window(hostBox.top)
2087
        if hostBox.result:
2088
            newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']}
2089
            newHostOpts['sched'] = hostBox.result['sched']
2090
            if len(hostBox.result['cpu']) > 0:
2091
                newHostOpts['cpu'] = float(hostBox.result['cpu'])
2092
            if len(hostBox.result['cores']) > 0:
2093
                newHostOpts['cores'] = hostBox.result['cores']
2094
            if len(hostBox.result['hostname']) > 0:
2095
                newHostOpts['hostname'] = hostBox.result['hostname']
2096
                name = hostBox.result['hostname']
2097
                widget[ 'text' ] = name
2098
            if len(hostBox.result['defaultRoute']) > 0:
2099
                newHostOpts['defaultRoute'] = hostBox.result['defaultRoute']
2100
            if len(hostBox.result['ip']) > 0:
2101
                newHostOpts['ip'] = hostBox.result['ip']
2102
            if len(hostBox.result['externalInterfaces']) > 0:
2103
                newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces']
2104
            if len(hostBox.result['vlanInterfaces']) > 0:
2105
                newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces']
2106
            self.hostOpts[name] = newHostOpts
2107
            print 'New host details for ' + name + ' = ' + str(newHostOpts)
2108

    
2109
    def switchDetails( self, _ignore=None ):
2110
        if ( self.selection is None or
2111
             self.net is not None or
2112
             self.selection not in self.itemToWidget ):
2113
            return
2114
        widget = self.itemToWidget[ self.selection ]
2115
        name = widget[ 'text' ]
2116
        tags = self.canvas.gettags( self.selection )
2117
        if 'Switch' not in tags:
2118
            return
2119

    
2120
        prefDefaults = self.switchOpts[name]
2121
        switchBox = SwitchDialog(self, title='Switch Details', prefDefaults=prefDefaults)
2122
        self.master.wait_window(switchBox.top)
2123
        if switchBox.result:
2124
            newSwitchOpts = {'nodeNum':self.switchOpts[name]['nodeNum']}
2125
            newSwitchOpts['switchType'] = switchBox.result['switchType']
2126
            newSwitchOpts['controllers'] = self.switchOpts[name]['controllers']
2127
            if len(switchBox.result['dpctl']) > 0:
2128
                newSwitchOpts['dpctl'] = switchBox.result['dpctl']
2129
            if len(switchBox.result['dpid']) > 0:
2130
                newSwitchOpts['dpid'] = switchBox.result['dpid']
2131
            if len(switchBox.result['hostname']) > 0:
2132
                newSwitchOpts['hostname'] = switchBox.result['hostname']
2133
                name = switchBox.result['hostname']
2134
                widget[ 'text' ] = name
2135
            if len(switchBox.result['externalInterfaces']) > 0:
2136
                newSwitchOpts['externalInterfaces'] = switchBox.result['externalInterfaces']
2137
            newSwitchOpts['switchIP'] = switchBox.result['switchIP']
2138
            newSwitchOpts['sflow'] = switchBox.result['sflow']
2139
            newSwitchOpts['netflow'] = switchBox.result['netflow']
2140
            self.switchOpts[name] = newSwitchOpts
2141
            print 'New switch details for ' + name + ' = ' + str(newSwitchOpts)
2142

    
2143
    def linkUp( self ):
2144
        if ( self.selection is None or
2145
             self.net is None):
2146
            return
2147
        link = self.selection
2148
        linkDetail =  self.links[link]
2149
        src = linkDetail['src']
2150
        dst = linkDetail['dest']
2151
        srcName, dstName = src[ 'text' ], dst[ 'text' ]
2152
        self.net.configLinkStatus(srcName, dstName, 'up')
2153
        self.canvas.itemconfig(link, dash=())
2154

    
2155
    def linkDown( self ):
2156
        if ( self.selection is None or
2157
             self.net is None):
2158
            return
2159
        link = self.selection
2160
        linkDetail =  self.links[link]
2161
        src = linkDetail['src']
2162
        dst = linkDetail['dest']
2163
        srcName, dstName = src[ 'text' ], dst[ 'text' ]
2164
        self.net.configLinkStatus(srcName, dstName, 'down')
2165
        self.canvas.itemconfig(link, dash=(4, 4))
2166

    
2167
    def linkDetails( self, _ignore=None ):
2168
        if ( self.selection is None or
2169
             self.net is not None):
2170
            return
2171
        link = self.selection
2172

    
2173
        linkDetail =  self.links[link]
2174
        src = linkDetail['src']
2175
        dest = linkDetail['dest']
2176
        linkopts = linkDetail['linkOpts']
2177
        linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
2178
        if linkBox.result:
2179
            linkDetail['linkOpts'] = linkBox.result
2180
            print 'New link details = ' + str(linkBox.result)
2181

    
2182
    def prefDetails( self ):
2183
        prefDefaults = self.appPrefs
2184
        prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults)
2185
        print 'New Prefs = ' + str(prefBox.result)
2186
        if prefBox.result:
2187
            self.appPrefs = prefBox.result
2188

    
2189

    
2190
    def controllerDetails( self ):
2191
        if ( self.selection is None or
2192
             self.net is not None or
2193
             self.selection not in self.itemToWidget ):
2194
            return
2195
        widget = self.itemToWidget[ self.selection ]
2196
        name = widget[ 'text' ]
2197
        tags = self.canvas.gettags( self.selection )
2198
        oldName = name
2199
        if 'Controller' not in tags:
2200
            return
2201

    
2202
        ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name])
2203
        if ctrlrBox.result:
2204
            #print 'Controller is ' + ctrlrBox.result[0]
2205
            if len(ctrlrBox.result['hostname']) > 0:
2206
                name = ctrlrBox.result['hostname']
2207
                widget[ 'text' ] = name
2208
            else:
2209
                ctrlrBox.result['hostname'] = name
2210
            self.controllers[name] = ctrlrBox.result
2211
            print 'New controller details for ' + name + ' = ' + str(self.controllers[name])
2212
            # Find references to controller and change name
2213
            if oldName != name:
2214
                for widget in self.widgetToItem:
2215
                    switchName = widget[ 'text' ]
2216
                    tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2217
                    if 'Switch' in tags:
2218
                        switch = self.switchOpts[switchName]
2219
                        if oldName in switch['controllers']:
2220
                            switch['controllers'].remove(oldName)
2221
                            switch['controllers'].append(name)
2222

    
2223

    
2224
    def listBridge( self, _ignore=None ):
2225
        if ( self.selection is None or
2226
             self.net is None or
2227
             self.selection not in self.itemToWidget ):
2228
            return
2229
        name = self.itemToWidget[ self.selection ][ 'text' ]
2230
        tags = self.canvas.gettags( self.selection )
2231

    
2232
        if name not in self.net.nameToNode:
2233
            return
2234
        if 'Switch' in tags:
2235
           call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True)
2236

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

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

    
2243
    # Model interface
2244
    #
2245
    # Ultimately we will either want to use a topo or
2246
    # mininet object here, probably.
2247

    
2248
    def addLink( self, source, dest, linktype='data', linkopts={} ):
2249
        "Add link to model."
2250
        source.links[ dest ] = self.link
2251
        dest.links[ source ] = self.link
2252
        self.links[ self.link ] = {'type' :linktype,
2253
                                   'src':source,
2254
                                   'dest':dest,
2255
                                   'linkOpts':linkopts}
2256

    
2257
    def deleteLink( self, link ):
2258
        "Delete link from model."
2259
        pair = self.links.get( link, None )
2260
        if pair is not None:
2261
            source=pair['src']
2262
            dest=pair['dest']
2263
            del source.links[ dest ]
2264
            del dest.links[ source ]
2265
            stags = self.canvas.gettags( self.widgetToItem[ source ] )
2266
            dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
2267
            ltags = self.canvas.gettags( link )
2268

    
2269
            if 'control' in ltags:
2270
                controllerName = ''
2271
                switchName = ''
2272
                if 'Controller' in stags:
2273
                    controllerName = source[ 'text' ]
2274
                    switchName = dest[ 'text' ]
2275
                else:
2276
                    controllerName = dest[ 'text' ]
2277
                    switchName = source[ 'text' ]
2278
    
2279
                if controllerName in self.switchOpts[switchName]['controllers']:
2280
                    self.switchOpts[switchName]['controllers'].remove(controllerName)
2281

    
2282

    
2283
        if link is not None:
2284
            del self.links[ link ]
2285

    
2286
    def deleteNode( self, item ):
2287
        "Delete node (and its links) from model."
2288

    
2289
        widget = self.itemToWidget[ item ]
2290
        tags = self.canvas.gettags(item)
2291
        if 'Controller' in tags:
2292
            # remove from switch controller lists
2293
            for serachwidget in self.widgetToItem:
2294
                name = serachwidget[ 'text' ]
2295
                tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] )
2296
                if 'Switch' in tags:
2297
                    if widget['text'] in self.switchOpts[name]['controllers']:
2298
                        self.switchOpts[name]['controllers'].remove(widget['text'])
2299
            
2300
        for link in widget.links.values():
2301
            # Delete from view and model
2302
            self.deleteItem( link )
2303
        del self.itemToWidget[ item ]
2304
        del self.widgetToItem[ widget ]
2305

    
2306
    def buildNodes( self, net):
2307
        # Make nodes
2308
        print "Getting Hosts and Switches."
2309
        for widget in self.widgetToItem:
2310
            name = widget[ 'text' ]
2311
            tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2312

    
2313
            if 'Switch' in tags:
2314
                opts = self.switchOpts[name]
2315

    
2316
                # Create the correct switch class
2317
                switchClass = customOvs
2318
                switchParms={}
2319
                if 'dpctl' in opts:
2320
                    switchParms['listenPort']=int(opts['dpctl'])
2321
                if 'dpid' in opts:
2322
                    switchParms['dpid']=opts['dpid']
2323
                if opts['switchType'] == 'default':
2324
                    if self.appPrefs['switchType'] == 'ivs':
2325
                        switchClass = IVSSwitch
2326
                    else:
2327
                        switchClass = customOvs
2328
                elif opts['switchType'] == 'ivs':
2329
                    switchClass = IVSSwitch
2330
                else:
2331
                    switchClass = customOvs
2332
                newSwitch = net.addSwitch( name , cls=switchClass, **switchParms)
2333
                #if self.appPrefs['switchType'] == 'ovs':
2334
                if switchClass == customOvs:
2335
                    newSwitch.setOpenFlowVersion(self.appPrefs['openFlowVersions'])
2336
                    if ('switchIP' in opts):
2337
                        if (len(opts['switchIP']) > 0):
2338
                            newSwitch.setSwitchIP(opts['switchIP'])
2339

    
2340
                # Attach external interfaces
2341
                if ('externalInterfaces' in opts):
2342
                    for extInterface in opts['externalInterfaces']:
2343
                        if self.checkIntf(extInterface):
2344
                           Intf( extInterface, node=newSwitch )
2345

    
2346
            elif 'Host' in tags:
2347
                opts = self.hostOpts[name]
2348
                ip = None
2349
                defaultRoute = None
2350
                if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
2351
                    defaultRoute = 'via '+opts['defaultRoute']
2352
                if 'ip' in opts and len(opts['ip']) > 0:
2353
                    ip = opts['ip']
2354
                else:
2355
                    nodeNum = self.hostOpts[name]['nodeNum']
2356
                    ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
2357
                    ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
2358

    
2359
                # Create the correct host class
2360
                newHost = net.addHost( name,
2361
                                       cls=CPULimitedHost,
2362
                                       ip=ip,
2363
                                       defaultRoute=defaultRoute
2364
                                      )
2365

    
2366
                # Set the CPULimitedHost specific options
2367
                if 'cores' in opts:
2368
                    newHost.setCPUs(cores = opts['cores'])
2369
                if 'cpu' in opts:
2370
                    newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched'])
2371

    
2372
                # Attach external interfaces
2373
                if ('externalInterfaces' in opts):
2374
                    for extInterface in opts['externalInterfaces']:
2375
                        if self.checkIntf(extInterface):
2376
                           Intf( extInterface, node=newHost )
2377
                if ('vlanInterfaces' in opts):
2378
                    if len(opts['vlanInterfaces']) > 0:
2379
                        print 'Checking that OS is VLAN prepared'
2380
                        self.pathCheck('vconfig', moduleName='vlan package')
2381
                        moduleDeps( add='8021q' )
2382
            elif 'Controller' in tags:
2383
                opts = self.controllers[name]
2384

    
2385
                # Get controller info from panel
2386
                controllerType = opts['controllerType']
2387

    
2388
                # Make controller
2389
                print 'Getting controller selection:'+controllerType
2390
                controllerIP = opts['remoteIP']
2391
                controllerPort = opts['remotePort']
2392
                if controllerType == 'remote':
2393
                    net.addController(name=name,
2394
                                      controller=RemoteController,
2395
                                      ip=controllerIP,
2396
                                      port=controllerPort)
2397
                elif controllerType == 'inband':
2398
                    net.addController(name=name,
2399
                                      controller=InbandController,
2400
                                      ip=controllerIP,
2401
                                      port=controllerPort)
2402
                elif controllerType == 'ovsc':
2403
                    net.addController(name=name,
2404
                                      controller=OVSController,
2405
                                      port=controllerPort)
2406
                else:
2407
                    net.addController(name=name,
2408
                                      controller=Controller,
2409
                                      port=controllerPort)
2410

    
2411
            else:
2412
                raise Exception( "Cannot create mystery node: " + name )
2413

    
2414
    def pathCheck( self, *args, **kwargs ):
2415
        "Make sure each program in *args can be found in $PATH."
2416
        moduleName = kwargs.get( 'moduleName', 'it' )
2417
        for arg in args:
2418
            if not quietRun( 'which ' + arg ):
2419
                showerror(title="Error",
2420
                      message= 'Cannot find required executable %s.\n' % arg +
2421
                       'Please make sure that %s is installed ' % moduleName +
2422
                       'and available in your $PATH.' )
2423

    
2424
    def buildLinks( self, net):
2425
        # Make links
2426
        print "Getting Links."
2427
        for key,link in self.links.iteritems():
2428
            tags = self.canvas.gettags(key)
2429
            if 'data' in tags:
2430
                src=link['src']
2431
                dst=link['dest']
2432
                linkopts=link['linkOpts']
2433
                srcName, dstName = src[ 'text' ], dst[ 'text' ]
2434
                src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
2435
                net.addLink(src, dst, **linkopts)
2436
                self.canvas.itemconfig(key, dash=())
2437

    
2438

    
2439
    def build( self ):
2440
        print "Build network based on our topology."
2441

    
2442
        dpctl = None
2443
        if len(self.appPrefs['dpctl']) > 0:
2444
            dpctl = int(self.appPrefs['dpctl'])
2445
        net = Mininet( topo=None,
2446
                       listenPort=dpctl,
2447
                       build=False,
2448
                       link=TCLink,
2449
                       ipBase=self.appPrefs['ipBase'] )
2450

    
2451
        self.buildNodes(net)
2452
        self.buildLinks(net)
2453

    
2454
        # Build network (we have to do this separately at the moment )
2455
        net.build()
2456

    
2457
        return net
2458

    
2459

    
2460
    def postStartSetup( self ):
2461

    
2462
        # Setup host VLAN subinterfaces
2463
        for widget in self.widgetToItem:
2464
            name = widget[ 'text' ]
2465
            tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2466
            if 'Host' in tags:
2467
                opts = self.hostOpts[name]
2468
                # Attach vlan interfaces
2469
                if ('vlanInterfaces' in opts):
2470
                    for vlanInterface in opts['vlanInterfaces']:
2471
                        print 'adding vlan interface '+vlanInterface[1]
2472
                        newHost = self.net.get(name)
2473
                        newHost.cmdPrint('vconfig add '+name+'-eth0 '+vlanInterface[1])
2474
                        newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0])
2475

    
2476
        # Configure NetFlow
2477
        nflowValues = self.appPrefs['netflow']
2478
        if len(nflowValues['nflowTarget']) > 0:
2479
            nflowEnabled = False
2480
            nflowSwitches = ''
2481
            for widget in self.widgetToItem:
2482
                name = widget[ 'text' ]
2483
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2484
    
2485
                if 'Switch' in tags:
2486
                    opts = self.switchOpts[name]
2487
                    if 'netflow' in opts:
2488
                        if opts['netflow'] == '1':
2489
                            print name+' has Netflow enabled'
2490
                            nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF'
2491
                            nflowEnabled=True
2492
            if nflowEnabled:
2493
                nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout']
2494
                if nflowValues['nflowAddId'] == '1':
2495
                    nflowCmd = nflowCmd + ' add_id_to_interface=true'
2496
                else:
2497
                    nflowCmd = nflowCmd + ' add_id_to_interface=false'
2498
                print 'cmd = '+nflowCmd+nflowSwitches
2499
                call(nflowCmd+nflowSwitches, shell=True)
2500

    
2501
            else:
2502
                print 'No switches with Netflow'
2503
        else:
2504
            print 'No NetFlow targets specified.'
2505

    
2506
        # Configure sFlow
2507
        sflowValues = self.appPrefs['sflow']
2508
        if len(sflowValues['sflowTarget']) > 0:
2509
            sflowEnabled = False
2510
            sflowSwitches = ''
2511
            for widget in self.widgetToItem:
2512
                name = widget[ 'text' ]
2513
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2514
    
2515
                if 'Switch' in tags:
2516
                    opts = self.switchOpts[name]
2517
                    if 'sflow' in opts:
2518
                        if opts['sflow'] == '1':
2519
                            print name+' has sflow enabled'
2520
                            sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
2521
                            sflowEnabled=True
2522
            if sflowEnabled:
2523
                sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling']
2524
                print 'cmd = '+sflowCmd+sflowSwitches
2525
                call(sflowCmd+sflowSwitches, shell=True)
2526

    
2527
            else:
2528
                print 'No switches with sflow'
2529
        else:
2530
            print 'No sFlow targets specified.'
2531

    
2532
        ## NOTE: MAKE SURE THIS IS LAST THING CALLED
2533
        # Start the CLI if enabled
2534
        if self.appPrefs['startCLI'] == '1':
2535
            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")
2536
            CLI(self.net)
2537

    
2538
    def start( self ):
2539
        "Start network."
2540
        if self.net is None:
2541
            self.net = self.build()
2542

    
2543
            # Since I am going to inject per switch controllers.
2544
            # I can't call net.start().  I have to replicate what it
2545
            # does and add the controller options.
2546
            #self.net.start()
2547
            info( '**** Starting %s controllers\n' % len( self.net.controllers ) )
2548
            for controller in self.net.controllers:
2549
                info( str(controller) + ' ')
2550
                controller.start()
2551
            info('\n')
2552
            info( '**** Starting %s switches\n' % len( self.net.switches ) )
2553
            #for switch in self.net.switches:
2554
            #    info( switch.name + ' ')
2555
            #    switch.start( self.net.controllers )
2556
            for widget in self.widgetToItem:
2557
                name = widget[ 'text' ]
2558
                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
2559
                if 'Switch' in tags:
2560
                    opts = self.switchOpts[name]
2561
                    switchControllers = []
2562
                    for ctrl in opts['controllers']:
2563
                        switchControllers.append(self.net.get(ctrl))
2564
                    info( name + ' ')
2565
                    # Figure out what controllers will manage this switch
2566
                    self.net.get(name).start( switchControllers )
2567
            info('\n')
2568

    
2569
            self.postStartSetup()
2570

    
2571
    def stop( self ):
2572
        "Stop network."
2573
        if self.net is not None:
2574
            self.net.stop()
2575
        cleanUpScreens()
2576
        self.net = None
2577

    
2578
    def do_linkPopup(self, event):
2579
        # display the popup menu
2580
        if ( self.net is None ):
2581
            try:
2582
                self.linkPopup.tk_popup(event.x_root, event.y_root, 0)
2583
            finally:
2584
                # make sure to release the grab (Tk 8.0a1 only)
2585
                self.linkPopup.grab_release()
2586
        else:
2587
            try:
2588
                self.linkRunPopup.tk_popup(event.x_root, event.y_root, 0)
2589
            finally:
2590
                # make sure to release the grab (Tk 8.0a1 only)
2591
                self.linkRunPopup.grab_release()
2592

    
2593
    def do_controllerPopup(self, event):
2594
        # display the popup menu
2595
        if ( self.net is None ):
2596
            try:
2597
                self.controllerPopup.tk_popup(event.x_root, event.y_root, 0)
2598
            finally:
2599
                # make sure to release the grab (Tk 8.0a1 only)
2600
                self.controllerPopup.grab_release()
2601

    
2602
    def do_hostPopup(self, event):
2603
        # display the popup menu
2604
        if ( self.net is None ):
2605
            try:
2606
                self.hostPopup.tk_popup(event.x_root, event.y_root, 0)
2607
            finally:
2608
                # make sure to release the grab (Tk 8.0a1 only)
2609
                self.hostPopup.grab_release()
2610
        else:
2611
            try:
2612
                self.hostRunPopup.tk_popup(event.x_root, event.y_root, 0)
2613
            finally:
2614
                # make sure to release the grab (Tk 8.0a1 only)
2615
                self.hostRunPopup.grab_release()
2616

    
2617
    def do_switchPopup(self, event):
2618
        # display the popup menu
2619
        if ( self.net is None ):
2620
            try:
2621
                self.switchPopup.tk_popup(event.x_root, event.y_root, 0)
2622
            finally:
2623
                # make sure to release the grab (Tk 8.0a1 only)
2624
                self.switchPopup.grab_release()
2625
        else:
2626
            try:
2627
                self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
2628
            finally:
2629
                # make sure to release the grab (Tk 8.0a1 only)
2630
                self.switchRunPopup.grab_release()
2631

    
2632
    def xterm( self, _ignore=None ):
2633
        "Make an xterm when a button is pressed."
2634
        if ( self.selection is None or
2635
             self.net is None or
2636
             self.selection not in self.itemToWidget ):
2637
            return
2638
        name = self.itemToWidget[ self.selection ][ 'text' ]
2639
        if name not in self.net.nameToNode:
2640
            return
2641
        term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] )
2642
        if StrictVersion(VERSION) > StrictVersion('2.0'):
2643
            self.net.terms += term
2644
        else:
2645
            self.net.terms.append(term)
2646

    
2647
    def iperf( self, _ignore=None ):
2648
        "Make an xterm when a button is pressed."
2649
        if ( self.selection is None or
2650
             self.net is None or
2651
             self.selection not in self.itemToWidget ):
2652
            return
2653
        name = self.itemToWidget[ self.selection ][ 'text' ]
2654
        if name not in self.net.nameToNode:
2655
            return
2656
        self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' )
2657

    
2658
    """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """
2659

    
2660
    def parseArgs( self ):
2661
        """Parse command-line args and return options object.
2662
           returns: opts parse options dict"""
2663

    
2664
        if '--custom' in sys.argv:
2665
            index = sys.argv.index( '--custom' )
2666
            if len( sys.argv ) > index + 1:
2667
                filename = sys.argv[ index + 1 ]
2668
                self.parseCustomFile( filename )
2669
            else:
2670
                raise Exception( 'Custom file name not found' )
2671

    
2672
        desc = ( "The %prog utility creates Mininet network from the\n"
2673
                 "command line. It can create parametrized topologies,\n"
2674
                 "invoke the Mininet CLI, and run tests." )
2675

    
2676
        usage = ( '%prog [options]\n'
2677
                  '(type %prog -h for details)' )
2678

    
2679
        opts = OptionParser( description=desc, usage=usage )
2680

    
2681
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
2682
        opts.add_option( '--custom', type='string', default=None,
2683
                         help='read custom topo and node params from .py' +
2684
                         'file' )
2685

    
2686
        self.options, self.args = opts.parse_args()
2687
        # We don't accept extra arguments after the options
2688
        if self.args:
2689
            opts.print_help()
2690
            exit()
2691

    
2692
    def setCustom( self, name, value ):
2693
        "Set custom parameters for MininetRunner."
2694
        if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
2695
            # Update dictionaries
2696
            param = name.upper()
2697
            globals()[ param ].update( value )
2698
        elif name == 'validate':
2699
            # Add custom validate function
2700
            self.validate = value
2701
        else:
2702
            # Add or modify global variable or class
2703
            globals()[ name ] = value
2704

    
2705
    def parseCustomFile( self, fileName ):
2706
        "Parse custom file and add params before parsing cmd-line options."
2707
        customs = {}
2708
        if os.path.isfile( fileName ):
2709
            execfile( fileName, customs, customs )
2710
            for name, val in customs.iteritems():
2711
                self.setCustom( name, val )
2712
        else:
2713
            raise Exception( 'could not find custom file: %s' % fileName )
2714

    
2715
    def importTopo( self ):
2716
        print 'topo='+self.options.topo
2717
        if self.options.topo == 'none':
2718
            return
2719
        self.newTopology()
2720
        topo = buildTopo( TOPOS, self.options.topo )
2721
        importNet = Mininet(topo=topo, build=False)
2722
        importNet.build()
2723

    
2724
        c = self.canvas
2725
        rowIncrement = 100
2726
        currentY = 100
2727

    
2728
        # Add Controllers
2729
        print 'controllers:'+str(len(importNet.controllers))
2730
        for controller in importNet.controllers:
2731
            name = controller.name
2732
            x = self.controllerCount*100+100
2733
            self.addNode('Controller', self.controllerCount,
2734
                 float(x), float(currentY), name=name)
2735
            icon = self.findWidgetByName(name)
2736
            icon.bind('<Button-3>', self.do_controllerPopup )
2737
            ctrlr = { 'controllerType': 'ref',
2738
                      'hostname': name,
2739
                      'remoteIP': controller.ip,
2740
                      'remotePort': controller.port}
2741
            self.controllers[name] = ctrlr
2742

    
2743

    
2744

    
2745
        currentY = currentY + rowIncrement
2746

    
2747
        # Add switches
2748
        print 'switches:'+str(len(importNet.switches))
2749
        columnCount = 0
2750
        for switch in importNet.switches:
2751
            name = switch.name
2752
            self.switchOpts[name] = {}
2753
            self.switchOpts[name]['nodeNum']=self.switchCount
2754
            self.switchOpts[name]['hostname']=name
2755
            self.switchOpts[name]['switchType']='default'
2756
            self.switchOpts[name]['controllers']=[]
2757

    
2758
            x = columnCount*100+100
2759
            self.addNode('Switch', self.switchCount,
2760
                 float(x), float(currentY), name=name)
2761
            icon = self.findWidgetByName(name)
2762
            icon.bind('<Button-3>', self.do_switchPopup )
2763
            # Now link to controllers
2764
            for controller in importNet.controllers:
2765
                self.switchOpts[name]['controllers'].append(controller.name)
2766
                dest = self.findWidgetByName(controller.name)
2767
                dx, dy = c.coords( self.widgetToItem[ dest ] )
2768
                self.link = c.create_line(float(x),
2769
                                          float(currentY),
2770
                                          dx,
2771
                                          dy,
2772
                                          width=4,
2773
                                          fill='red',
2774
                                          dash=(6, 4, 2, 4),
2775
                                          tag='link' )
2776
                c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
2777
                self.addLink( icon, dest, linktype='control' )
2778
                self.createControlLinkBindings()
2779
                self.link = self.linkWidget = None
2780
            if columnCount == 9:
2781
                columnCount = 0
2782
                currentY = currentY + rowIncrement
2783
            else:
2784
                columnCount =columnCount+1
2785

    
2786

    
2787
        currentY = currentY + rowIncrement
2788
        # Add hosts
2789
        print 'hosts:'+str(len(importNet.hosts))
2790
        columnCount = 0
2791
        for host in importNet.hosts:
2792
            name = host.name
2793
            self.hostOpts[name] = {'sched':'host'}
2794
            self.hostOpts[name]['nodeNum']=self.hostCount
2795
            self.hostOpts[name]['hostname']=name
2796
            self.hostOpts[name]['ip']=host.IP()
2797

    
2798
            x = columnCount*100+100
2799
            self.addNode('Host', self.hostCount,
2800
                 float(x), float(currentY), name=name)
2801
            icon = self.findWidgetByName(name)
2802
            icon.bind('<Button-3>', self.do_hostPopup )
2803
            if columnCount == 9:
2804
                columnCount = 0
2805
                currentY = currentY + rowIncrement
2806
            else:
2807
                columnCount =columnCount+1
2808

    
2809
        print 'links:'+str(len(topo.links()))
2810
        #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')]
2811
        for link in topo.links():
2812
            srcNode = link[0]
2813
            src = self.findWidgetByName(srcNode)
2814
            sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
2815

    
2816
            destNode = link[1]
2817
            dest = self.findWidgetByName(destNode)
2818
            dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
2819

    
2820
            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
2821
                                             fill='blue', tag='link' )
2822
            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
2823
            self.addLink( src, dest )
2824
            self.createDataLinkBindings()
2825
            self.link = self.linkWidget = None
2826

    
2827
        importNet.stop()
2828

    
2829
def miniEditImages():
2830
    "Create and return images for MiniEdit."
2831

    
2832
    # Image data. Git will be unhappy. However, the alternative
2833
    # is to keep track of separate binary files, which is also
2834
    # unappealing.
2835

    
2836
    return {
2837
        'Select': BitmapImage(
2838
            file='/usr/include/X11/bitmaps/left_ptr' ),
2839

    
2840
        'Controller': PhotoImage( data=r"""
2841
            R0lGODlhMAAwAPcAAAEBAWfNAYWFhcfHx+3t6/f390lJUaWlpfPz8/Hx72lpaZGRke/v77m5uc0B
2842
            AeHh4e/v7WNjY3t7e5eXlyMjI4mJidPT0+3t7f///09PT7Ozs/X19fHx8ZWTk8HBwX9/fwAAAAAA
2843
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2844
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2845
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2846
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2847
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2848
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2849
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2850
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2851
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2852
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2853
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2854
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAwADAA
2855
            Bwj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAEIeMCxo8ePHwVkBGABg8mTKFOmtDByAIYN
2856
            MGPCRCCzQIENNzEMGOkBAwIKQIMKpYCgKAIHCDB4GNkAA4OnUJ9++CDhQ1QGFzA0GKkBA4GvYMOK
2857
            BYtBA1cNaNOqXcuWq8q3b81m7Cqzbk2bMMu6/Tl0qFEEAZLKxdj1KlSqVA3rnet1rOOwiwmznUzZ
2858
            LdzLJgdfpIv3pmebN2Pm1GyRbocNp1PLNMDaAM3Im1/alQk4gO28pCt2RdCBt+/eRg8IP1AUdmmf
2859
            f5MrL56bYlcOvaP7Xo6Ag3HdGDho3869u/YE1507t+3AgLz58ujPMwg/sTBUCAzgy49PH0LW5u0x
2860
            XFiwvz////5dcJ9bjxVIAHsSdUXAAgs2yOCDDn6FYEQaFGDgYxNCpEFfHHKIX4IDhCjiiCSS+CGF
2861
            FlCmogYpcnVABTDGKGOMAlRQYwUHnKjhAjX2aOOPN8LImgAL6PiQBhLMqCSNAThQgQRGOqRBBD1W
2862
            aaOVAggnQARRNqRBBxmEKeaYZIrZQZcMKbDiigqM5OabcMYp55x01ilnQAA7
2863
            """),
2864

    
2865
        'Host': PhotoImage( data=r"""
2866
            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
2867
            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
2868
            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
2869
            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
2870
            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
2871
            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
2872
            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
2873
            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
2874
            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
2875
            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
2876
            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
2877
            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
2878
            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
2879
            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
2880
            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
2881
            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
2882
            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
2883
            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
2884
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
2885
            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
2886
            ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw
2887
            BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2
2888
            HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO
2889
            p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/
2890
            C8cSBBAQADs=
2891
        """ ),
2892

    
2893
        'Switch': PhotoImage( data=r"""
2894
            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
2895
            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
2896
            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
2897
            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
2898
            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
2899
            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
2900
            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
2901
            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
2902
            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
2903
            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
2904
            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
2905
            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
2906
            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
2907
            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
2908
            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
2909
            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
2910
            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
2911
            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
2912
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
2913
            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
2914
            ACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGDCBMqXMiwocOH
2915
            ECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5
2916
            /AeTpsyYI1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui1
2917
            6saLWLNq3cq1q9evYB0GBAA7
2918
        """ ),
2919

    
2920
        'Link': PhotoImage( data=r"""
2921
            R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
2922
            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
2923
            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
2924
            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
2925
            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
2926
            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
2927
            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
2928
            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
2929
            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
2930
            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
2931
            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
2932
            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
2933
            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
2934
            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
2935
            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
2936
            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
2937
            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
2938
            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
2939
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
2940
            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
2941
            ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG
2942
            Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy
2943
            lBmxI8mSNknm1Dnx5sCAADs=
2944
        """ )
2945
    }
2946

    
2947
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
2948
    """Convenience function to add choices dicts to OptionParser.
2949
       opts: OptionParser instance
2950
       choicesDict: dictionary of valid choices, must include default
2951
       default: default choice key
2952
       name: long option name
2953
       help: string"""
2954
    if default not in choicesDict:
2955
        raise Exception( 'Invalid  default %s for choices dict: %s' %
2956
                         ( default, name ) )
2957
    if not helpStr:
2958
        helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
2959
                    '[,param=value...]' )
2960
    opts.add_option( '--' + name,
2961
                     type='string',
2962
                     default = default,
2963
                     help = helpStr )
2964

    
2965
if __name__ == '__main__':
2966
    setLogLevel( 'info' )
2967
    app = MiniEdit()
2968
    """ import topology if specified """
2969
    app.parseArgs()
2970
    app.importTopo()
2971

    
2972
    app.mainloop()