Revision 9aefda7c

View differences:

examples/miniedit.py
6 6
This is a simple demonstration of how one might build a
7 7
GUI application using Mininet as the network model.
8 8

  
9
Development version - not entirely functional!
10

  
11 9
Bob Lantz, April 2010
10
Gregory Gee, July 2013
11

  
12
Controller icon from http://semlabs.co.uk/
12 13
"""
13 14

  
14
from Tkinter import Frame, Button, Label, Scrollbar, Canvas
15
from Tkinter import Menu, BitmapImage, PhotoImage, Wm, Toplevel
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
16 33

  
17 34
# someday: from ttk import *
18 35

  
19
from mininet.log import setLogLevel
20
from mininet.net import Mininet
21
from mininet.util import ipStr
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
22 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'
23 883

  
24 884
class MiniEdit( Frame ):
25 885

  
26 886
    "A simple network editor for Mininet."
27 887

  
28
    def __init__( self, parent=None, cheight=200, cwidth=500 ):
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

  
29 915

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

  
34 921
        # Style
35 922
        self.font = ( 'Geneva', 9 )
......
48 935
        self.cframe, self.canvas = self.createCanvas()
49 936

  
50 937
        # Toolbar
938
        self.controllers = {}
939

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

  
......
67 957

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

  
......
83 973
        self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
84 974
        self.focus()
85 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

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

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

  
95 1026
        # Close window gracefully
......
108 1039
        mbar = Menu( self.top, font=font )
109 1040
        self.top.configure( menu=mbar )
110 1041

  
111
        # Application menu
112
        appMenu = Menu( mbar, tearoff=False )
113
        mbar.add_cascade( label=self.appName, font=font, menu=appMenu )
114
        appMenu.add_command( label='About MiniEdit', command=self.about,
115
                             font=font)
116
        appMenu.add_separator()
117
        appMenu.add_command( label='Quit', command=self.quit, font=font )
118 1042

  
119
        #fileMenu = Menu( mbar, tearoff=False )
120
        #mbar.add_cascade( label="File", font=font, menu=fileMenu )
121
        #fileMenu.add_command( label="Load...", font=font )
122
        #fileMenu.add_separator()
123
        #fileMenu.add_command( label="Save", font=font )
124
        #fileMenu.add_separator()
125
        #fileMenu.add_command( label="Print", font=font )
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 )
126 1051

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

  
132 1058
        runMenu = Menu( mbar, tearoff=False )
133 1059
        mbar.add_cascade( label="Run", font=font, menu=runMenu )
134 1060
        runMenu.add_command( label="Run", font=font, command=self.doRun )
135 1061
        runMenu.add_command( label="Stop", font=font, command=self.doStop )
136
        runMenu.add_separator()
137
        runMenu.add_command( label='Xterm', font=font, command=self.xterm )
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 )
138 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)
139 1071
    # Canvas
140 1072

  
141 1073
    def createCanvas( self ):
......
241 1173
        for tool in self.tools:
242 1174
            self.buttons[ tool ].config( state='normal' )
243 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 + ","
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff