blob: fa5d071cf61168cebf756eeefcec26f8d7768685 [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
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500517 self.tools = ( 'Select', 'Host', 'Switch', 'NetLink' )
ashu7b6ba182015-04-17 15:02:37 -0500518 #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()
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500533 self.nodePrefixes = { 'LegacyRouter': 'r', 'Host': 'h', 'Switch': 's'}
ashu01b62f72015-03-12 15:16:11 -0500534 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
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500575 self.switchCount = 0
ashu01b62f72015-03-12 15:16:11 -0500576 self.routerCount = 0
577 self.net = None
578
579 # Close window gracefully
580 Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
581
582 def quit( self ):
583 "Stop our network, if any, then quit."
584 #sself.stop()
585 Frame.quit( self )
586
587 def createMenubar( self ): # MODIFICADO - OK
588 "Create our menu bar."
589
590 font = self.font
591
592 mbar = Menu( self.top, font=font )
593 self.top.configure( menu=mbar )
594
595 fileMenu = Menu( mbar, tearoff=False )
596 mbar.add_cascade( label="File", font=font, menu=fileMenu )
597 fileMenu.add_command( label="New", font=font, command=self.newTopology )
598 fileMenu.add_command( label="Open", font=font, command=self.loadTopology )
599 fileMenu.add_command( label="Save", font=font, command=self.saveTopology )
600 fileMenu.add_command( label="Generate", font=font, command=self.doGenerate )
ashu7b6ba182015-04-17 15:02:37 -0500601 fileMenu.add_command( label="Run", font=font, command=self.doRun )
ashu01b62f72015-03-12 15:16:11 -0500602 fileMenu.add_separator()
603 fileMenu.add_command( label='Quit', command=self.quit, font=font )
604
605 editMenu = Menu( mbar, tearoff=False )
606 mbar.add_cascade( label="Edit", font=font, menu=editMenu )
607 editMenu.add_command( label="Cut", font=font,
608 command=lambda: self.deleteSelection( None ) )
609
610 # Application menu
611 appMenu = Menu( mbar, tearoff=False )
612 mbar.add_cascade( label=self.appName, font=font, menu=appMenu )
ashu7b6ba182015-04-17 15:02:37 -0500613 appMenu.add_command( label='About Mini-NDN', command=self.about,
ashu01b62f72015-03-12 15:16:11 -0500614 font=font)
615 #appMenu.add_separator()
616 #appMenu.add_command( label='Quit', command=self.quit, font=font )
617
618 # Canvas - TUDO IGUAL - OK
619
620 def createCanvas( self ):
621 "Create and return our scrolling canvas frame."
622 f = Frame( self )
623
624 canvas = Canvas( f, width=self.cwidth, height=self.cheight,
625 bg=self.bg )
626
627 # Scroll bars
628 xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
629 ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
630 canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
631
632 # Resize box
633 resize = Label( f, bg='white' )
634
635 # Layout
636 canvas.grid( row=0, column=1, sticky='nsew')
637 ybar.grid( row=0, column=2, sticky='ns')
638 xbar.grid( row=1, column=1, sticky='ew' )
639 resize.grid( row=1, column=2, sticky='nsew' )
640
641 # Resize behavior
642 f.rowconfigure( 0, weight=1 )
643 f.columnconfigure( 1, weight=1 )
644 f.grid( row=0, column=0, sticky='nsew' )
645 f.bind( '<Configure>', lambda event: self.updateScrollRegion() )
646
647 # Mouse bindings
648 canvas.bind( '<ButtonPress-1>', self.clickCanvas )
649 canvas.bind( '<B1-Motion>', self.dragCanvas )
650 canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
651
652 return f, canvas
653
654 def updateScrollRegion( self ):
655 "Update canvas scroll region to hold everything."
656 bbox = self.canvas.bbox( 'all' )
657 if bbox is not None:
658 self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ],
659 bbox[ 3 ] ) )
660
661 def canvasx( self, x_root ):
662 "Convert root x coordinate to canvas coordinate."
663 c = self.canvas
664 return c.canvasx( x_root ) - c.winfo_rootx()
665
666 def canvasy( self, y_root ):
667 "Convert root y coordinate to canvas coordinate."
668 c = self.canvas
669 return c.canvasy( y_root ) - c.winfo_rooty()
670
671 # Toolbar
672
673 def activate( self, toolName ): #IGUAL - OK
674 "Activate a tool and press its button."
675 # Adjust button appearance
676 if self.active:
677 self.buttons[ self.active ].configure( relief='raised' )
678 self.buttons[ toolName ].configure( relief='sunken' )
679 # Activate dynamic bindings
680 self.active = toolName
681
682
683 def createToolTip(self, widget, text): #NOVA - CRIA HINTS E TIPS
684 toolTip = ToolTip(widget)
685 def enter(event):
686 toolTip.showtip(text)
687 def leave(event):
688 toolTip.hidetip()
689 widget.bind('<Enter>', enter)
690 widget.bind('<Leave>', leave)
691
692 def createToolbar( self ): #MODIFICADO - OK
693 "Create and return our toolbar frame."
694
695 toolbar = Frame( self )
696
697 # Tools
698 for tool in self.tools:
699 cmd = ( lambda t=tool: self.activate( t ) )
700 b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
701 if tool in self.images:
702 b.config( height=35, image=self.images[ tool ] )
703 self.createToolTip(b, str(tool))
704 # b.config( compound='top' )
705 b.pack( fill='x' )
706 self.buttons[ tool ] = b
707 self.activate( self.tools[ 0 ] )
708
709 # Spacer
710 Label( toolbar, text='' ).pack()
711
ashu7b6ba182015-04-17 15:02:37 -0500712 # abaixo copiado Mini-NDN para criar botao Generate
ashu01b62f72015-03-12 15:16:11 -0500713
714 for cmd, color in [ ( 'Generate', 'darkGreen' ) ]:
715 doCmd = getattr( self, 'do' + cmd )
716 b = Button( toolbar, text=cmd, font=self.smallFont,
717 fg=color, command=doCmd )
718 b.pack( fill='x', side='bottom' )
719
720 return toolbar
721
ashu7b6ba182015-04-17 15:02:37 -0500722 def doGenerate( self ): #COPIA Mini-NDN - GERA TEMPLATE
ashu01b62f72015-03-12 15:16:11 -0500723 "Generate template."
724 self.activate( 'Select' )
725 for tool in self.tools:
726 self.buttons[ tool ].config( state='disabled' )
727
728 self.buildTemplate()
729
730 for tool in self.tools:
731 self.buttons[ tool ].config( state='normal' )
732
733 toplevel = Toplevel()
734 label1 = Label(toplevel, text="Template file generated successfully", height=0, width=30)
735 label1.pack()
736 b=Button(toplevel, text="Ok", width=5, command=toplevel.destroy)
737 b.pack(side='bottom', padx=0,pady=0)
738
ashu7b6ba182015-04-17 15:02:37 -0500739 def doRun( self ):
740 "Use current configuration to generate a template and run the topology"
741
742 # Generate temporary template file
743 old_template_file = self.template_file
744 self.template_file = "/tmp/tmp.conf"
745 self.doGenerate()
746
747 thread = threading.Thread(target=runMiniNdn, args=(self.master, self.template_file))
748 thread.start()
749
750 self.template_file = old_template_file
751
ashu01b62f72015-03-12 15:16:11 -0500752 def parseFibEntries ( self, fibEntries ):
753 "Parse FIB Entries for write"
754 result=''
755
756 for fibEntry in fibEntries:
757 entry = ','.join(map(str, fibEntry))
758 result += entry + ' '
759
760 return result
761
ashu7b6ba182015-04-17 15:02:37 -0500762 def buildTemplate( self ): #COPIA Mini-NDN para criar Template
ashu01b62f72015-03-12 15:16:11 -0500763 "Generate template"
764
765 template = open(self.template_file, 'w')
766
767 # hosts
ashu7b6ba182015-04-17 15:02:37 -0500768 template.write('[nodes]\n')
ashu01b62f72015-03-12 15:16:11 -0500769 for widget in self.widgetToItem:
770 name = widget[ 'text' ]
771 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500772
ashu01b62f72015-03-12 15:16:11 -0500773 if 'Host' in tags:
774 hOpts=self.hostOpts[name]
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500775 print hOpts
776
ashu01b62f72015-03-12 15:16:11 -0500777 template.write(name + ': ')
778 if 'startCommand' in hOpts:
Ashlesh Gawande557cb842015-07-01 15:39:44 -0500779 cmds = hOpts['startCommand'].replace("\"", "\\\"")
780 template.write('apps="%s" ' % cmds)
ashu01b62f72015-03-12 15:16:11 -0500781 else:
782 template.write('_ ')
783 if 'cache' in hOpts:
784 template.write('cache=' + hOpts['cache'] + ' ')
785 if 'cpu' in hOpts:
786 cpu=float(hOpts['cpu'])/100
787 template.write('cpu=' + repr(cpu) + ' ')
788 if 'mem' in hOpts:
789 mem=float(hOpts['mem'])/100
790 template.write('mem=' + repr(mem) + ' ')
791 if 'fibEntries' in hOpts:
792 customFib = self.parseFibEntries(hOpts['fibEntries'])
793 template.write(customFib)
ashu7b6ba182015-04-17 15:02:37 -0500794 if 'nlsr' in hOpts:
795 values = hOpts['nlsr']
796
797 template.write('hyperbolic-state=' + values['hyperbolic-state'] + ' ')
798 template.write('radius=' + values['radius'] + ' ')
799 template.write('angle=' + values['angle'] + ' ')
800 template.write('network=' + values['network'] + ' ')
801 template.write('router=' + values['router'] + ' ')
802 template.write('site=' + values['site'] + ' ')
Ashlesh Gawandec3ed2b92015-07-01 12:58:08 -0500803 template.write('nlsr-log-level=' + values['log-level'] + ' ')
ashu7b6ba182015-04-17 15:02:37 -0500804 template.write('max-faces-per-prefix=' + values['max-faces-per-prefix'] + ' ')
Ashlesh Gawande3a4afb12015-07-09 09:23:30 -0500805 if 'nfd' in hOpts:
806 values = hOpts['nfd']
807
808 template.write('nfd-log-level=' + values['log-level'] + ' ')
ashu7b6ba182015-04-17 15:02:37 -0500809
ashu01b62f72015-03-12 15:16:11 -0500810 template.write('\n')
811
812 # switches/routers
ashu7b6ba182015-04-17 15:02:37 -0500813 #template.write('[routers]\n')
ashu01b62f72015-03-12 15:16:11 -0500814
815 for router in self.routerOpts.values():
816
817 hasOpt='False'
818 routerName=router['hostname']
819 #nodetype=router['nodetype']
820 #nodenum=router['nodenum']
821
822 rOpts=self.routerOpts[routerName]
823
824 template.write(routerName + ': ')
825
826 if 'cpu' in rOpts:
827 cpu=float(rOpts['cpu'])/100
828 template.write('cpu=' + repr(cpu) + ' ')
829 hasOpt='True'
830 if 'mem' in rOpts:
831 mem=float(rOpts['mem'])/100
832 template.write('mem=' + repr(mem) + ' ')
833 hasOpt='True'
834 if 'cache' in rOpts:
835 template.write('cache=' + rOpts['cache'] + ' ')
836 hasOpt='True'
837 if 'fibEntries' in rOpts:
838 customFib = self.parseFibEntries(rOpts['fibEntries'])
839 template.write(customFib)
840 hasOpt='True'
841 if hasOpt == 'False':
842 template.write('_')
843
844 template.write('\n')
845
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500846 # Write switches
847 template.write('[switches]\n')
848
849 for switch in self.switchOpts.values():
850 print "Switch%s" % switch
851
852 name = switch['hostname']
853 template.write(name + ': _\n')
854
ashu01b62f72015-03-12 15:16:11 -0500855 # Make links
856 template.write('[links]\n')
857 for link in self.links.values():
858 dst=link['dest']
859 src=link['src']
860 linkopts=link['linkOpts']
861 linktype=link['type']
862
863 srcName, dstName = src[ 'text' ], dst[ 'text' ]
864 template.write(srcName + ':' + dstName + ' ')
865 if 'bw' in linkopts:
866 template.write('bw=' + str(linkopts['bw']) + ' ' )
867 if 'loss' in linkopts:
868 template.write('loss=' + repr(linkopts['loss']) + ' ' )
869 if 'delay' in linkopts:
ashu7b6ba182015-04-17 15:02:37 -0500870 template.write('delay=' + str(linkopts['delay'])+ 'ms' )
ashu01b62f72015-03-12 15:16:11 -0500871
872 template.write('\n')
873
874 template.close()
875
876 def addNode( self, node, nodeNum, x, y, name=None):
877 "Add a new node to our canvas."
878
879 if 'LegacyRouter' == node:
880 self.routerCount += 1
881 if 'Host' == node:
882 self.hostCount += 1
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500883 if 'Switch' == node:
884 self.switchCount += 1
ashu01b62f72015-03-12 15:16:11 -0500885 if name is None:
886 name = self.nodePrefixes[ node ] + nodeNum
887 self.addNamedNode(node, name, x, y)
888
889 def addNamedNode( self, node, name, x, y):
890 "Add a new node to our canvas."
891 c = self.canvas
892 icon = self.nodeIcon( node, name )
893 item = self.canvas.create_window( x, y, anchor='c', window=icon,
894 tags=node )
895 self.widgetToItem[ icon ] = item
896 self.itemToWidget[ item ] = icon
897 icon.links = {}
898
899 def convertJsonUnicode(self, input):
900 "Some part of Mininet don't like Unicode"
901 if isinstance(input, dict):
902 return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in input.iteritems()}
903 elif isinstance(input, list):
904 return [self.convertJsonUnicode(element) for element in input]
905 elif isinstance(input, unicode):
906 return input.encode('utf-8')
907 else:
908 return input
909
910 def loadTopology( self ):
911 "Load command."
912 c = self.canvas
913
914 myFormats = [
ashu7b6ba182015-04-17 15:02:37 -0500915 ('MiniNDN Topology','*.mnndn'),
ashu01b62f72015-03-12 15:16:11 -0500916 ('All Files','*'),
917 ]
918 f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
919 if f == None:
920 return
921 self.newTopology()
922 loadedTopology = self.convertJsonUnicode(json.load(f))
923
924 # Load hosts
Ashlesh Gawandeab087da2015-07-09 15:10:02 -0500925 hosts = loadedTopology['hosts']
ashu01b62f72015-03-12 15:16:11 -0500926 for host in hosts:
927 nodeNum = host['number']
928 hostname = 'h'+nodeNum
929 if 'hostname' in host['opts']:
930 hostname = host['opts']['hostname']
931 else:
932 host['opts']['hostname'] = hostname
933 if 'nodeNum' not in host['opts']:
934 host['opts']['nodeNum'] = int(nodeNum)
Ashlesh Gawandeab087da2015-07-09 15:10:02 -0500935
ashu01b62f72015-03-12 15:16:11 -0500936 x = host['x']
937 y = host['y']
ashu7b6ba182015-04-17 15:02:37 -0500938
ashu01b62f72015-03-12 15:16:11 -0500939 self.addNode('Host', nodeNum, float(x), float(y), name=hostname)
940
941 # Fix JSON converting tuple to list when saving
942 if 'privateDirectory' in host['opts']:
943 newDirList = []
944 for privateDir in host['opts']['privateDirectory']:
945 if isinstance( privateDir, list ):
946 newDirList.append((privateDir[0],privateDir[1]))
947 else:
948 newDirList.append(privateDir)
949 host['opts']['privateDirectory'] = newDirList
950 self.hostOpts[hostname] = host['opts']
951 icon = self.findWidgetByName(hostname)
952 icon.bind('<Button-3>', self.do_hostPopup )
953
954 # Load routers
955 routers = loadedTopology['routers']
956 for router in routers:
957 nodeNum = router['number']
958 hostname = 'r'+nodeNum
959 #print router
960 if 'nodeType' not in router['opts']:
961 router['opts']['nodeType'] = 'legacyRouter'
962 if 'hostname' in router['opts']:
963 hostname = router['opts']['hostname']
964 else:
965 router['opts']['hostname'] = hostname
966 if 'nodeNum' not in router['opts']:
967 router['opts']['nodeNum'] = int(nodeNum)
968 x = router['x']
969 y = router['y']
970 if router['opts']['nodeType'] == "legacyRouter":
971 self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname)
972 icon = self.findWidgetByName(hostname)
973 icon.bind('<Button-3>', self.do_legacyRouterPopup )
974 self.routerOpts[hostname] = router['opts']
975
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500976 # Load switches
977 switches = loadedTopology['switches']
978
979 for switch in switches:
980 nodeNum = switch['number']
981 hostname = 's' + nodeNum
982
983 if 'hostname' in switch['opts']:
984 hostname = switch['opts']['hostname']
985 else:
986 switch['opts']['hostname'] = hostname
987
988 if 'nodeNum' not in switch['opts']:
989 switch['opts']['nodeNum'] = int(nodeNum)
990
991 x = switch['x']
992 y = switch['y']
993
994 self.addNode('Switch', nodeNum, float(x), float(y), name=hostname)
995 icon = self.findWidgetByName(hostname)
996
997 self.switchOpts[hostname] = switch['opts']
998
ashu01b62f72015-03-12 15:16:11 -0500999 # Load links
1000 links = loadedTopology['links']
1001 for link in links:
1002 srcNode = link['src']
1003 src = self.findWidgetByName(srcNode)
1004 sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
1005
1006 destNode = link['dest']
1007 dest = self.findWidgetByName(destNode)
1008 dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
1009
1010 self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
1011 fill='blue', tag='link' )
1012 c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
1013 self.addLink( src, dest, linkopts=link['opts'] )
1014 self.createDataLinkBindings()
1015 self.link = self.linkWidget = None
1016
1017 f.close
1018
1019 def findWidgetByName( self, name ):
1020 for widget in self.widgetToItem:
1021 if name == widget[ 'text' ]:
1022 return widget
1023
1024 def newTopology( self ):
1025 "New command."
1026 for widget in self.widgetToItem.keys():
1027 self.deleteItem( self.widgetToItem[ widget ] )
1028 self.hostCount = 0
1029 self.routerCount = 0
Vince Lehmanfbd47c92015-10-14 16:00:06 -05001030 self.switchCount = 0
ashu01b62f72015-03-12 15:16:11 -05001031 self.links = {}
1032 self.hostOpts = {}
1033 self.routerOpts = {}
1034
1035 def saveTopology( self ):
1036 "Save command."
1037 myFormats = [
ashu7b6ba182015-04-17 15:02:37 -05001038 ('MiniNDN Topology','*.mnndn'),
ashu01b62f72015-03-12 15:16:11 -05001039 ('All Files','*'),
1040 ]
1041
1042 savingDictionary = {}
1043 fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...")
1044 if len(fileName ) > 0:
1045 # Save Application preferences
1046 savingDictionary['version'] = '2'
1047
1048 # Save routers and Hosts
1049 hostsToSave = []
1050 routersToSave = []
Vince Lehmanfbd47c92015-10-14 16:00:06 -05001051 switchesToSave = []
ashu01b62f72015-03-12 15:16:11 -05001052
1053 for widget in self.widgetToItem:
1054 name = widget[ 'text' ]
1055 tags = self.canvas.gettags( self.widgetToItem[ widget ] )
1056 x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] )
1057 if 'LegacyRouter' in tags:
1058 nodeNum = self.routerOpts[name]['nodeNum']
1059 nodeToSave = {'number':str(nodeNum),
1060 'x':str(x1),
1061 'y':str(y1),
1062 'opts':self.routerOpts[name] }
1063 routersToSave.append(nodeToSave)
1064 elif 'Host' in tags:
1065 nodeNum = self.hostOpts[name]['nodeNum']
1066 nodeToSave = {'number':str(nodeNum),
1067 'x':str(x1),
1068 'y':str(y1),
1069 'opts':self.hostOpts[name] }
1070 hostsToSave.append(nodeToSave)
Vince Lehmanfbd47c92015-10-14 16:00:06 -05001071 elif 'Switch' in tags:
1072 nodeNum = self.switchOpts[name]['nodeNum']
1073 nodeToSave = {'number':str(nodeNum),
1074 'x':str(x1),
1075 'y':str(y1),
1076 'opts':self.switchOpts[name] }
1077 switchesToSave.append(nodeToSave)
ashu01b62f72015-03-12 15:16:11 -05001078 else:
1079 raise Exception( "Cannot create mystery node: " + name )
1080 savingDictionary['hosts'] = hostsToSave
1081 savingDictionary['routers'] = routersToSave
Vince Lehmanfbd47c92015-10-14 16:00:06 -05001082 savingDictionary['switches'] = switchesToSave
ashu01b62f72015-03-12 15:16:11 -05001083
1084 # Save Links
1085 linksToSave = []
1086 for link in self.links.values():
1087 src = link['src']
1088 dst = link['dest']
1089 linkopts = link['linkOpts']
1090
1091 srcName, dstName = src[ 'text' ], dst[ 'text' ]
1092 linkToSave = {'src':srcName,
1093 'dest':dstName,
1094 'opts':linkopts}
1095 if link['type'] == 'data':
1096 linksToSave.append(linkToSave)
1097 savingDictionary['links'] = linksToSave
1098
1099 # Save Application preferences
1100 #savingDictionary['application'] = self.appPrefs
1101
1102 try:
1103 f = open(fileName, 'wb')
1104 f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': ')))
1105 except Exception as er:
1106 print er
1107 finally:
1108 f.close()
1109
1110 # Generic canvas handler
1111 #
1112 # We could have used bindtags, as in nodeIcon, but
1113 # the dynamic approach used here
1114 # may actually require less code. In any case, it's an
1115 # interesting introspection-based alternative to bindtags.
1116
1117 def canvasHandle( self, eventName, event ):
1118 "Generic canvas event handler"
1119 if self.active is None:
1120 return
1121 toolName = self.active
1122 handler = getattr( self, eventName + toolName, None )
1123 if handler is not None:
1124 handler( event )
1125
1126 def clickCanvas( self, event ):
1127 "Canvas click handler."
1128 self.canvasHandle( 'click', event )
1129
1130 def dragCanvas( self, event ):
1131 "Canvas drag handler."
1132 self.canvasHandle( 'drag', event )
1133
1134 def releaseCanvas( self, event ):
1135 "Canvas mouse up handler."
1136 self.canvasHandle( 'release', event )
1137
1138 # Currently the only items we can select directly are
1139 # links. Nodes are handled by bindings in the node icon.
1140
1141 def findItem( self, x, y ):
1142 "Find items at a location in our canvas."
1143 items = self.canvas.find_overlapping( x, y, x, y )
1144 if len( items ) == 0:
1145 return None
1146 else:
1147 return items[ 0 ]
1148
1149 # Canvas bindings for Select, Host, Router and Link tools
1150
1151 def clickSelect( self, event ):
1152 "Select an item."
1153 self.selectItem( self.findItem( event.x, event.y ) )
1154
1155 def deleteItem( self, item ):
1156 "Delete an item."
1157 # Don't delete while network is running
1158 if self.buttons[ 'Select' ][ 'state' ] == 'disabled':
1159 return
1160 # Delete from model
1161 if item in self.links:
1162 self.deleteLink( item )
1163 if item in self.itemToWidget:
1164 self.deleteNode( item )
1165 # Delete from view
1166 self.canvas.delete( item )
1167
1168 def deleteSelection( self, _event ):
1169 "Delete the selected item."
1170 if self.selection is not None:
1171 self.deleteItem( self.selection )
1172 self.selectItem( None )
1173
1174 def clearPopups(self):
1175 print 'Entrou funcao clear_popups'
ashu7b6ba182015-04-17 15:02:37 -05001176 if isHostPopup == True:
1177 print 'Hostpopup = true'
1178 self.hostPopup.unpost
1179 isHostPopup = False
ashu01b62f72015-03-12 15:16:11 -05001180 #if isRouterPopup == True
1181 #if isLinkPopup == True
1182
1183 def nodeIcon( self, node, name ):
1184 "Create a new node icon."
1185 icon = Button( self.canvas, image=self.images[ node ],
1186 text=name, compound='top' )
1187 # Unfortunately bindtags wants a tuple
1188 bindtags = [ str( self.nodeBindings ) ]
1189 bindtags += list( icon.bindtags() )
1190 icon.bindtags( tuple( bindtags ) )
1191 return icon
1192
1193 def newNode( self, node, event ):
1194 "Add a new node to our canvas."
1195 c = self.canvas
1196 x, y = c.canvasx( event.x ), c.canvasy( event.y )
1197 name = self.nodePrefixes[ node ]
1198
1199 if 'LegacyRouter' == node:
1200 self.routerCount += 1
1201 name = self.nodePrefixes[ node ] + str( self.routerCount )
1202 self.routerOpts[name] = {}
1203 self.routerOpts[name]['nodeNum']=self.routerCount
1204 self.routerOpts[name]['hostname']=name
1205 self.routerOpts[name]['nodeType']='legacyRouter'
1206
1207 if 'Host' == node:
1208 self.hostCount += 1
1209 name = self.nodePrefixes[ node ] + str( self.hostCount )
1210 self.hostOpts[name] = {'sched':'host'}
1211 self.hostOpts[name]['nodeNum']=self.hostCount
1212 self.hostOpts[name]['hostname']=name
1213
Vince Lehmanfbd47c92015-10-14 16:00:06 -05001214 if 'Switch' == node:
1215 self.switchCount += 1
1216 name = self.nodePrefixes[ node ] + str( self.switchCount )
1217 self.switchOpts[name] = {}
1218 self.switchOpts[name]['nodeNum']=self.switchCount
1219 self.switchOpts[name]['hostname']=name
1220
ashu01b62f72015-03-12 15:16:11 -05001221 icon = self.nodeIcon( node, name )
1222 item = self.canvas.create_window( x, y, anchor='c', window=icon,
1223 tags=node )
1224 self.widgetToItem[ icon ] = item
1225 self.itemToWidget[ item ] = icon
1226 self.selectItem( item )
1227 icon.links = {}
1228 if 'LegacyRouter' == node:
1229 icon.bind('<Button-3>', self.do_legacyRouterPopup )
1230 if 'Host' == node:
1231 icon.bind('<Button-3>', self.do_hostPopup )
1232
1233 def clickHost( self, event ):
1234 "Add a new host to our canvas."
1235 self.newNode( 'Host', event )
1236
Vince Lehmanfbd47c92015-10-14 16:00:06 -05001237 def clickSwitch( self, event ):
1238 "Add a new switch to the canvas."
1239 self.newNode( 'Switch', event )
1240
ashu01b62f72015-03-12 15:16:11 -05001241 def clickLegacyRouter( self, event ):
1242 "Add a new router to our canvas."
1243 self.newNode( 'LegacyRouter', event )
1244
1245 def dragNetLink( self, event ):
1246 "Drag a link's endpoint to another node."
1247 if self.link is None:
1248 return
1249 # Since drag starts in widget, we use root coords
1250 x = self.canvasx( event.x_root )
1251 y = self.canvasy( event.y_root )
1252 c = self.canvas
1253 c.coords( self.link, self.linkx, self.linky, x, y )
1254
1255 def releaseNetLink( self, _event ):
1256 "Give up on the current link."
1257 if self.link is not None:
1258 self.canvas.delete( self.link )
1259 self.linkWidget = self.linkItem = self.link = None
1260
1261 # Generic node handlers
1262
1263 def createNodeBindings( self ):
1264 "Create a set of bindings for nodes."
1265 bindings = {
1266 '<ButtonPress-1>': self.clickNode,
1267 '<B1-Motion>': self.dragNode,
1268 '<ButtonRelease-1>': self.releaseNode,
1269 '<Enter>': self.enterNode,
1270 '<Leave>': self.leaveNode
1271 }
1272 l = Label() # lightweight-ish owner for bindings
1273 for event, binding in bindings.items():
1274 l.bind( event, binding )
1275 return l
1276
1277 def selectItem( self, item ):
1278 "Select an item and remember old selection."
1279 self.lastSelection = self.selection
1280 self.selection = item
1281
1282 def enterNode( self, event ):
1283 "Select node on entry."
1284 self.selectNode( event )
1285
1286 def leaveNode( self, _event ):
1287 "Restore old selection on exit."
1288 self.selectItem( self.lastSelection )
1289
1290 def clickNode( self, event ):
1291 "Node click handler."
1292 if self.active is 'NetLink':
1293 self.startLink( event )
1294 else:
1295 self.selectNode( event )
1296 return 'break'
1297
1298 def dragNode( self, event ):
1299 "Node drag handler."
1300 if self.active is 'NetLink':
1301 self.dragNetLink( event )
1302 else:
1303 self.dragNodeAround( event )
1304
1305 def releaseNode( self, event ):
1306 "Node release handler."
1307 if self.active is 'NetLink':
1308 self.finishLink( event )
1309
1310 # Specific node handlers
1311
1312 def selectNode( self, event ):
1313 "Select the node that was clicked on."
1314 item = self.widgetToItem.get( event.widget, None )
1315 self.selectItem( item )
1316
1317 def dragNodeAround( self, event ):
1318 "Drag a node around on the canvas."
1319 c = self.canvas
1320 # Convert global to local coordinates;
1321 # Necessary since x, y are widget-relative
1322 x = self.canvasx( event.x_root )
1323 y = self.canvasy( event.y_root )
1324 w = event.widget
1325 # Adjust node position
1326 item = self.widgetToItem[ w ]
1327 c.coords( item, x, y )
1328 # Adjust link positions
1329 for dest in w.links:
1330 link = w.links[ dest ]
1331 item = self.widgetToItem[ dest ]
1332 x1, y1 = c.coords( item )
1333 c.coords( link, x, y, x1, y1 )
1334 self.updateScrollRegion()
1335
1336 def createDataLinkBindings( self ):
1337 "Create a set of bindings for nodes."
1338 # Link bindings
1339 # Selection still needs a bit of work overall
1340 # Callbacks ignore event
1341
1342 def select( _event, link=self.link ):
1343 "Select item on mouse entry."
1344 self.selectItem( link )
1345
1346 def highlight( _event, link=self.link ):
1347 "Highlight item on mouse entry."
1348 self.selectItem( link )
1349 self.canvas.itemconfig( link, fill='green' )
1350
1351 def unhighlight( _event, link=self.link ):
1352 "Unhighlight item on mouse exit."
1353 self.canvas.itemconfig( link, fill='blue' )
1354 #self.selectItem( None )
1355
1356 self.canvas.tag_bind( self.link, '<Enter>', highlight )
1357 self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
1358 self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
1359 self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup )
1360
1361 def startLink( self, event ):
1362 "Start a new link."
1363 if event.widget not in self.widgetToItem:
1364 # Didn't click on a node
1365 return
1366
1367 w = event.widget
1368 item = self.widgetToItem[ w ]
1369 x, y = self.canvas.coords( item )
1370 self.link = self.canvas.create_line( x, y, x, y, width=4,
1371 fill='blue', tag='link' )
1372 self.linkx, self.linky = x, y
1373 self.linkWidget = w
1374 self.linkItem = item
1375
1376 def finishLink( self, event ):
1377 "Finish creating a link"
1378 if self.link is None:
1379 return
1380 source = self.linkWidget
1381 c = self.canvas
1382 # Since we dragged from the widget, use root coords
1383 x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root )
1384 target = self.findItem( x, y )
1385 dest = self.itemToWidget.get( target, None )
1386 if ( source is None or dest is None or source == dest
1387 or dest in source.links or source in dest.links ):
1388 self.releaseNetLink( event )
1389 return
1390 # For now, don't allow hosts to be directly linked
1391 stags = self.canvas.gettags( self.widgetToItem[ source ] )
1392 dtags = self.canvas.gettags( target )
ashu7b6ba182015-04-17 15:02:37 -05001393 #if (('Host' in stags and 'Host' in dtags)):
1394 #self.releaseNetLink( event )
1395 #return
ashu01b62f72015-03-12 15:16:11 -05001396
1397 # Set link type
1398 linkType='data'
1399
1400 self.createDataLinkBindings()
1401 c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,))
1402
1403 x, y = c.coords( target )
1404 c.coords( self.link, self.linkx, self.linky, x, y )
1405 self.addLink( source, dest, linktype=linkType )
1406
1407 # We're done
1408 self.link = self.linkWidget = None
1409
1410 # Menu handlers
1411
1412 def about( self ):
1413 "Display about box."
1414 about = self.aboutBox
1415 if about is None:
1416 bg = 'white'
1417 about = Toplevel( bg='white' )
1418 about.title( 'About' )
ashu7b6ba182015-04-17 15:02:37 -05001419 info = self.appName + ': a simple network editor for MiniNDN - based on Miniedit'
ashu01b62f72015-03-12 15:16:11 -05001420 warning = 'Development version - not entirely functional!'
1421 #version = 'MiniEdit '+MINIEDIT_VERSION
ashu7b6ba182015-04-17 15:02:37 -05001422 author = 'Vince Lehman, Jan 2015'
1423 author2 = 'Ashlesh Gawande, Jan 2015'
1424 author3 = 'Carlos Cabral, Jan 2013'
1425 author4 = 'Caio Elias, Nov 2014'
1426 author5 = 'Originally by: Bob Lantz <rlantz@cs>, April 2010'
ashu01b62f72015-03-12 15:16:11 -05001427 enhancements = 'Enhancements by: Gregory Gee, Since July 2013'
1428 www = 'http://gregorygee.wordpress.com/category/miniedit/'
1429 line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg )
1430 line2 = Label( about, text=warning, font='Helvetica 9', bg=bg )
1431 line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
1432 line4 = Label( about, text=author2, font='Helvetica 9', bg=bg )
1433 line5 = Label( about, text=author3, font='Helvetica 9', bg=bg )
ashu7b6ba182015-04-17 15:02:37 -05001434 line6 = Label( about, text=author4, font='Helvetica 9', bg=bg )
1435 line7 = Label( about, text=author5, font='Helvetica 9', bg=bg )
1436 line8 = Label( about, text=enhancements, font='Helvetica 9', bg=bg )
1437 line9 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER )
ashu01b62f72015-03-12 15:16:11 -05001438
ashu7b6ba182015-04-17 15:02:37 -05001439
1440 line9.insert(0, www)
1441 line9.configure(state='readonly')
ashu01b62f72015-03-12 15:16:11 -05001442 line1.pack( padx=20, pady=10 )
1443 line2.pack(pady=10 )
1444 line3.pack(pady=10 )
1445 line4.pack(pady=10 )
1446 line5.pack(pady=10 )
1447 line6.pack(pady=10 )
1448 line7.pack(pady=10 )
ashu7b6ba182015-04-17 15:02:37 -05001449 line8.pack(pady=10 )
1450 line9.pack(pady=10 )
ashu01b62f72015-03-12 15:16:11 -05001451 hide = ( lambda about=about: about.withdraw() )
1452 self.aboutBox = about
1453 # Hide on close rather than destroying window
1454 Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
1455 # Show (existing) window
1456 about.deiconify()
1457
1458 def createToolImages( self ):
1459 "Create toolbar (and icon) images."
1460
1461 def hostDetails( self, _ignore=None ):
1462 if ( self.selection is None or
1463 self.net is not None or
1464 self.selection not in self.itemToWidget ):
1465 return
1466 widget = self.itemToWidget[ self.selection ]
1467 name = widget[ 'text' ]
1468 tags = self.canvas.gettags( self.selection )
1469
1470 #print tags
1471 if 'Host' in tags:
1472
1473 prefDefaults = self.hostOpts[name]
1474 hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults, isRouter='False')
1475 self.master.wait_window(hostBox.top)
1476 if hostBox.result:
1477 newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']}
1478
1479 if len(hostBox.result['startCommand']) > 0:
1480 newHostOpts['startCommand'] = hostBox.result['startCommand']
1481 if hostBox.result['cpu']:
1482 newHostOpts['cpu'] = hostBox.result['cpu']
1483 if hostBox.result['mem']:
1484 newHostOpts['mem'] = hostBox.result['mem']
1485 if len(hostBox.result['hostname']) > 0:
1486 newHostOpts['hostname'] = hostBox.result['hostname']
1487 name = hostBox.result['hostname']
1488 widget[ 'text' ] = name
1489 if len(hostBox.result['cache']) > 0:
1490 newHostOpts['cache'] = hostBox.result['cache']
1491 if len(hostBox.result['fibEntries']) > 0:
1492 newHostOpts['fibEntries'] = hostBox.result['fibEntries']
ashu7b6ba182015-04-17 15:02:37 -05001493
1494 newHostOpts['nlsr'] = hostBox.nlsrFrame.getValues()
Ashlesh Gawande3a4afb12015-07-09 09:23:30 -05001495 newHostOpts['nfd'] = hostBox.nfdFrame.getValues()
ashu7b6ba182015-04-17 15:02:37 -05001496
ashu01b62f72015-03-12 15:16:11 -05001497 self.hostOpts[name] = newHostOpts
1498
1499 print 'New host details for ' + name + ' = ' + str(newHostOpts)
1500
1501 elif 'LegacyRouter' in tags:
1502
1503 prefDefaults = self.routerOpts[name]
1504 hostBox = HostDialog(self, title='Router Details', prefDefaults=prefDefaults, isRouter='True')
1505 self.master.wait_window(hostBox.top)
1506 if hostBox.result:
1507 newRouterOpts = {'nodeNum':self.routerOpts[name]['nodeNum']}
1508
1509 if hostBox.result['cpu']:
1510 newRouterOpts['cpu'] = hostBox.result['cpu']
1511 if hostBox.result['mem']:
1512 newRouterOpts['mem'] = hostBox.result['mem']
1513 if len(hostBox.result['hostname']) > 0:
1514 newRouterOpts['hostname'] = hostBox.result['hostname']
1515 name = hostBox.result['hostname']
1516 widget[ 'text' ] = name
1517 if len(hostBox.result['cache']) > 0:
1518 newRouterOpts['cache'] = hostBox.result['cache']
1519 if len(hostBox.result['fibEntries']) > 0:
1520 newRouterOpts['fibEntries'] = hostBox.result['fibEntries']
1521 self.routerOpts[name] = newRouterOpts
1522
1523 print 'New host details for ' + name + ' = ' + str(newRouterOpts)
1524
1525 def linkDetails( self, _ignore=None ):
1526 if ( self.selection is None or
1527 self.net is not None):
1528 return
1529 link = self.selection
1530
1531 linkDetail = self.links[link]
1532 src = linkDetail['src']
1533 dest = linkDetail['dest']
1534 linkopts = linkDetail['linkOpts']
1535 linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
1536 if linkBox.result is not None:
1537 linkDetail['linkOpts'] = linkBox.result
1538 print 'New link details = ' + str(linkBox.result)
1539
1540 # Model interface
1541 #
1542 # Ultimately we will either want to use a topo or
1543 # mininet object here, probably.
1544
1545 def addLink( self, source, dest, linktype='data', linkopts={} ):
1546 "Add link to model."
1547 source.links[ dest ] = self.link
1548 dest.links[ source ] = self.link
1549 self.links[ self.link ] = {'type' :linktype,
1550 'src':source,
1551 'dest':dest,
1552 'linkOpts':linkopts}
1553
1554 def deleteLink( self, link ):
1555 "Delete link from model."
1556 pair = self.links.get( link, None )
1557 if pair is not None:
1558 source=pair['src']
1559 dest=pair['dest']
1560 del source.links[ dest ]
1561 del dest.links[ source ]
1562 stags = self.canvas.gettags( self.widgetToItem[ source ] )
1563 dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
1564 ltags = self.canvas.gettags( link )
1565
1566 if link is not None:
1567 del self.links[ link ]
1568
1569 def deleteNode( self, item ):
1570 "Delete node (and its links) from model."
1571
1572 widget = self.itemToWidget[ item ]
1573 tags = self.canvas.gettags(item)
1574
1575 for link in widget.links.values():
1576 # Delete from view and model
1577 self.deleteItem( link )
1578 del self.itemToWidget[ item ]
1579 del self.widgetToItem[ widget ]
1580
1581 def do_linkPopup(self, event):
1582 # display the popup menu
1583 if ( self.net is None ):
1584 try:
1585 self.linkPopup.tk_popup(event.x_root, event.y_root)
1586 finally:
1587 # make sure to release the grab (Tk 8.0a1 only)
1588 self.linkPopup.grab_release()
1589
1590 def do_legacyRouterPopup(self, event):
1591 # display the popup menu
1592 if ( self.net is None ):
1593 try:
1594 self.legacyRouterPopup.tk_popup(event.x_root, event.y_root)
1595 finally:
1596 # make sure to release the grab (Tk 8.0a1 only)
1597 self.legacyRouterPopup.grab_release()
1598
1599 def do_hostPopup(self, event):
1600 # display the popup menu
1601 if ( self.net is None ):
1602 try:
1603 self.hostPopup.tk_popup(event.x_root, event.y_root)
1604 isHostPopup = True
1605 finally:
1606 # make sure to release the grab (Tk 8.0a1 only)
1607 self.hostPopup.grab_release()
1608
1609 def xterm( self, _ignore=None ):
1610 "Make an xterm when a button is pressed."
1611 if ( self.selection is None or
1612 self.net is None or
1613 self.selection not in self.itemToWidget ):
1614 return
1615 name = self.itemToWidget[ self.selection ][ 'text' ]
1616 if name not in self.net.nameToNode:
1617 return
1618 term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] )
1619 if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
1620 self.net.terms += term
1621 else:
1622 self.net.terms.append(term)
1623
1624 def iperf( self, _ignore=None ):
1625 "Make an xterm when a button is pressed."
1626 if ( self.selection is None or
1627 self.net is None or
1628 self.selection not in self.itemToWidget ):
1629 return
1630 name = self.itemToWidget[ self.selection ][ 'text' ]
1631 if name not in self.net.nameToNode:
1632 return
1633 self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' )
1634
1635 """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """
1636
1637 def parseArgs( self ):
1638 """Parse command-line args and return options object.
1639 returns: opts parse options dict"""
1640
1641 if '--custom' in sys.argv:
1642 index = sys.argv.index( '--custom' )
1643 if len( sys.argv ) > index + 1:
1644 filename = sys.argv[ index + 1 ]
1645 self.parseCustomFile( filename )
1646 else:
1647 raise Exception( 'Custom file name not found' )
1648
ashu7b6ba182015-04-17 15:02:37 -05001649 desc = ( "The %prog utility creates Minindn network from the\n"
ashu01b62f72015-03-12 15:16:11 -05001650 "command line. It can create parametrized topologies,\n"
ashu7b6ba182015-04-17 15:02:37 -05001651 "invoke the Minindn CLI, and run tests." )
ashu01b62f72015-03-12 15:16:11 -05001652
1653 usage = ( '%prog [options] [template_file]\n'
ashu7b6ba182015-04-17 15:02:37 -05001654 '\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 -05001655 'Type %prog -h for details)' )
1656
1657 opts = OptionParser( description=desc, usage=usage )
1658
1659 addDictOption( opts, TOPOS, TOPODEF, 'topo' )
1660 addDictOption( opts, LINKS, LINKDEF, 'link' )
1661
1662 opts.add_option( '--custom', type='string', default=None,
1663 help='read custom topo and node params from .py' +
1664 'file' )
1665
1666 self.options, self.args = opts.parse_args()
1667 # We don't accept extra arguments after the options
1668 if self.args:
1669 if len(self.args) > 1:
1670 opts.print_help()
1671 exit()
1672 else:
1673 self.template_file=self.args[0]
1674
1675 def setCustom( self, name, value ):
1676 "Set custom parameters for MininetRunner."
1677 if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
1678 # Update dictionaries
1679 param = name.upper()
1680 globals()[ param ].update( value )
1681 elif name == 'validate':
1682 # Add custom validate function
1683 self.validate = value
1684 else:
1685 # Add or modify global variable or class
1686 globals()[ name ] = value
1687
1688 def parseCustomFile( self, fileName ):
1689 "Parse custom file and add params before parsing cmd-line options."
1690 customs = {}
1691 if os.path.isfile( fileName ):
1692 execfile( fileName, customs, customs )
1693 for name, val in customs.iteritems():
1694 self.setCustom( name, val )
1695 else:
1696 raise Exception( 'could not find custom file: %s' % fileName )
1697
1698 def importTopo( self ):
1699 print 'topo='+self.options.topo
1700 if self.options.topo == 'none':
1701 return
1702 self.newTopology()
1703 topo = buildTopo( TOPOS, self.options.topo )
1704 link = customConstructor( LINKS, self.options.link )
1705 importNet = Mininet(topo=topo, build=False, link=link)
1706 importNet.build()
1707
1708 c = self.canvas
1709 rowIncrement = 100
1710 currentY = 100
1711
1712 # Add switches
1713 print 'switches:'+str(len(importNet.switches))
1714 columnCount = 0
1715 for switch in importNet.switches:
1716 name = switch.name
1717 self.switchOpts[name] = {}
1718 self.switchOpts[name]['nodeNum']=self.switchCount
1719 self.switchOpts[name]['hostname']=name
1720 self.switchOpts[name]['switchType']='default'
1721 self.switchOpts[name]['controllers']=[]
1722
1723 x = columnCount*100+100
1724 self.addNode('Switch', self.switchCount,
1725 float(x), float(currentY), name=name)
1726 icon = self.findWidgetByName(name)
1727 icon.bind('<Button-3>', self.do_switchPopup )
1728
1729 if columnCount == 9:
1730 columnCount = 0
1731 currentY = currentY + rowIncrement
1732 else:
1733 columnCount =columnCount+1
1734
1735 currentY = currentY + rowIncrement
1736 # Add hosts
1737 print 'hosts:'+str(len(importNet.hosts))
1738 columnCount = 0
1739 for host in importNet.hosts:
1740 name = host.name
1741 self.hostOpts[name] = {'sched':'host'}
1742 self.hostOpts[name]['nodeNum']=self.hostCount
1743 self.hostOpts[name]['hostname']=name
1744 #self.hostOpts[name]['ip']=host.IP()
1745
1746 x = columnCount*100+100
1747 self.addNode('Host', self.hostCount,
1748 float(x), float(currentY), name=name)
1749 icon = self.findWidgetByName(name)
1750 icon.bind('<Button-3>', self.do_hostPopup )
1751 if columnCount == 9:
1752 columnCount = 0
1753 currentY = currentY + rowIncrement
1754 else:
1755 columnCount =columnCount+1
1756
1757 print 'links:'+str(len(topo.links()))
1758 #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')]
1759 for link in topo.links():
1760 print str(link)
1761 srcNode = link[0]
1762 src = self.findWidgetByName(srcNode)
1763 sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
1764
1765 destNode = link[1]
1766 dest = self.findWidgetByName(destNode)
1767 dx, dy = self.canvas.coords( self.widgetToItem[ dest] )
1768
1769 params = topo.linkInfo( srcNode, destNode )
1770 print 'Link Parameters='+str(params)
1771
1772 self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
1773 fill='blue', tag='link' )
1774 c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
1775 self.addLink( src, dest, linkopts=params )
1776 self.createDataLinkBindings()
1777 self.link = self.linkWidget = None
1778
1779 importNet.stop()
1780
1781def miniEditImages():
1782 "Create and return images for MiniEdit."
1783
1784 # Image data. Git will be unhappy. However, the alternative
1785 # is to keep track of separate binary files, which is also
1786 # unappealing.
1787
1788 return {
1789 'Select': BitmapImage(
1790 file='/usr/include/X11/bitmaps/left_ptr' ),
1791
1792 'LegacyRouter': PhotoImage( data=r"""
1793 R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+
1794 3QFq1DmL3wJMmAMzZZW11dnZ2SFrtyNdmTSO6gIZMUKa8gJVqEOH
1795 zR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq6ymF
1796 4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9
1797 vwNgvwJZsX+69gsXJQFHjTtjizF0tvHx8VOm9z2V736Dhz2N3QM2
1798 acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtgtktjfQFu
1799 3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh3
1800 12Gt+VGm/AQIDTmByAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i5
1801 6gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia7DpeggFt2QNP
1802 m97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er
1803 /yVVhwJJktPh70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVV
1804 hQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18zKfP9wwcLAMHCwFF
1805 iS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh
1806 7cve8pG/7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR
1807 1RMjNTF3vU2X4TZupwRSolNne4nB+T+L2YGz4zJ/zYe99YGHjRdD
1808 cT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6Wl
1809 pW2t7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90
1810 uvPz8wIVKBp42SV5zbfT7wtXpStVfwFWrBVvyTt3swFz5kGBv2+1
1811 /QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t
1812 u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T
1813 5yH5BAEAAAAALAAAAAAyABgABwj/AAEIHEiQYJY7Qwg9UsTplRIb
1814 ENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4cHeoI
1815 abJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPA
1816 TqEBoRB9gVJsxRlhPwHI0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o
1817 9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkSrtwADuxC
1818 G/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmD
1819 jhTMmseoKQIFDx7RoxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7
1820 VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq9dGvv09RHFhc
1821 IUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p
1822 9HEUFhxgMSAvjbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJ
1823 ifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CFoVggmEgCyRf01WcF
1824 CYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57Ag
1825 yZckpKKPGFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemM
1826 IQggeaJywSQ/wgHOAmJskQEfWqBlFBEH1P/QaGY3QOpDZXA2+A6m
1827 7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlB
1828 pZdiisd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtCh
1829 RmVPNWgpr+Be+Nc9icARww9TkIEuDAsQ0O7DzGIQzD2QdDEJHTsI
1830 AROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB
1831 xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE
1832 42Q9jtFIp8z0Dy1jQMA1AGziz9VoW7310V0znYDTGMQgwUDXLDBO
1833 2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOMLQkc
1834 jvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZ
1835 MDHKCTwI8EcQFHBBAAFcgGPLHwLwcMIo12Qxu0ABAQA7
1836 """),
1837
1838 'Host': PhotoImage( data=r"""
1839 R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
1840 mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
1841 Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
1842 M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
1843 AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
1844 /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
1845 zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
1846 mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
1847 ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
1848 M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
1849 AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
1850 /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
1851 zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
1852 mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
1853 ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
1854 MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
1855 AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
1856 ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
1857 AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
1858 RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
1859 ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw
1860 BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2
1861 HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO
1862 p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/
1863 C8cSBBAQADs=
1864 """ ),
1865
Vince Lehmanfbd47c92015-10-14 16:00:06 -05001866 'Switch': PhotoImage( data=r"""
1867 R0lGODlhMgAYAPcAAAEBAXmDjbe4uAE5cjF7xwFWq2Sa0S9biSlrrdTW1k2Ly02a5xUvSQFHjmep
1868 6bfI2Q5SlQIYLwFfvj6M3Jaan8fHyDuFzwFp0Vah60uU3AEiRhFgrgFRogFr10N9uTFrpytHYQFM
1869 mGWt9wIwX+bm5kaT4gtFgR1cnJPF9yt80CF0yAIMGHmp2c/P0AEoUb/P4Fei7qK4zgpLjgFkyQlf
1870 t1mf5jKD1WWJrQ86ZwFAgBhYmVOa4MPV52uv8y+A0iR3ywFbtUyX5ECI0Q1UmwIcOUGQ3RBXoQI0
1871 aRJbpr3BxVeJvQUJDafH5wIlS2aq7xBmv52lr7fH12el5Wml3097ph1ru7vM3HCz91Ke6lid40KQ
1872 4GSQvgQGClFnfwVJjszMzVCX3hljrdPT1AFLlBRnutPf6yd5zjeI2QE9eRBdrBNVl+3v70mV4ydf
1873 lwMVKwErVlul8AFChTGB1QE3bsTFxQImTVmAp0FjiUSM1k+b6QQvWQ1SlxMgLgFixEqU3xJhsgFT
1874 pn2Xs5OluZ+1yz1Xb6HN+Td9wy1zuYClykV5r0x2oeDh4qmvt8LDwxhuxRlLfyRioo2124mft9bi
1875 71mDr7fT79nl8Z2hpQs9b7vN4QMQIOPj5XOPrU2Jx32z6xtvwzeBywFFikFnjwcPFa29yxJjuFmP
1876 xQFv3qGxwRc/Z8vb6wsRGBNqwqmpqTdvqQIbNQFPngMzZAEfP0mQ13mHlQFYsAFnznOXu2mPtQxj
1877 vQ1Vn4Ot1+/x8my0/CJgnxNNh8DT5CdJaWyx+AELFWmt8QxPkxBZpwMFB015pgFduGCNuyx7zdnZ
1878 2WKm6h1xyOPp8aW70QtPkUmM0LrCyr/FyztljwFPm0OJzwFny7/L1xFjswE/e12i50iR2VR8o2Gf
1879 3xszS2eTvz2BxSlloQdJiwMHDzF3u7bJ3T2I1WCp8+Xt80FokQFJklef6mORw2ap7SJ1y77Q47nN
1880 3wFfu1Kb5cXJyxdhrdDR0wlNkTSF11Oa4yp4yQEuW0WQ3QIDBQI7dSH5BAEAAAAALAAAAAAyABgA
1881 Bwj/AAEIHDjKF6SDvhImPMHwhA6HOiLqUENRDYSLEIplxBcNHz4Z5GTI8BLKS5OBA1Ply2fDhxwf
1882 PlLITGFmmRkzP+DlVKHCmU9nnz45csSqKKsn9gileZKrVC4aRFACOGZu5UobNuRohRkzhc2b+36o
1883 qCaqrFmzZEV1ERBg3BOmMl5JZTBhwhm7ZyycYZnvJdeuNl21qkCHTiPDhxspTtKoQgUKCJ6wehMV
1884 5QctWupeo6TkjOd8e1lmdQkTGbTTMaDFiDGINeskX6YhEicUiQa5A/kUKaFFwQ0oXzjZ8Tbcm3Hj
1885 irwpMtTSgg9QMJf5WEZ9375AiED19ImpSQSUB4Kw/8HFSMyiRWJaqG/xhf2X91+oCbmq1e/MFD/2
1886 EcApVkWVJhp8J9AqsywQxDfAbLJJPAy+kMkL8shjxTkUnhOJZ5+JVp8cKfhwxwdf4fQLgG4MFAwW
1887 KOZRAxM81EAPPQvoE0QQfrDhx4399OMBMjz2yCMVivCoCAWXKLKMTPvoUYcsKwi0RCcwYCAlFjU0
1888 A6OBM4pXAhsl8FYELYWFWZhiZCbRQgIC2AGTLy408coxAoEDx5wwtGPALTVg0E4NKC7gp4FsBKoA
1889 Ki8U+oIVmVih6DnZPMBMAlGwIARWOLiggSYC+ZNIOulwY4AkSZCyxaikbqHMqaeaIp4+rAaxQxBg
1890 2P+IozuRzvLZIS4syYVAfMAhwhSC1EPCGoskIIYY9yS7Hny75OFnEIAGyiVvWkjjRxF11fXIG3WU
1891 KNA6wghDTCW88PKMJZOkm24Z7LarSjPtoIjFn1lKyyVmmBVhwRtvaDDMgFL0Eu4VhaiDwhXCXNFD
1892 D8QQw7ATEDsBw8RSxotFHs7CKJ60XWrRBj91EOGPQCA48c7J7zTjSTPctOzynjVkkYU+O9S8Axg4
1893 Z6BzBt30003Ps+AhNB5C4PCGC5gKJMMTZJBRytOl/CH1HxvQkMbVVxujtdZGGKGL17rsEfYQe+xR
1894 zNnFcGQCv7LsKlAtp8R9Sgd0032BLXjPoPcMffTd3YcEgAMOxOBA1GJ4AYgXAMjiHDTgggveCgRI
1895 3RfcnffefgcOeDKEG3444osDwgEspMNiTQhx5FoOShxcrrfff0uQjOycD+554qFzMHrpp4cwBju/
1896 5+CmVNbArnntndeCO+O689777+w0IH0o1P/TRJMohRA4EJwn47nyiocOSOmkn/57COxE3wD11Mfh
1897 fg45zCGyVF4Ufvvyze8ewv5jQK9++6FwXxzglwM0GPAfR8AeSo4gwAHCbxsQNCAa/kHBAVhwAHPI
1898 4BE2eIRYeHAEIBwBP0Y4Qn41YWRSCQgAOw==
1899 """ ),
1900
ashu01b62f72015-03-12 15:16:11 -05001901 'NetLink': PhotoImage( data=r"""
1902 R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
1903 mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
1904 Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
1905 M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
1906 AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
1907 /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
1908 zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
1909 mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
1910 ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
1911 M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
1912 AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
1913 /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
1914 zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
1915 mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
1916 ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
1917 MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
1918 AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
1919 ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
1920 AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
1921 RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
1922 ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG
1923 Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy
1924 lBmxI8mSNknm1Dnx5sCAADs=
1925 """ )
1926 }
1927
1928def addDictOption( opts, choicesDict, default, name, helpStr=None ):
1929 """Convenience function to add choices dicts to OptionParser.
1930 opts: OptionParser instance
1931 choicesDict: dictionary of valid choices, must include default
1932 default: default choice key
1933 name: long option name
1934 help: string"""
1935 if default not in choicesDict:
1936 raise Exception( 'Invalid default %s for choices dict: %s' %
1937 ( default, name ) )
1938 if not helpStr:
1939 helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
1940 '[,param=value...]' )
1941 opts.add_option( '--' + name,
1942 type='string',
1943 default = default,
1944 help = helpStr )
1945
1946if __name__ == '__main__':
1947 setLogLevel( 'info' )
1948 app = MiniEdit()
1949 """ import topology if specified """
1950 app.parseArgs()
1951 app.importTopo()
1952
1953 global isHostPopup
1954 global isRouterPopup
1955 global isLinkPopup
1956
1957 app.mainloop()