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