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