blob: bb6e0301df6a071157a7b6c51435ac3d638129c0 [file] [log] [blame]
ashu01b62f72015-03-12 15:16:11 -05001#!/usr/bin/python
2
3"""
ashu7b6ba182015-04-17 15:02:37 -05004MiniNDNEdit: a simple network editor for MiniNDN
ashu01b62f72015-03-12 15:16:11 -05005
6Based on miniedit by:
7Bob Lantz, April 2010
8Gregory Gee, July 2013
9
10Carlos Cabral, Jan 2013
11Caio Elias, Nov 2014
12
13"""
14
15MINIEDIT_VERSION = '2.2.0.1'
16
17from optparse import OptionParser
18from Tkinter import *
19from ttk import Notebook
20from tkMessageBox import showinfo, showerror, showwarning
ashu7b6ba182015-04-17 15:02:37 -050021from subprocess import call, Popen
ashu01b62f72015-03-12 15:16:11 -050022import tkFont
23import csv
24import tkFileDialog
25import tkSimpleDialog
26import re
27import json
28from distutils.version import StrictVersion
29import os
30import sys
ashu7b6ba182015-04-17 15:02:37 -050031import threading
ashu01b62f72015-03-12 15:16:11 -050032from functools import partial
33
34import pdb
35
36if 'PYTHONPATH' in os.environ:
37 sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
38
39# someday: from ttk import *
40
41from mininet.log import info, error, debug, output, setLogLevel
42from mininet.net import Mininet, VERSION
43from mininet.util import ipStr, netParse, ipAdd, quietRun
44from mininet.util import buildTopo
ashu2ad32e22015-05-29 13:37:40 -050045from mininet.util import custom
ashu01b62f72015-03-12 15:16:11 -050046from mininet.term import makeTerm, cleanUpScreens
47from mininet.node import Controller, RemoteController, NOX, OVSController
48from mininet.node import CPULimitedHost, Host, Node
49from mininet.node import OVSKernelSwitch, OVSSwitch, UserSwitch
50from mininet.link import TCLink, Intf, Link
51from mininet.cli import CLI
52from mininet.moduledeps import moduleDeps, pathCheck
53from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
54from mininet.topolib import TreeTopo
55
ashu7b6ba182015-04-17 15:02:37 -050056print 'MiniNDNEdit running...' #+VERSION
ashu01b62f72015-03-12 15:16:11 -050057MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION)
58if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
59 from mininet.node import IVSSwitch
60
ashu7b6ba182015-04-17 15:02:37 -050061
62from ndn.gui import NfdFrame, NlsrFrame
63
ashu01b62f72015-03-12 15:16:11 -050064TOPODEF = 'none'
65TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
66 'linear': LinearTopo,
67 'reversed': SingleSwitchReversedTopo,
68 'single': SingleSwitchTopo,
69 'none': None,
70 'tree': TreeTopo }
71LINKDEF = 'default'
72LINKS = { 'default': Link,
73 'tc': TCLink }
74HOSTDEF = 'proc'
75HOSTS = { 'proc': Host,
76 'rt': custom( CPULimitedHost, sched='rt' ),
77 'cfs': custom( CPULimitedHost, sched='cfs' ) }
78
ashu7b6ba182015-04-17 15:02:37 -050079def runMiniNdn(window, template):
80 # Hide window
81 window.withdraw()
82
83 proc = Popen("sudo minindn %s" % template, shell=True)
84 proc.wait()
85
86 # Restore window
87 window.deiconify()
88
ashu01b62f72015-03-12 15:16:11 -050089class LegacyRouter( Node ):
90
91 def __init__( self, name, inNamespace=True, **params ):
92 Node.__init__( self, name, inNamespace, **params )
93
94 def config( self, **_params ):
95 if self.intfs:
96 self.setParam( _params, 'setIP', ip='0.0.0.0' )
97 r = Node.config( self, **_params )
98 self.cmd('sysctl -w net.ipv4.ip_forward=1')
99 return r
100
101class CustomDialog(object):
102
103 # TODO: Fix button placement and Title and window focus lock
104 def __init__(self, master, title):
105 self.top=Toplevel(master)
106
107 self.bodyFrame = Frame(self.top)
108 self.bodyFrame.grid(row=0, column=0, sticky='nswe')
109 self.body(self.bodyFrame)
110
111 #return self.b # initial focus
112 buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey')
113 buttonFrame.grid(row=1 , column=0, sticky='nswe')
114
115 okButton = Button(buttonFrame, width=8, text='OK', relief='groove',
116 bd=4, command=self.okAction)
117 okButton.grid(row=1, column=0, sticky=E)
118
119 canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove',
120 bd=4, command=self.cancelAction)
121 canlceButton.grid(row=1, column=1, sticky=W)
122
123 def body(self, master):
124 self.rootFrame = master
125
126 def apply(self):
127 self.top.destroy()
128
129 def cancelAction(self):
130 self.top.destroy()
131
132 def okAction(self):
133 self.apply()
134 self.top.destroy()
135
136class HostDialog(CustomDialog):
137
138 def __init__(self, master, title, prefDefaults, isRouter):
139
140 self.prefValues = prefDefaults
141 self.result = None
142 self.isRouter = isRouter
143 self.title = title
144
145 CustomDialog.__init__(self, master, title)
146
147 def body(self, master):
148 self.rootFrame = master
149 n = Notebook(self.rootFrame)
150 self.propFrame = Frame(n)
151 self.fibFrame = Frame(n)
ashu7b6ba182015-04-17 15:02:37 -0500152
153 # NDN
154 self.nfdFrame = NfdFrame(n)
155 self.nlsrFrame = NlsrFrame(n)
156
ashu01b62f72015-03-12 15:16:11 -0500157 n.add(self.propFrame, text='Properties')
158 n.add(self.fibFrame, text='FIB Entries')
ashu7b6ba182015-04-17 15:02:37 -0500159
160 n.add(self.nfdFrame, text=self.nfdFrame.frameLabel)
161 n.add(self.nlsrFrame, text=self.nlsrFrame.frameLabel)
162
ashu01b62f72015-03-12 15:16:11 -0500163 n.pack()
164
165 ### TAB 1
166 # Field for Hostname
167 Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E)
168 self.hostnameEntry = Entry(self.propFrame)
169 self.hostnameEntry.grid(row=0, column=1)
170 if 'hostname' in self.prefValues:
171 self.hostnameEntry.insert(0, self.prefValues['hostname'])
172
173 # Field for CPU
174 Label(self.propFrame, text="Amount CPU:").grid(row=2, sticky=E)
175 self.cpuEntry = Entry(self.propFrame)
176 self.cpuEntry.grid(row=2, column=1)
177 Label(self.propFrame, text="%").grid(row=2, column=2, sticky=W)
178 if 'cpu' in self.prefValues:
179 self.cpuEntry.insert(0, str(self.prefValues['cpu']))
180
181 # Field for Memory
182 Label(self.propFrame, text="Amount MEM:").grid(row=3, sticky=E)
183 self.memEntry = Entry(self.propFrame)
184 self.memEntry.grid(row=3, column=1)
185 Label(self.propFrame, text="%").grid(row=3, column=2, sticky=W)
186 if 'mem' in self.prefValues:
187 self.memEntry.insert(0, str(self.prefValues['mem']))
188
189 # Field for Cache
190 Label(self.propFrame, text="Amount CACHE:").grid(row=4, sticky=E)
191 self.cacheEntry = Entry(self.propFrame)
192 self.cacheEntry.grid(row=4, column=1)
193 Label(self.propFrame, text="KBytes").grid(row=4, column=2, sticky=W)
194 if 'cache' in self.prefValues:
195 self.cacheEntry.insert(0, str(self.prefValues['cache']))
196
197 # Start command
198 #print self.isRouter
199 if self.isRouter == 'False':
Ashlesh Gawande557cb842015-07-01 15:39:44 -0500200 Label(self.propFrame, text="Start Command(s):").grid(row=5, sticky=E)
201 self.scrollbar = Scrollbar(self.propFrame, orient="horizontal")
202 self.startEntry = Entry(self.propFrame, xscrollcommand=self.scrollbar.set,)
203 self.startEntry.grid(row=5, column=1)
204 self.scrollbar.grid(row=6, column=1, sticky=N+S+E+W)
205 self.scrollbar.config(command=self.startEntry.xview)
206 Label(self.propFrame, text="[Use bash syntax]").grid(row=5, column=2, sticky=W)
ashu01b62f72015-03-12 15:16:11 -0500207 if 'startCommand' in self.prefValues:
208 self.startEntry.insert(0, str(self.prefValues['startCommand']))
209 else:
210 self.startEntry= Entry(self.propFrame)
211
212 ### TAB 2
213 # FIB Entries
214 self.fibEntries = 0
215 Label(self.fibFrame, text="FIB Entry:").grid(row=0, column=0, sticky=E)
216 self.fibButton = Button( self.fibFrame, text='Add', command=self.addEntry)
217 self.fibButton.grid(row=0, column=1)
218
219 self.fibFrame = VerticalScrolledTable(self.fibFrame, rows=0, columns=2, title='FIB Entries')
220 self.fibFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
221 self.fibTableFrame = self.fibFrame.interior
222 self.fibTableFrame.addRow(value=['Prefix','Next Hop'], readonly=True)
223
224 fibList = []
225 if 'fibEntries' in self.prefValues:
226 fibList = self.prefValues['fibEntries']
227 for fibEntr in fibList:
228 if isinstance( fibEntr, tuple ):
229 self.fibTableFrame.addRow(value=fibEntr)
230 else:
231 self.fibTableFrame.addRow(value=[fibEntr,''])
232
233 def addEntry( self ):
234 self.fibTableFrame.addRow()
235
236 def apply(self):
237 fibEntries = []
238 for row in range(self.fibTableFrame.rows):
239 if (len(self.fibTableFrame.get(row, 0)) > 0 and row > 0):
240 if(len(self.fibTableFrame.get(row, 1)) > 0):
241 fibEntries.append((self.fibTableFrame.get(row, 0), self.fibTableFrame.get(row, 1)))
242 else:
243 fibEntries.append(self.fibTableFrame.get(row, 0))
244
245 results = {'cpu': self.cpuEntry.get(),
246 'cache': self.cacheEntry.get(),
247 'mem': self.memEntry.get(),
248 'hostname':self.hostnameEntry.get(),
249 'startCommand':self.startEntry.get(),
ashu7b6ba182015-04-17 15:02:37 -0500250 'fibEntries':fibEntries,
Ashlesh Gawande3a4afb12015-07-09 09:23:30 -0500251 'nfd': self.nfdFrame.getValues(),
ashu7b6ba182015-04-17 15:02:37 -0500252 'nlsr': self.nlsrFrame.getValues()
253 }
254
ashu01b62f72015-03-12 15:16:11 -0500255 self.result = results
256
257class VerticalScrolledTable(LabelFrame):
258 """A pure Tkinter scrollable frame that actually works!
259
260 * Use the 'interior' attribute to place widgets inside the scrollable frame
261 * Construct and pack/place/grid normally
262 * This frame only allows vertical scrolling
263
264 """
265 def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw):
266 LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw)
267
268 # create a canvas object and a vertical scrollbar for scrolling it
269 vscrollbar = Scrollbar(self, orient=VERTICAL)
270 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
271 canvas = Canvas(self, bd=0, highlightthickness=0,
272 yscrollcommand=vscrollbar.set)
273 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
274 vscrollbar.config(command=canvas.yview)
275
276 # reset the view
277 canvas.xview_moveto(0)
278 canvas.yview_moveto(0)
279
280 # create a frame inside the canvas which will be scrolled with it
281 self.interior = interior = TableFrame(canvas, rows=rows, columns=columns)
282 interior_id = canvas.create_window(0, 0, window=interior,
283 anchor=NW)
284
285 # track changes to the canvas and frame width and sync them,
286 # also updating the scrollbar
287 def _configure_interior(event):
288 # update the scrollbars to match the size of the inner frame
289 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
290 canvas.config(scrollregion="0 0 %s %s" % size)
291 if interior.winfo_reqwidth() != canvas.winfo_width():
292 # update the canvas's width to fit the inner frame
293 canvas.config(width=interior.winfo_reqwidth())
294 interior.bind('<Configure>', _configure_interior)
295
296 def _configure_canvas(event):
297 if interior.winfo_reqwidth() != canvas.winfo_width():
298 # update the inner frame's width to fill the canvas
299 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
300 canvas.bind('<Configure>', _configure_canvas)
301
302 return
303
304class TableFrame(Frame):
305 def __init__(self, parent, rows=2, columns=2):
306
307 Frame.__init__(self, parent, background="black")
308 self._widgets = []
309 self.rows = rows
310 self.columns = columns
311 for row in range(rows):
312 current_row = []
313 for column in range(columns):
314 label = Entry(self, borderwidth=0)
315 label.grid(row=row, column=column, sticky="wens", padx=1, pady=1)
316 current_row.append(label)
317 self._widgets.append(current_row)
318
319 def set(self, row, column, value):
320 widget = self._widgets[row][column]
321 widget.insert(0, value)
322
323 def get(self, row, column):
324 widget = self._widgets[row][column]
325 return widget.get()
326
327 def addRow( self, value=None, readonly=False ):
328 #print "Adding row " + str(self.rows +1)
329 current_row = []
330 for column in range(self.columns):
331 label = Entry(self, borderwidth=0)
332 label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1)
333 if value is not None:
334 label.insert(0, value[column])
335 if (readonly == True):
336 label.configure(state='readonly')
337 current_row.append(label)
338 self._widgets.append(current_row)
339 self.update_idletasks()
340 self.rows += 1
341
342class LinkDialog(tkSimpleDialog.Dialog):
343
344 def __init__(self, parent, title, linkDefaults):
345
346 self.linkValues = linkDefaults
347
348 tkSimpleDialog.Dialog.__init__(self, parent, title)
349
350 def body(self, master):
351
352 self.var = StringVar(master)
353 Label(master, text="Bandwidth:").grid(row=0, sticky=E)
354 self.e1 = Entry(master)
355 self.e1.grid(row=0, column=1)
356 Label(master, text="[1-1000] Mbps").grid(row=0, column=2, sticky=W)
357 if 'bw' in self.linkValues:
358 self.e1.insert(0,str(self.linkValues['bw']))
359
360 Label(master, text="Delay:").grid(row=1, sticky=E)
361 self.e2 = Entry(master)
362 self.e2.grid(row=1, column=1)
363 Label(master, text="[0-1000] ms").grid(row=1, column=2, sticky=W)
364 if 'delay' in self.linkValues:
365 self.e2.insert(0, self.linkValues['delay'])
366
367 Label(master, text="Loss:").grid(row=2, sticky=E)
368 self.e3 = Entry(master)
369 self.e3.grid(row=2, column=1)
370 Label(master, text="%").grid(row=2, column=2, sticky=W)
371 if 'loss' in self.linkValues:
372 self.e3.insert(0, str(self.linkValues['loss']))
373
374 return self.e1 # initial focus
375
376 def apply(self):
377 self.result = {}
378 if (len(self.e1.get()) > 0):
379 self.result['bw'] = int(self.e1.get())
380 if (len(self.e2.get()) > 0):
381 self.result['delay'] = self.e2.get()
382 if (len(self.e3.get()) > 0):
383 self.result['loss'] = int(self.e3.get())
384
385class ToolTip(object):
386
387 def __init__(self, widget):
388 self.widget = widget
389 self.tipwindow = None
390 self.id = None
391 self.x = self.y = 0
392
393 def showtip(self, text):
394 "Display text in tooltip window"
395 self.text = text
396 if self.tipwindow or not self.text:
397 return
398 x, y, cx, cy = self.widget.bbox("insert")
399 x = x + self.widget.winfo_rootx() + 27
400 y = y + cy + self.widget.winfo_rooty() +27
401 self.tipwindow = tw = Toplevel(self.widget)
402 tw.wm_overrideredirect(1)
403 tw.wm_geometry("+%d+%d" % (x, y))
404 try:
405 # For Mac OS
406 tw.tk.call("::tk::unsupported::MacWindowStyle",
407 "style", tw._w,
408 "help", "noActivates")
409 except TclError:
410 pass
411 label = Label(tw, text=self.text, justify=LEFT,
412 background="#ffffe0", relief=SOLID, borderwidth=1,
413 font=("tahoma", "8", "normal"))
414 label.pack(ipadx=1)
415
416 def hidetip(self):
417 tw = self.tipwindow
418 self.tipwindow = None
419 if tw:
420 tw.destroy()
421
422class MiniEdit( Frame ):
423
ashu7b6ba182015-04-17 15:02:37 -0500424 "A simple network editor for MiniNDN."
ashu01b62f72015-03-12 15:16:11 -0500425
ashu7b6ba182015-04-17 15:02:37 -0500426 def __init__( self, parent=None, cheight=600, cwidth=1000, template_file='minindn.conf' ):
ashu01b62f72015-03-12 15:16:11 -0500427
428 self.template_file = template_file
429
430 Frame.__init__( self, parent )
431 self.action = None
ashu7b6ba182015-04-17 15:02:37 -0500432 self.appName = 'MiniNDNEdit'
ashu01b62f72015-03-12 15:16:11 -0500433 self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" )
434
435 # Style
436 self.font = ( 'Geneva', 9 )
437 self.smallFont = ( 'Geneva', 7 )
438 self.bg = 'white'
439
440 # Title
441 self.top = self.winfo_toplevel()
442 self.top.title( self.appName )
443
444 # Menu bar
445 self.createMenubar()
446
447 # Editing canvas
448 self.cheight, self.cwidth = cheight, cwidth
449 self.cframe, self.canvas = self.createCanvas()
450
451 # Toolbar
452 self.controllers = {}
453
454 # Toolbar
455 self.images = miniEditImages()
456 self.buttons = {}
457 self.active = None
ashu7b6ba182015-04-17 15:02:37 -0500458 self.tools = ( 'Select', 'Host', 'NetLink' )
459 #self.customColors = { 'LegacyRouter': 'darkGreen', 'Host': 'blue' }
ashu01b62f72015-03-12 15:16:11 -0500460 self.toolbar = self.createToolbar()
461
462 # Layout
463 self.toolbar.grid( column=0, row=0, sticky='nsew')
464 self.cframe.grid( column=1, row=0 )
465 self.columnconfigure( 1, weight=1 )
466 self.rowconfigure( 0, weight=1 )
467 self.pack( expand=True, fill='both' )
468
469 # About box
470 self.aboutBox = None
471
472 # Initialize node data
473 self.nodeBindings = self.createNodeBindings()
474 self.nodePrefixes = { 'LegacyRouter': 'r', 'Host': 'h'}
475 self.widgetToItem = {}
476 self.itemToWidget = {}
477
478 # Initialize link tool
479 self.link = self.linkWidget = None
480
481 # Selection support
482 self.selection = None
483
484 # Keyboard bindings
485 self.bind( '<Control-q>', lambda event: self.quit() )
486 self.bind( '<KeyPress-Delete>', self.deleteSelection )
487 self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
488 self.focus()
489
490 #Mouse bindings
491 self.bind( '<Button-1>', lambda event: self.clearPopups )
492
493 self.hostPopup = Menu(self.top, tearoff=0)
494 self.hostPopup.add_command(label='Host Options', font=self.font, command=self.hostDetails)
495 #self.hostPopup.add_separator()
496 #self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails )
497
498 self.legacyRouterPopup = Menu(self.top, tearoff=0)
499 self.legacyRouterPopup.add_command(label='Router Options', font=self.font, command=self.hostDetails)
500
501 self.linkPopup = Menu(self.top, tearoff=0)
502 self.linkPopup.add_command(label='Link Options', font=self.font, command=self.linkDetails)
503 #self.linkPopup.add_separator()
504 #self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails )
505
506 # Event handling initalization
507 self.linkx = self.linky = self.linkItem = None
508 self.lastSelection = None
509
510 # Model initialization
511 self.links = {}
512 self.hostOpts = {}
513 self.switchOpts = {}
514 self.routerOpts = {}
515 self.hostCount = 0
516 self.routerCount = 0
517 self.net = None
518
519 # Close window gracefully
520 Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
521
522 def quit( self ):
523 "Stop our network, if any, then quit."
524 #sself.stop()
525 Frame.quit( self )
526
527 def createMenubar( self ): # MODIFICADO - OK
528 "Create our menu bar."
529
530 font = self.font
531
532 mbar = Menu( self.top, font=font )
533 self.top.configure( menu=mbar )
534
535 fileMenu = Menu( mbar, tearoff=False )
536 mbar.add_cascade( label="File", font=font, menu=fileMenu )
537 fileMenu.add_command( label="New", font=font, command=self.newTopology )
538 fileMenu.add_command( label="Open", font=font, command=self.loadTopology )
539 fileMenu.add_command( label="Save", font=font, command=self.saveTopology )
540 fileMenu.add_command( label="Generate", font=font, command=self.doGenerate )
ashu7b6ba182015-04-17 15:02:37 -0500541 fileMenu.add_command( label="Run", font=font, command=self.doRun )
ashu01b62f72015-03-12 15:16:11 -0500542 fileMenu.add_separator()
543 fileMenu.add_command( label='Quit', command=self.quit, font=font )
544
545 editMenu = Menu( mbar, tearoff=False )
546 mbar.add_cascade( label="Edit", font=font, menu=editMenu )
547 editMenu.add_command( label="Cut", font=font,
548 command=lambda: self.deleteSelection( None ) )
549
550 # Application menu
551 appMenu = Menu( mbar, tearoff=False )
552 mbar.add_cascade( label=self.appName, font=font, menu=appMenu )
ashu7b6ba182015-04-17 15:02:37 -0500553 appMenu.add_command( label='About Mini-NDN', command=self.about,
ashu01b62f72015-03-12 15:16:11 -0500554 font=font)
555 #appMenu.add_separator()
556 #appMenu.add_command( label='Quit', command=self.quit, font=font )
557
558 # Canvas - TUDO IGUAL - OK
559
560 def createCanvas( self ):
561 "Create and return our scrolling canvas frame."
562 f = Frame( self )
563
564 canvas = Canvas( f, width=self.cwidth, height=self.cheight,
565 bg=self.bg )
566
567 # Scroll bars
568 xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
569 ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
570 canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
571
572 # Resize box
573 resize = Label( f, bg='white' )
574
575 # Layout
576 canvas.grid( row=0, column=1, sticky='nsew')
577 ybar.grid( row=0, column=2, sticky='ns')
578 xbar.grid( row=1, column=1, sticky='ew' )
579 resize.grid( row=1, column=2, sticky='nsew' )
580
581 # Resize behavior
582 f.rowconfigure( 0, weight=1 )
583 f.columnconfigure( 1, weight=1 )
584 f.grid( row=0, column=0, sticky='nsew' )
585 f.bind( '<Configure>', lambda event: self.updateScrollRegion() )
586
587 # Mouse bindings
588 canvas.bind( '<ButtonPress-1>', self.clickCanvas )
589 canvas.bind( '<B1-Motion>', self.dragCanvas )
590 canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
591
592 return f, canvas
593
594 def updateScrollRegion( self ):
595 "Update canvas scroll region to hold everything."
596 bbox = self.canvas.bbox( 'all' )
597 if bbox is not None:
598 self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ],
599 bbox[ 3 ] ) )
600
601 def canvasx( self, x_root ):
602 "Convert root x coordinate to canvas coordinate."
603 c = self.canvas
604 return c.canvasx( x_root ) - c.winfo_rootx()
605
606 def canvasy( self, y_root ):
607 "Convert root y coordinate to canvas coordinate."
608 c = self.canvas
609 return c.canvasy( y_root ) - c.winfo_rooty()
610
611 # Toolbar
612
613 def activate( self, toolName ): #IGUAL - OK
614 "Activate a tool and press its button."
615 # Adjust button appearance
616 if self.active:
617 self.buttons[ self.active ].configure( relief='raised' )
618 self.buttons[ toolName ].configure( relief='sunken' )
619 # Activate dynamic bindings
620 self.active = toolName
621
622
623 def createToolTip(self, widget, text): #NOVA - CRIA HINTS E TIPS
624 toolTip = ToolTip(widget)
625 def enter(event):
626 toolTip.showtip(text)
627 def leave(event):
628 toolTip.hidetip()
629 widget.bind('<Enter>', enter)
630 widget.bind('<Leave>', leave)
631
632 def createToolbar( self ): #MODIFICADO - OK
633 "Create and return our toolbar frame."
634
635 toolbar = Frame( self )
636
637 # Tools
638 for tool in self.tools:
639 cmd = ( lambda t=tool: self.activate( t ) )
640 b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
641 if tool in self.images:
642 b.config( height=35, image=self.images[ tool ] )
643 self.createToolTip(b, str(tool))
644 # b.config( compound='top' )
645 b.pack( fill='x' )
646 self.buttons[ tool ] = b
647 self.activate( self.tools[ 0 ] )
648
649 # Spacer
650 Label( toolbar, text='' ).pack()
651
ashu7b6ba182015-04-17 15:02:37 -0500652 # abaixo copiado Mini-NDN para criar botao Generate
ashu01b62f72015-03-12 15:16:11 -0500653
654 for cmd, color in [ ( 'Generate', 'darkGreen' ) ]:
655 doCmd = getattr( self, 'do' + cmd )
656 b = Button( toolbar, text=cmd, font=self.smallFont,
657 fg=color, command=doCmd )
658 b.pack( fill='x', side='bottom' )
659
660 return toolbar
661
ashu7b6ba182015-04-17 15:02:37 -0500662 def doGenerate( self ): #COPIA Mini-NDN - GERA TEMPLATE
ashu01b62f72015-03-12 15:16:11 -0500663 "Generate template."
664 self.activate( 'Select' )
665 for tool in self.tools:
666 self.buttons[ tool ].config( state='disabled' )
667
668 self.buildTemplate()
669
670 for tool in self.tools:
671 self.buttons[ tool ].config( state='normal' )
672
673 toplevel = Toplevel()
674 label1 = Label(toplevel, text="Template file generated successfully", height=0, width=30)
675 label1.pack()
676 b=Button(toplevel, text="Ok", width=5, command=toplevel.destroy)
677 b.pack(side='bottom', padx=0,pady=0)
678
ashu7b6ba182015-04-17 15:02:37 -0500679 def doRun( self ):
680 "Use current configuration to generate a template and run the topology"
681
682 # Generate temporary template file
683 old_template_file = self.template_file
684 self.template_file = "/tmp/tmp.conf"
685 self.doGenerate()
686
687 thread = threading.Thread(target=runMiniNdn, args=(self.master, self.template_file))
688 thread.start()
689
690 self.template_file = old_template_file
691
ashu01b62f72015-03-12 15:16:11 -0500692 def parseFibEntries ( self, fibEntries ):
693 "Parse FIB Entries for write"
694 result=''
695
696 for fibEntry in fibEntries:
697 entry = ','.join(map(str, fibEntry))
698 result += entry + ' '
699
700 return result
701
ashu7b6ba182015-04-17 15:02:37 -0500702 def buildTemplate( self ): #COPIA Mini-NDN para criar Template
ashu01b62f72015-03-12 15:16:11 -0500703 "Generate template"
704
705 template = open(self.template_file, 'w')
706
707 # hosts
ashu7b6ba182015-04-17 15:02:37 -0500708 template.write('[nodes]\n')
ashu01b62f72015-03-12 15:16:11 -0500709 for widget in self.widgetToItem:
710 name = widget[ 'text' ]
711 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
ashu7b6ba182015-04-17 15:02:37 -0500712 print self.hostOpts[name]
ashu01b62f72015-03-12 15:16:11 -0500713 if 'Host' in tags:
714 hOpts=self.hostOpts[name]
715 template.write(name + ': ')
716 if 'startCommand' in hOpts:
Ashlesh Gawande557cb842015-07-01 15:39:44 -0500717 cmds = hOpts['startCommand'].replace("\"", "\\\"")
718 template.write('apps="%s" ' % cmds)
ashu01b62f72015-03-12 15:16:11 -0500719 else:
720 template.write('_ ')
721 if 'cache' in hOpts:
722 template.write('cache=' + hOpts['cache'] + ' ')
723 if 'cpu' in hOpts:
724 cpu=float(hOpts['cpu'])/100
725 template.write('cpu=' + repr(cpu) + ' ')
726 if 'mem' in hOpts:
727 mem=float(hOpts['mem'])/100
728 template.write('mem=' + repr(mem) + ' ')
729 if 'fibEntries' in hOpts:
730 customFib = self.parseFibEntries(hOpts['fibEntries'])
731 template.write(customFib)
ashu7b6ba182015-04-17 15:02:37 -0500732 if 'nlsr' in hOpts:
733 values = hOpts['nlsr']
734
735 template.write('hyperbolic-state=' + values['hyperbolic-state'] + ' ')
736 template.write('radius=' + values['radius'] + ' ')
737 template.write('angle=' + values['angle'] + ' ')
738 template.write('network=' + values['network'] + ' ')
739 template.write('router=' + values['router'] + ' ')
740 template.write('site=' + values['site'] + ' ')
Ashlesh Gawandec3ed2b92015-07-01 12:58:08 -0500741 template.write('nlsr-log-level=' + values['log-level'] + ' ')
ashu7b6ba182015-04-17 15:02:37 -0500742 template.write('max-faces-per-prefix=' + values['max-faces-per-prefix'] + ' ')
Ashlesh Gawande3a4afb12015-07-09 09:23:30 -0500743 if 'nfd' in hOpts:
744 values = hOpts['nfd']
745
746 template.write('nfd-log-level=' + values['log-level'] + ' ')
ashu7b6ba182015-04-17 15:02:37 -0500747
ashu01b62f72015-03-12 15:16:11 -0500748 template.write('\n')
749
750 # switches/routers
ashu7b6ba182015-04-17 15:02:37 -0500751 #template.write('[routers]\n')
ashu01b62f72015-03-12 15:16:11 -0500752
753 for router in self.routerOpts.values():
754
755 hasOpt='False'
756 routerName=router['hostname']
757 #nodetype=router['nodetype']
758 #nodenum=router['nodenum']
759
760 rOpts=self.routerOpts[routerName]
761
762 template.write(routerName + ': ')
763
764 if 'cpu' in rOpts:
765 cpu=float(rOpts['cpu'])/100
766 template.write('cpu=' + repr(cpu) + ' ')
767 hasOpt='True'
768 if 'mem' in rOpts:
769 mem=float(rOpts['mem'])/100
770 template.write('mem=' + repr(mem) + ' ')
771 hasOpt='True'
772 if 'cache' in rOpts:
773 template.write('cache=' + rOpts['cache'] + ' ')
774 hasOpt='True'
775 if 'fibEntries' in rOpts:
776 customFib = self.parseFibEntries(rOpts['fibEntries'])
777 template.write(customFib)
778 hasOpt='True'
779 if hasOpt == 'False':
780 template.write('_')
781
782 template.write('\n')
783
784 # Make links
785 template.write('[links]\n')
786 for link in self.links.values():
787 dst=link['dest']
788 src=link['src']
789 linkopts=link['linkOpts']
790 linktype=link['type']
791
792 srcName, dstName = src[ 'text' ], dst[ 'text' ]
793 template.write(srcName + ':' + dstName + ' ')
794 if 'bw' in linkopts:
795 template.write('bw=' + str(linkopts['bw']) + ' ' )
796 if 'loss' in linkopts:
797 template.write('loss=' + repr(linkopts['loss']) + ' ' )
798 if 'delay' in linkopts:
ashu7b6ba182015-04-17 15:02:37 -0500799 template.write('delay=' + str(linkopts['delay'])+ 'ms' )
ashu01b62f72015-03-12 15:16:11 -0500800
801 template.write('\n')
802
803 template.close()
804
805 def addNode( self, node, nodeNum, x, y, name=None):
806 "Add a new node to our canvas."
807
808 if 'LegacyRouter' == node:
809 self.routerCount += 1
810 if 'Host' == node:
811 self.hostCount += 1
812 if name is None:
813 name = self.nodePrefixes[ node ] + nodeNum
814 self.addNamedNode(node, name, x, y)
815
816 def addNamedNode( self, node, name, x, y):
817 "Add a new node to our canvas."
818 c = self.canvas
819 icon = self.nodeIcon( node, name )
820 item = self.canvas.create_window( x, y, anchor='c', window=icon,
821 tags=node )
822 self.widgetToItem[ icon ] = item
823 self.itemToWidget[ item ] = icon
824 icon.links = {}
825
826 def convertJsonUnicode(self, input):
827 "Some part of Mininet don't like Unicode"
828 if isinstance(input, dict):
829 return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in input.iteritems()}
830 elif isinstance(input, list):
831 return [self.convertJsonUnicode(element) for element in input]
832 elif isinstance(input, unicode):
833 return input.encode('utf-8')
834 else:
835 return input
836
837 def loadTopology( self ):
838 "Load command."
839 c = self.canvas
840
841 myFormats = [
ashu7b6ba182015-04-17 15:02:37 -0500842 ('MiniNDN Topology','*.mnndn'),
ashu01b62f72015-03-12 15:16:11 -0500843 ('All Files','*'),
844 ]
845 f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
846 if f == None:
847 return
848 self.newTopology()
849 loadedTopology = self.convertJsonUnicode(json.load(f))
850
851 # Load hosts
ashu7b6ba182015-04-17 15:02:37 -0500852 hosts = loadedTopology['nodes']
ashu01b62f72015-03-12 15:16:11 -0500853 for host in hosts:
854 nodeNum = host['number']
855 hostname = 'h'+nodeNum
856 if 'hostname' in host['opts']:
857 hostname = host['opts']['hostname']
858 else:
859 host['opts']['hostname'] = hostname
860 if 'nodeNum' not in host['opts']:
861 host['opts']['nodeNum'] = int(nodeNum)
862 x = host['x']
863 y = host['y']
ashu7b6ba182015-04-17 15:02:37 -0500864
ashu01b62f72015-03-12 15:16:11 -0500865 self.addNode('Host', nodeNum, float(x), float(y), name=hostname)
866
867 # Fix JSON converting tuple to list when saving
868 if 'privateDirectory' in host['opts']:
869 newDirList = []
870 for privateDir in host['opts']['privateDirectory']:
871 if isinstance( privateDir, list ):
872 newDirList.append((privateDir[0],privateDir[1]))
873 else:
874 newDirList.append(privateDir)
875 host['opts']['privateDirectory'] = newDirList
876 self.hostOpts[hostname] = host['opts']
877 icon = self.findWidgetByName(hostname)
878 icon.bind('<Button-3>', self.do_hostPopup )
879
880 # Load routers
881 routers = loadedTopology['routers']
882 for router in routers:
883 nodeNum = router['number']
884 hostname = 'r'+nodeNum
885 #print router
886 if 'nodeType' not in router['opts']:
887 router['opts']['nodeType'] = 'legacyRouter'
888 if 'hostname' in router['opts']:
889 hostname = router['opts']['hostname']
890 else:
891 router['opts']['hostname'] = hostname
892 if 'nodeNum' not in router['opts']:
893 router['opts']['nodeNum'] = int(nodeNum)
894 x = router['x']
895 y = router['y']
896 if router['opts']['nodeType'] == "legacyRouter":
897 self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname)
898 icon = self.findWidgetByName(hostname)
899 icon.bind('<Button-3>', self.do_legacyRouterPopup )
900 self.routerOpts[hostname] = router['opts']
901
902 # Load links
903 links = loadedTopology['links']
904 for link in links:
905 srcNode = link['src']
906 src = self.findWidgetByName(srcNode)
907 sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
908
909 destNode = link['dest']
910 dest = self.findWidgetByName(destNode)
911 dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
912
913 self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
914 fill='blue', tag='link' )
915 c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
916 self.addLink( src, dest, linkopts=link['opts'] )
917 self.createDataLinkBindings()
918 self.link = self.linkWidget = None
919
920 f.close
921
922 def findWidgetByName( self, name ):
923 for widget in self.widgetToItem:
924 if name == widget[ 'text' ]:
925 return widget
926
927 def newTopology( self ):
928 "New command."
929 for widget in self.widgetToItem.keys():
930 self.deleteItem( self.widgetToItem[ widget ] )
931 self.hostCount = 0
932 self.routerCount = 0
933 self.links = {}
934 self.hostOpts = {}
935 self.routerOpts = {}
936
937 def saveTopology( self ):
938 "Save command."
939 myFormats = [
ashu7b6ba182015-04-17 15:02:37 -0500940 ('MiniNDN Topology','*.mnndn'),
ashu01b62f72015-03-12 15:16:11 -0500941 ('All Files','*'),
942 ]
943
944 savingDictionary = {}
945 fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...")
946 if len(fileName ) > 0:
947 # Save Application preferences
948 savingDictionary['version'] = '2'
949
950 # Save routers and Hosts
951 hostsToSave = []
952 routersToSave = []
953
954 for widget in self.widgetToItem:
955 name = widget[ 'text' ]
956 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
957 x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] )
958 if 'LegacyRouter' in tags:
959 nodeNum = self.routerOpts[name]['nodeNum']
960 nodeToSave = {'number':str(nodeNum),
961 'x':str(x1),
962 'y':str(y1),
963 'opts':self.routerOpts[name] }
964 routersToSave.append(nodeToSave)
965 elif 'Host' in tags:
966 nodeNum = self.hostOpts[name]['nodeNum']
967 nodeToSave = {'number':str(nodeNum),
968 'x':str(x1),
969 'y':str(y1),
970 'opts':self.hostOpts[name] }
971 hostsToSave.append(nodeToSave)
972 else:
973 raise Exception( "Cannot create mystery node: " + name )
974 savingDictionary['hosts'] = hostsToSave
975 savingDictionary['routers'] = routersToSave
976
977 # Save Links
978 linksToSave = []
979 for link in self.links.values():
980 src = link['src']
981 dst = link['dest']
982 linkopts = link['linkOpts']
983
984 srcName, dstName = src[ 'text' ], dst[ 'text' ]
985 linkToSave = {'src':srcName,
986 'dest':dstName,
987 'opts':linkopts}
988 if link['type'] == 'data':
989 linksToSave.append(linkToSave)
990 savingDictionary['links'] = linksToSave
991
992 # Save Application preferences
993 #savingDictionary['application'] = self.appPrefs
994
995 try:
996 f = open(fileName, 'wb')
997 f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': ')))
998 except Exception as er:
999 print er
1000 finally:
1001 f.close()
1002
1003 # Generic canvas handler
1004 #
1005 # We could have used bindtags, as in nodeIcon, but
1006 # the dynamic approach used here
1007 # may actually require less code. In any case, it's an
1008 # interesting introspection-based alternative to bindtags.
1009
1010 def canvasHandle( self, eventName, event ):
1011 "Generic canvas event handler"
1012 if self.active is None:
1013 return
1014 toolName = self.active
1015 handler = getattr( self, eventName + toolName, None )
1016 if handler is not None:
1017 handler( event )
1018
1019 def clickCanvas( self, event ):
1020 "Canvas click handler."
1021 self.canvasHandle( 'click', event )
1022
1023 def dragCanvas( self, event ):
1024 "Canvas drag handler."
1025 self.canvasHandle( 'drag', event )
1026
1027 def releaseCanvas( self, event ):
1028 "Canvas mouse up handler."
1029 self.canvasHandle( 'release', event )
1030
1031 # Currently the only items we can select directly are
1032 # links. Nodes are handled by bindings in the node icon.
1033
1034 def findItem( self, x, y ):
1035 "Find items at a location in our canvas."
1036 items = self.canvas.find_overlapping( x, y, x, y )
1037 if len( items ) == 0:
1038 return None
1039 else:
1040 return items[ 0 ]
1041
1042 # Canvas bindings for Select, Host, Router and Link tools
1043
1044 def clickSelect( self, event ):
1045 "Select an item."
1046 self.selectItem( self.findItem( event.x, event.y ) )
1047
1048 def deleteItem( self, item ):
1049 "Delete an item."
1050 # Don't delete while network is running
1051 if self.buttons[ 'Select' ][ 'state' ] == 'disabled':
1052 return
1053 # Delete from model
1054 if item in self.links:
1055 self.deleteLink( item )
1056 if item in self.itemToWidget:
1057 self.deleteNode( item )
1058 # Delete from view
1059 self.canvas.delete( item )
1060
1061 def deleteSelection( self, _event ):
1062 "Delete the selected item."
1063 if self.selection is not None:
1064 self.deleteItem( self.selection )
1065 self.selectItem( None )
1066
1067 def clearPopups(self):
1068 print 'Entrou funcao clear_popups'
ashu7b6ba182015-04-17 15:02:37 -05001069 if isHostPopup == True:
1070 print 'Hostpopup = true'
1071 self.hostPopup.unpost
1072 isHostPopup = False
ashu01b62f72015-03-12 15:16:11 -05001073 #if isRouterPopup == True
1074 #if isLinkPopup == True
1075
1076 def nodeIcon( self, node, name ):
1077 "Create a new node icon."
1078 icon = Button( self.canvas, image=self.images[ node ],
1079 text=name, compound='top' )
1080 # Unfortunately bindtags wants a tuple
1081 bindtags = [ str( self.nodeBindings ) ]
1082 bindtags += list( icon.bindtags() )
1083 icon.bindtags( tuple( bindtags ) )
1084 return icon
1085
1086 def newNode( self, node, event ):
1087 "Add a new node to our canvas."
1088 c = self.canvas
1089 x, y = c.canvasx( event.x ), c.canvasy( event.y )
1090 name = self.nodePrefixes[ node ]
1091
1092 if 'LegacyRouter' == node:
1093 self.routerCount += 1
1094 name = self.nodePrefixes[ node ] + str( self.routerCount )
1095 self.routerOpts[name] = {}
1096 self.routerOpts[name]['nodeNum']=self.routerCount
1097 self.routerOpts[name]['hostname']=name
1098 self.routerOpts[name]['nodeType']='legacyRouter'
1099
1100 if 'Host' == node:
1101 self.hostCount += 1
1102 name = self.nodePrefixes[ node ] + str( self.hostCount )
1103 self.hostOpts[name] = {'sched':'host'}
1104 self.hostOpts[name]['nodeNum']=self.hostCount
1105 self.hostOpts[name]['hostname']=name
1106
1107 icon = self.nodeIcon( node, name )
1108 item = self.canvas.create_window( x, y, anchor='c', window=icon,
1109 tags=node )
1110 self.widgetToItem[ icon ] = item
1111 self.itemToWidget[ item ] = icon
1112 self.selectItem( item )
1113 icon.links = {}
1114 if 'LegacyRouter' == node:
1115 icon.bind('<Button-3>', self.do_legacyRouterPopup )
1116 if 'Host' == node:
1117 icon.bind('<Button-3>', self.do_hostPopup )
1118
1119 def clickHost( self, event ):
1120 "Add a new host to our canvas."
1121 self.newNode( 'Host', event )
1122
1123 def clickLegacyRouter( self, event ):
1124 "Add a new router to our canvas."
1125 self.newNode( 'LegacyRouter', event )
1126
1127 def dragNetLink( self, event ):
1128 "Drag a link's endpoint to another node."
1129 if self.link is None:
1130 return
1131 # Since drag starts in widget, we use root coords
1132 x = self.canvasx( event.x_root )
1133 y = self.canvasy( event.y_root )
1134 c = self.canvas
1135 c.coords( self.link, self.linkx, self.linky, x, y )
1136
1137 def releaseNetLink( self, _event ):
1138 "Give up on the current link."
1139 if self.link is not None:
1140 self.canvas.delete( self.link )
1141 self.linkWidget = self.linkItem = self.link = None
1142
1143 # Generic node handlers
1144
1145 def createNodeBindings( self ):
1146 "Create a set of bindings for nodes."
1147 bindings = {
1148 '<ButtonPress-1>': self.clickNode,
1149 '<B1-Motion>': self.dragNode,
1150 '<ButtonRelease-1>': self.releaseNode,
1151 '<Enter>': self.enterNode,
1152 '<Leave>': self.leaveNode
1153 }
1154 l = Label() # lightweight-ish owner for bindings
1155 for event, binding in bindings.items():
1156 l.bind( event, binding )
1157 return l
1158
1159 def selectItem( self, item ):
1160 "Select an item and remember old selection."
1161 self.lastSelection = self.selection
1162 self.selection = item
1163
1164 def enterNode( self, event ):
1165 "Select node on entry."
1166 self.selectNode( event )
1167
1168 def leaveNode( self, _event ):
1169 "Restore old selection on exit."
1170 self.selectItem( self.lastSelection )
1171
1172 def clickNode( self, event ):
1173 "Node click handler."
1174 if self.active is 'NetLink':
1175 self.startLink( event )
1176 else:
1177 self.selectNode( event )
1178 return 'break'
1179
1180 def dragNode( self, event ):
1181 "Node drag handler."
1182 if self.active is 'NetLink':
1183 self.dragNetLink( event )
1184 else:
1185 self.dragNodeAround( event )
1186
1187 def releaseNode( self, event ):
1188 "Node release handler."
1189 if self.active is 'NetLink':
1190 self.finishLink( event )
1191
1192 # Specific node handlers
1193
1194 def selectNode( self, event ):
1195 "Select the node that was clicked on."
1196 item = self.widgetToItem.get( event.widget, None )
1197 self.selectItem( item )
1198
1199 def dragNodeAround( self, event ):
1200 "Drag a node around on the canvas."
1201 c = self.canvas
1202 # Convert global to local coordinates;
1203 # Necessary since x, y are widget-relative
1204 x = self.canvasx( event.x_root )
1205 y = self.canvasy( event.y_root )
1206 w = event.widget
1207 # Adjust node position
1208 item = self.widgetToItem[ w ]
1209 c.coords( item, x, y )
1210 # Adjust link positions
1211 for dest in w.links:
1212 link = w.links[ dest ]
1213 item = self.widgetToItem[ dest ]
1214 x1, y1 = c.coords( item )
1215 c.coords( link, x, y, x1, y1 )
1216 self.updateScrollRegion()
1217
1218 def createDataLinkBindings( self ):
1219 "Create a set of bindings for nodes."
1220 # Link bindings
1221 # Selection still needs a bit of work overall
1222 # Callbacks ignore event
1223
1224 def select( _event, link=self.link ):
1225 "Select item on mouse entry."
1226 self.selectItem( link )
1227
1228 def highlight( _event, link=self.link ):
1229 "Highlight item on mouse entry."
1230 self.selectItem( link )
1231 self.canvas.itemconfig( link, fill='green' )
1232
1233 def unhighlight( _event, link=self.link ):
1234 "Unhighlight item on mouse exit."
1235 self.canvas.itemconfig( link, fill='blue' )
1236 #self.selectItem( None )
1237
1238 self.canvas.tag_bind( self.link, '<Enter>', highlight )
1239 self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
1240 self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
1241 self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup )
1242
1243 def startLink( self, event ):
1244 "Start a new link."
1245 if event.widget not in self.widgetToItem:
1246 # Didn't click on a node
1247 return
1248
1249 w = event.widget
1250 item = self.widgetToItem[ w ]
1251 x, y = self.canvas.coords( item )
1252 self.link = self.canvas.create_line( x, y, x, y, width=4,
1253 fill='blue', tag='link' )
1254 self.linkx, self.linky = x, y
1255 self.linkWidget = w
1256 self.linkItem = item
1257
1258 def finishLink( self, event ):
1259 "Finish creating a link"
1260 if self.link is None:
1261 return
1262 source = self.linkWidget
1263 c = self.canvas
1264 # Since we dragged from the widget, use root coords
1265 x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root )
1266 target = self.findItem( x, y )
1267 dest = self.itemToWidget.get( target, None )
1268 if ( source is None or dest is None or source == dest
1269 or dest in source.links or source in dest.links ):
1270 self.releaseNetLink( event )
1271 return
1272 # For now, don't allow hosts to be directly linked
1273 stags = self.canvas.gettags( self.widgetToItem[ source ] )
1274 dtags = self.canvas.gettags( target )
ashu7b6ba182015-04-17 15:02:37 -05001275 #if (('Host' in stags and 'Host' in dtags)):
1276 #self.releaseNetLink( event )
1277 #return
ashu01b62f72015-03-12 15:16:11 -05001278
1279 # Set link type
1280 linkType='data'
1281
1282 self.createDataLinkBindings()
1283 c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,))
1284
1285 x, y = c.coords( target )
1286 c.coords( self.link, self.linkx, self.linky, x, y )
1287 self.addLink( source, dest, linktype=linkType )
1288
1289 # We're done
1290 self.link = self.linkWidget = None
1291
1292 # Menu handlers
1293
1294 def about( self ):
1295 "Display about box."
1296 about = self.aboutBox
1297 if about is None:
1298 bg = 'white'
1299 about = Toplevel( bg='white' )
1300 about.title( 'About' )
ashu7b6ba182015-04-17 15:02:37 -05001301 info = self.appName + ': a simple network editor for MiniNDN - based on Miniedit'
ashu01b62f72015-03-12 15:16:11 -05001302 warning = 'Development version - not entirely functional!'
1303 #version = 'MiniEdit '+MINIEDIT_VERSION
ashu7b6ba182015-04-17 15:02:37 -05001304 author = 'Vince Lehman, Jan 2015'
1305 author2 = 'Ashlesh Gawande, Jan 2015'
1306 author3 = 'Carlos Cabral, Jan 2013'
1307 author4 = 'Caio Elias, Nov 2014'
1308 author5 = 'Originally by: Bob Lantz <rlantz@cs>, April 2010'
ashu01b62f72015-03-12 15:16:11 -05001309 enhancements = 'Enhancements by: Gregory Gee, Since July 2013'
1310 www = 'http://gregorygee.wordpress.com/category/miniedit/'
1311 line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg )
1312 line2 = Label( about, text=warning, font='Helvetica 9', bg=bg )
1313 line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
1314 line4 = Label( about, text=author2, font='Helvetica 9', bg=bg )
1315 line5 = Label( about, text=author3, font='Helvetica 9', bg=bg )
ashu7b6ba182015-04-17 15:02:37 -05001316 line6 = Label( about, text=author4, font='Helvetica 9', bg=bg )
1317 line7 = Label( about, text=author5, font='Helvetica 9', bg=bg )
1318 line8 = Label( about, text=enhancements, font='Helvetica 9', bg=bg )
1319 line9 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER )
ashu01b62f72015-03-12 15:16:11 -05001320
ashu7b6ba182015-04-17 15:02:37 -05001321
1322 line9.insert(0, www)
1323 line9.configure(state='readonly')
ashu01b62f72015-03-12 15:16:11 -05001324 line1.pack( padx=20, pady=10 )
1325 line2.pack(pady=10 )
1326 line3.pack(pady=10 )
1327 line4.pack(pady=10 )
1328 line5.pack(pady=10 )
1329 line6.pack(pady=10 )
1330 line7.pack(pady=10 )
ashu7b6ba182015-04-17 15:02:37 -05001331 line8.pack(pady=10 )
1332 line9.pack(pady=10 )
ashu01b62f72015-03-12 15:16:11 -05001333 hide = ( lambda about=about: about.withdraw() )
1334 self.aboutBox = about
1335 # Hide on close rather than destroying window
1336 Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
1337 # Show (existing) window
1338 about.deiconify()
1339
1340 def createToolImages( self ):
1341 "Create toolbar (and icon) images."
1342
1343 def hostDetails( self, _ignore=None ):
1344 if ( self.selection is None or
1345 self.net is not None or
1346 self.selection not in self.itemToWidget ):
1347 return
1348 widget = self.itemToWidget[ self.selection ]
1349 name = widget[ 'text' ]
1350 tags = self.canvas.gettags( self.selection )
1351
1352 #print tags
1353 if 'Host' in tags:
1354
1355 prefDefaults = self.hostOpts[name]
1356 hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults, isRouter='False')
1357 self.master.wait_window(hostBox.top)
1358 if hostBox.result:
1359 newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']}
1360
1361 if len(hostBox.result['startCommand']) > 0:
1362 newHostOpts['startCommand'] = hostBox.result['startCommand']
1363 if hostBox.result['cpu']:
1364 newHostOpts['cpu'] = hostBox.result['cpu']
1365 if hostBox.result['mem']:
1366 newHostOpts['mem'] = hostBox.result['mem']
1367 if len(hostBox.result['hostname']) > 0:
1368 newHostOpts['hostname'] = hostBox.result['hostname']
1369 name = hostBox.result['hostname']
1370 widget[ 'text' ] = name
1371 if len(hostBox.result['cache']) > 0:
1372 newHostOpts['cache'] = hostBox.result['cache']
1373 if len(hostBox.result['fibEntries']) > 0:
1374 newHostOpts['fibEntries'] = hostBox.result['fibEntries']
ashu7b6ba182015-04-17 15:02:37 -05001375
1376 newHostOpts['nlsr'] = hostBox.nlsrFrame.getValues()
Ashlesh Gawande3a4afb12015-07-09 09:23:30 -05001377 newHostOpts['nfd'] = hostBox.nfdFrame.getValues()
ashu7b6ba182015-04-17 15:02:37 -05001378
ashu01b62f72015-03-12 15:16:11 -05001379 self.hostOpts[name] = newHostOpts
1380
1381 print 'New host details for ' + name + ' = ' + str(newHostOpts)
1382
1383 elif 'LegacyRouter' in tags:
1384
1385 prefDefaults = self.routerOpts[name]
1386 hostBox = HostDialog(self, title='Router Details', prefDefaults=prefDefaults, isRouter='True')
1387 self.master.wait_window(hostBox.top)
1388 if hostBox.result:
1389 newRouterOpts = {'nodeNum':self.routerOpts[name]['nodeNum']}
1390
1391 if hostBox.result['cpu']:
1392 newRouterOpts['cpu'] = hostBox.result['cpu']
1393 if hostBox.result['mem']:
1394 newRouterOpts['mem'] = hostBox.result['mem']
1395 if len(hostBox.result['hostname']) > 0:
1396 newRouterOpts['hostname'] = hostBox.result['hostname']
1397 name = hostBox.result['hostname']
1398 widget[ 'text' ] = name
1399 if len(hostBox.result['cache']) > 0:
1400 newRouterOpts['cache'] = hostBox.result['cache']
1401 if len(hostBox.result['fibEntries']) > 0:
1402 newRouterOpts['fibEntries'] = hostBox.result['fibEntries']
1403 self.routerOpts[name] = newRouterOpts
1404
1405 print 'New host details for ' + name + ' = ' + str(newRouterOpts)
1406
1407 def linkDetails( self, _ignore=None ):
1408 if ( self.selection is None or
1409 self.net is not None):
1410 return
1411 link = self.selection
1412
1413 linkDetail = self.links[link]
1414 src = linkDetail['src']
1415 dest = linkDetail['dest']
1416 linkopts = linkDetail['linkOpts']
1417 linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
1418 if linkBox.result is not None:
1419 linkDetail['linkOpts'] = linkBox.result
1420 print 'New link details = ' + str(linkBox.result)
1421
1422 # Model interface
1423 #
1424 # Ultimately we will either want to use a topo or
1425 # mininet object here, probably.
1426
1427 def addLink( self, source, dest, linktype='data', linkopts={} ):
1428 "Add link to model."
1429 source.links[ dest ] = self.link
1430 dest.links[ source ] = self.link
1431 self.links[ self.link ] = {'type' :linktype,
1432 'src':source,
1433 'dest':dest,
1434 'linkOpts':linkopts}
1435
1436 def deleteLink( self, link ):
1437 "Delete link from model."
1438 pair = self.links.get( link, None )
1439 if pair is not None:
1440 source=pair['src']
1441 dest=pair['dest']
1442 del source.links[ dest ]
1443 del dest.links[ source ]
1444 stags = self.canvas.gettags( self.widgetToItem[ source ] )
1445 dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
1446 ltags = self.canvas.gettags( link )
1447
1448 if link is not None:
1449 del self.links[ link ]
1450
1451 def deleteNode( self, item ):
1452 "Delete node (and its links) from model."
1453
1454 widget = self.itemToWidget[ item ]
1455 tags = self.canvas.gettags(item)
1456
1457 for link in widget.links.values():
1458 # Delete from view and model
1459 self.deleteItem( link )
1460 del self.itemToWidget[ item ]
1461 del self.widgetToItem[ widget ]
1462
1463 def do_linkPopup(self, event):
1464 # display the popup menu
1465 if ( self.net is None ):
1466 try:
1467 self.linkPopup.tk_popup(event.x_root, event.y_root)
1468 finally:
1469 # make sure to release the grab (Tk 8.0a1 only)
1470 self.linkPopup.grab_release()
1471
1472 def do_legacyRouterPopup(self, event):
1473 # display the popup menu
1474 if ( self.net is None ):
1475 try:
1476 self.legacyRouterPopup.tk_popup(event.x_root, event.y_root)
1477 finally:
1478 # make sure to release the grab (Tk 8.0a1 only)
1479 self.legacyRouterPopup.grab_release()
1480
1481 def do_hostPopup(self, event):
1482 # display the popup menu
1483 if ( self.net is None ):
1484 try:
1485 self.hostPopup.tk_popup(event.x_root, event.y_root)
1486 isHostPopup = True
1487 finally:
1488 # make sure to release the grab (Tk 8.0a1 only)
1489 self.hostPopup.grab_release()
1490
1491 def xterm( self, _ignore=None ):
1492 "Make an xterm when a button is pressed."
1493 if ( self.selection is None or
1494 self.net is None or
1495 self.selection not in self.itemToWidget ):
1496 return
1497 name = self.itemToWidget[ self.selection ][ 'text' ]
1498 if name not in self.net.nameToNode:
1499 return
1500 term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] )
1501 if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
1502 self.net.terms += term
1503 else:
1504 self.net.terms.append(term)
1505
1506 def iperf( self, _ignore=None ):
1507 "Make an xterm when a button is pressed."
1508 if ( self.selection is None or
1509 self.net is None or
1510 self.selection not in self.itemToWidget ):
1511 return
1512 name = self.itemToWidget[ self.selection ][ 'text' ]
1513 if name not in self.net.nameToNode:
1514 return
1515 self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' )
1516
1517 """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """
1518
1519 def parseArgs( self ):
1520 """Parse command-line args and return options object.
1521 returns: opts parse options dict"""
1522
1523 if '--custom' in sys.argv:
1524 index = sys.argv.index( '--custom' )
1525 if len( sys.argv ) > index + 1:
1526 filename = sys.argv[ index + 1 ]
1527 self.parseCustomFile( filename )
1528 else:
1529 raise Exception( 'Custom file name not found' )
1530
ashu7b6ba182015-04-17 15:02:37 -05001531 desc = ( "The %prog utility creates Minindn network from the\n"
ashu01b62f72015-03-12 15:16:11 -05001532 "command line. It can create parametrized topologies,\n"
ashu7b6ba182015-04-17 15:02:37 -05001533 "invoke the Minindn CLI, and run tests." )
ashu01b62f72015-03-12 15:16:11 -05001534
1535 usage = ( '%prog [options] [template_file]\n'
ashu7b6ba182015-04-17 15:02:37 -05001536 '\nIf no template_file is given, generated template will be written to the file minindn.conf in the current directory.\n'
ashu01b62f72015-03-12 15:16:11 -05001537 'Type %prog -h for details)' )
1538
1539 opts = OptionParser( description=desc, usage=usage )
1540
1541 addDictOption( opts, TOPOS, TOPODEF, 'topo' )
1542 addDictOption( opts, LINKS, LINKDEF, 'link' )
1543
1544 opts.add_option( '--custom', type='string', default=None,
1545 help='read custom topo and node params from .py' +
1546 'file' )
1547
1548 self.options, self.args = opts.parse_args()
1549 # We don't accept extra arguments after the options
1550 if self.args:
1551 if len(self.args) > 1:
1552 opts.print_help()
1553 exit()
1554 else:
1555 self.template_file=self.args[0]
1556
1557 def setCustom( self, name, value ):
1558 "Set custom parameters for MininetRunner."
1559 if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
1560 # Update dictionaries
1561 param = name.upper()
1562 globals()[ param ].update( value )
1563 elif name == 'validate':
1564 # Add custom validate function
1565 self.validate = value
1566 else:
1567 # Add or modify global variable or class
1568 globals()[ name ] = value
1569
1570 def parseCustomFile( self, fileName ):
1571 "Parse custom file and add params before parsing cmd-line options."
1572 customs = {}
1573 if os.path.isfile( fileName ):
1574 execfile( fileName, customs, customs )
1575 for name, val in customs.iteritems():
1576 self.setCustom( name, val )
1577 else:
1578 raise Exception( 'could not find custom file: %s' % fileName )
1579
1580 def importTopo( self ):
1581 print 'topo='+self.options.topo
1582 if self.options.topo == 'none':
1583 return
1584 self.newTopology()
1585 topo = buildTopo( TOPOS, self.options.topo )
1586 link = customConstructor( LINKS, self.options.link )
1587 importNet = Mininet(topo=topo, build=False, link=link)
1588 importNet.build()
1589
1590 c = self.canvas
1591 rowIncrement = 100
1592 currentY = 100
1593
1594 # Add switches
1595 print 'switches:'+str(len(importNet.switches))
1596 columnCount = 0
1597 for switch in importNet.switches:
1598 name = switch.name
1599 self.switchOpts[name] = {}
1600 self.switchOpts[name]['nodeNum']=self.switchCount
1601 self.switchOpts[name]['hostname']=name
1602 self.switchOpts[name]['switchType']='default'
1603 self.switchOpts[name]['controllers']=[]
1604
1605 x = columnCount*100+100
1606 self.addNode('Switch', self.switchCount,
1607 float(x), float(currentY), name=name)
1608 icon = self.findWidgetByName(name)
1609 icon.bind('<Button-3>', self.do_switchPopup )
1610
1611 if columnCount == 9:
1612 columnCount = 0
1613 currentY = currentY + rowIncrement
1614 else:
1615 columnCount =columnCount+1
1616
1617 currentY = currentY + rowIncrement
1618 # Add hosts
1619 print 'hosts:'+str(len(importNet.hosts))
1620 columnCount = 0
1621 for host in importNet.hosts:
1622 name = host.name
1623 self.hostOpts[name] = {'sched':'host'}
1624 self.hostOpts[name]['nodeNum']=self.hostCount
1625 self.hostOpts[name]['hostname']=name
1626 #self.hostOpts[name]['ip']=host.IP()
1627
1628 x = columnCount*100+100
1629 self.addNode('Host', self.hostCount,
1630 float(x), float(currentY), name=name)
1631 icon = self.findWidgetByName(name)
1632 icon.bind('<Button-3>', self.do_hostPopup )
1633 if columnCount == 9:
1634 columnCount = 0
1635 currentY = currentY + rowIncrement
1636 else:
1637 columnCount =columnCount+1
1638
1639 print 'links:'+str(len(topo.links()))
1640 #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')]
1641 for link in topo.links():
1642 print str(link)
1643 srcNode = link[0]
1644 src = self.findWidgetByName(srcNode)
1645 sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
1646
1647 destNode = link[1]
1648 dest = self.findWidgetByName(destNode)
1649 dx, dy = self.canvas.coords( self.widgetToItem[ dest] )
1650
1651 params = topo.linkInfo( srcNode, destNode )
1652 print 'Link Parameters='+str(params)
1653
1654 self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
1655 fill='blue', tag='link' )
1656 c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
1657 self.addLink( src, dest, linkopts=params )
1658 self.createDataLinkBindings()
1659 self.link = self.linkWidget = None
1660
1661 importNet.stop()
1662
1663def miniEditImages():
1664 "Create and return images for MiniEdit."
1665
1666 # Image data. Git will be unhappy. However, the alternative
1667 # is to keep track of separate binary files, which is also
1668 # unappealing.
1669
1670 return {
1671 'Select': BitmapImage(
1672 file='/usr/include/X11/bitmaps/left_ptr' ),
1673
1674 'LegacyRouter': PhotoImage( data=r"""
1675 R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+
1676 3QFq1DmL3wJMmAMzZZW11dnZ2SFrtyNdmTSO6gIZMUKa8gJVqEOH
1677 zR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq6ymF
1678 4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9
1679 vwNgvwJZsX+69gsXJQFHjTtjizF0tvHx8VOm9z2V736Dhz2N3QM2
1680 acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtgtktjfQFu
1681 3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh3
1682 12Gt+VGm/AQIDTmByAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i5
1683 6gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia7DpeggFt2QNP
1684 m97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er
1685 /yVVhwJJktPh70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVV
1686 hQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18zKfP9wwcLAMHCwFF
1687 iS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh
1688 7cve8pG/7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR
1689 1RMjNTF3vU2X4TZupwRSolNne4nB+T+L2YGz4zJ/zYe99YGHjRdD
1690 cT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6Wl
1691 pW2t7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90
1692 uvPz8wIVKBp42SV5zbfT7wtXpStVfwFWrBVvyTt3swFz5kGBv2+1
1693 /QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t
1694 u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T
1695 5yH5BAEAAAAALAAAAAAyABgABwj/AAEIHEiQYJY7Qwg9UsTplRIb
1696 ENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4cHeoI
1697 abJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPA
1698 TqEBoRB9gVJsxRlhPwHI0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o
1699 9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkSrtwADuxC
1700 G/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmD
1701 jhTMmseoKQIFDx7RoxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7
1702 VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq9dGvv09RHFhc
1703 IUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p
1704 9HEUFhxgMSAvjbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJ
1705 ifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CFoVggmEgCyRf01WcF
1706 CYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57Ag
1707 yZckpKKPGFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemM
1708 IQggeaJywSQ/wgHOAmJskQEfWqBlFBEH1P/QaGY3QOpDZXA2+A6m
1709 7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlB
1710 pZdiisd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtCh
1711 RmVPNWgpr+Be+Nc9icARww9TkIEuDAsQ0O7DzGIQzD2QdDEJHTsI
1712 AROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB
1713 xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE
1714 42Q9jtFIp8z0Dy1jQMA1AGziz9VoW7310V0znYDTGMQgwUDXLDBO
1715 2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOMLQkc
1716 jvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZ
1717 MDHKCTwI8EcQFHBBAAFcgGPLHwLwcMIo12Qxu0ABAQA7
1718 """),
1719
1720 'Host': PhotoImage( data=r"""
1721 R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
1722 mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
1723 Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
1724 M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
1725 AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
1726 /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
1727 zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
1728 mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
1729 ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
1730 M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
1731 AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
1732 /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
1733 zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
1734 mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
1735 ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
1736 MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
1737 AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
1738 ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
1739 AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
1740 RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
1741 ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw
1742 BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2
1743 HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO
1744 p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/
1745 C8cSBBAQADs=
1746 """ ),
1747
1748 'NetLink': PhotoImage( data=r"""
1749 R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
1750 mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
1751 Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
1752 M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
1753 AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
1754 /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
1755 zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
1756 mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
1757 ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
1758 M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
1759 AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
1760 /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
1761 zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
1762 mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
1763 ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
1764 MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
1765 AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
1766 ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
1767 AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
1768 RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
1769 ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG
1770 Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy
1771 lBmxI8mSNknm1Dnx5sCAADs=
1772 """ )
1773 }
1774
1775def addDictOption( opts, choicesDict, default, name, helpStr=None ):
1776 """Convenience function to add choices dicts to OptionParser.
1777 opts: OptionParser instance
1778 choicesDict: dictionary of valid choices, must include default
1779 default: default choice key
1780 name: long option name
1781 help: string"""
1782 if default not in choicesDict:
1783 raise Exception( 'Invalid default %s for choices dict: %s' %
1784 ( default, name ) )
1785 if not helpStr:
1786 helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
1787 '[,param=value...]' )
1788 opts.add_option( '--' + name,
1789 type='string',
1790 default = default,
1791 help = helpStr )
1792
1793if __name__ == '__main__':
1794 setLogLevel( 'info' )
1795 app = MiniEdit()
1796 """ import topology if specified """
1797 app.parseArgs()
1798 app.importTopo()
1799
1800 global isHostPopup
1801 global isRouterPopup
1802 global isLinkPopup
1803
1804 app.mainloop()