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