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