Vince Lehman | b8b1806 | 2015-07-14 13:07:22 -0500 | [diff] [blame^] | 1 | # -*- 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. |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 60 | |
| 61 | """ |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 62 | MiniNDNEdit: a simple network editor for MiniNDN |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 63 | |
Vince Lehman | b8b1806 | 2015-07-14 13:07:22 -0500 | [diff] [blame^] | 64 | Based on miniccnxedit by: |
| 65 | Carlos Cabral, Jan 2013 |
| 66 | Caio Elias, Nov 2014 |
| 67 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 68 | Based on miniedit by: |
| 69 | Bob Lantz, April 2010 |
| 70 | Gregory Gee, July 2013 |
| 71 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 72 | """ |
| 73 | |
| 74 | MINIEDIT_VERSION = '2.2.0.1' |
| 75 | |
| 76 | from optparse import OptionParser |
| 77 | from Tkinter import * |
| 78 | from ttk import Notebook |
| 79 | from tkMessageBox import showinfo, showerror, showwarning |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 80 | from subprocess import call, Popen |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 81 | import tkFont |
| 82 | import csv |
| 83 | import tkFileDialog |
| 84 | import tkSimpleDialog |
| 85 | import re |
| 86 | import json |
| 87 | from distutils.version import StrictVersion |
| 88 | import os |
| 89 | import sys |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 90 | import threading |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 91 | from functools import partial |
| 92 | |
| 93 | import pdb |
| 94 | |
| 95 | if 'PYTHONPATH' in os.environ: |
| 96 | sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path |
| 97 | |
| 98 | # someday: from ttk import * |
| 99 | |
| 100 | from mininet.log import info, error, debug, output, setLogLevel |
| 101 | from mininet.net import Mininet, VERSION |
| 102 | from mininet.util import ipStr, netParse, ipAdd, quietRun |
| 103 | from mininet.util import buildTopo |
ashu | 2ad32e2 | 2015-05-29 13:37:40 -0500 | [diff] [blame] | 104 | from mininet.util import custom |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 105 | from mininet.term import makeTerm, cleanUpScreens |
| 106 | from mininet.node import Controller, RemoteController, NOX, OVSController |
| 107 | from mininet.node import CPULimitedHost, Host, Node |
| 108 | from mininet.node import OVSKernelSwitch, OVSSwitch, UserSwitch |
| 109 | from mininet.link import TCLink, Intf, Link |
| 110 | from mininet.cli import CLI |
| 111 | from mininet.moduledeps import moduleDeps, pathCheck |
| 112 | from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo |
| 113 | from mininet.topolib import TreeTopo |
| 114 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 115 | print 'MiniNDNEdit running...' #+VERSION |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 116 | MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION) |
| 117 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): |
| 118 | from mininet.node import IVSSwitch |
| 119 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 120 | |
| 121 | from ndn.gui import NfdFrame, NlsrFrame |
| 122 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 123 | TOPODEF = 'none' |
| 124 | TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), |
| 125 | 'linear': LinearTopo, |
| 126 | 'reversed': SingleSwitchReversedTopo, |
| 127 | 'single': SingleSwitchTopo, |
| 128 | 'none': None, |
| 129 | 'tree': TreeTopo } |
| 130 | LINKDEF = 'default' |
| 131 | LINKS = { 'default': Link, |
| 132 | 'tc': TCLink } |
| 133 | HOSTDEF = 'proc' |
| 134 | HOSTS = { 'proc': Host, |
| 135 | 'rt': custom( CPULimitedHost, sched='rt' ), |
| 136 | 'cfs': custom( CPULimitedHost, sched='cfs' ) } |
| 137 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 138 | def 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 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 148 | class 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 | |
| 160 | class 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 | |
| 195 | class 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) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 211 | |
| 212 | # NDN |
Ashlesh Gawande | ab087da | 2015-07-09 15:10:02 -0500 | [diff] [blame] | 213 | self.nfdFrame = NfdFrame(n, self.prefValues) |
| 214 | self.nlsrFrame = NlsrFrame(n,self.prefValues) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 215 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 216 | n.add(self.propFrame, text='Properties') |
| 217 | n.add(self.fibFrame, text='FIB Entries') |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 218 | |
| 219 | n.add(self.nfdFrame, text=self.nfdFrame.frameLabel) |
| 220 | n.add(self.nlsrFrame, text=self.nlsrFrame.frameLabel) |
| 221 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 222 | 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 Gawande | 557cb84 | 2015-07-01 15:39:44 -0500 | [diff] [blame] | 259 | 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) |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 266 | 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(), |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 309 | 'fibEntries':fibEntries, |
Ashlesh Gawande | 3a4afb1 | 2015-07-09 09:23:30 -0500 | [diff] [blame] | 310 | 'nfd': self.nfdFrame.getValues(), |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 311 | 'nlsr': self.nlsrFrame.getValues() |
| 312 | } |
| 313 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 314 | self.result = results |
| 315 | |
| 316 | class 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 | |
| 363 | class 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 | |
| 401 | class 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 | |
| 444 | class 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 | |
| 481 | class MiniEdit( Frame ): |
| 482 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 483 | "A simple network editor for MiniNDN." |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 484 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 485 | def __init__( self, parent=None, cheight=600, cwidth=1000, template_file='minindn.conf' ): |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 486 | |
| 487 | self.template_file = template_file |
| 488 | |
| 489 | Frame.__init__( self, parent ) |
| 490 | self.action = None |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 491 | self.appName = 'MiniNDNEdit' |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 492 | 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 |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 517 | self.tools = ( 'Select', 'Host', 'NetLink' ) |
| 518 | #self.customColors = { 'LegacyRouter': 'darkGreen', 'Host': 'blue' } |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 519 | self.toolbar = self.createToolbar() |
| 520 | |
| 521 | # Layout |
| 522 | self.toolbar.grid( column=0, row=0, sticky='nsew') |
| 523 | self.cframe.grid( column=1, row=0 ) |
| 524 | self.columnconfigure( 1, weight=1 ) |
| 525 | self.rowconfigure( 0, weight=1 ) |
| 526 | self.pack( expand=True, fill='both' ) |
| 527 | |
| 528 | # About box |
| 529 | self.aboutBox = None |
| 530 | |
| 531 | # Initialize node data |
| 532 | self.nodeBindings = self.createNodeBindings() |
| 533 | self.nodePrefixes = { 'LegacyRouter': 'r', 'Host': 'h'} |
| 534 | self.widgetToItem = {} |
| 535 | self.itemToWidget = {} |
| 536 | |
| 537 | # Initialize link tool |
| 538 | self.link = self.linkWidget = None |
| 539 | |
| 540 | # Selection support |
| 541 | self.selection = None |
| 542 | |
| 543 | # Keyboard bindings |
| 544 | self.bind( '<Control-q>', lambda event: self.quit() ) |
| 545 | self.bind( '<KeyPress-Delete>', self.deleteSelection ) |
| 546 | self.bind( '<KeyPress-BackSpace>', self.deleteSelection ) |
| 547 | self.focus() |
| 548 | |
| 549 | #Mouse bindings |
| 550 | self.bind( '<Button-1>', lambda event: self.clearPopups ) |
| 551 | |
| 552 | self.hostPopup = Menu(self.top, tearoff=0) |
| 553 | self.hostPopup.add_command(label='Host Options', font=self.font, command=self.hostDetails) |
| 554 | #self.hostPopup.add_separator() |
| 555 | #self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails ) |
| 556 | |
| 557 | self.legacyRouterPopup = Menu(self.top, tearoff=0) |
| 558 | self.legacyRouterPopup.add_command(label='Router Options', font=self.font, command=self.hostDetails) |
| 559 | |
| 560 | self.linkPopup = Menu(self.top, tearoff=0) |
| 561 | self.linkPopup.add_command(label='Link Options', font=self.font, command=self.linkDetails) |
| 562 | #self.linkPopup.add_separator() |
| 563 | #self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails ) |
| 564 | |
| 565 | # Event handling initalization |
| 566 | self.linkx = self.linky = self.linkItem = None |
| 567 | self.lastSelection = None |
| 568 | |
| 569 | # Model initialization |
| 570 | self.links = {} |
| 571 | self.hostOpts = {} |
| 572 | self.switchOpts = {} |
| 573 | self.routerOpts = {} |
| 574 | self.hostCount = 0 |
| 575 | self.routerCount = 0 |
| 576 | self.net = None |
| 577 | |
| 578 | # Close window gracefully |
| 579 | Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit ) |
| 580 | |
| 581 | def quit( self ): |
| 582 | "Stop our network, if any, then quit." |
| 583 | #sself.stop() |
| 584 | Frame.quit( self ) |
| 585 | |
| 586 | def createMenubar( self ): # MODIFICADO - OK |
| 587 | "Create our menu bar." |
| 588 | |
| 589 | font = self.font |
| 590 | |
| 591 | mbar = Menu( self.top, font=font ) |
| 592 | self.top.configure( menu=mbar ) |
| 593 | |
| 594 | fileMenu = Menu( mbar, tearoff=False ) |
| 595 | mbar.add_cascade( label="File", font=font, menu=fileMenu ) |
| 596 | fileMenu.add_command( label="New", font=font, command=self.newTopology ) |
| 597 | fileMenu.add_command( label="Open", font=font, command=self.loadTopology ) |
| 598 | fileMenu.add_command( label="Save", font=font, command=self.saveTopology ) |
| 599 | fileMenu.add_command( label="Generate", font=font, command=self.doGenerate ) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 600 | fileMenu.add_command( label="Run", font=font, command=self.doRun ) |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 601 | fileMenu.add_separator() |
| 602 | fileMenu.add_command( label='Quit', command=self.quit, font=font ) |
| 603 | |
| 604 | editMenu = Menu( mbar, tearoff=False ) |
| 605 | mbar.add_cascade( label="Edit", font=font, menu=editMenu ) |
| 606 | editMenu.add_command( label="Cut", font=font, |
| 607 | command=lambda: self.deleteSelection( None ) ) |
| 608 | |
| 609 | # Application menu |
| 610 | appMenu = Menu( mbar, tearoff=False ) |
| 611 | mbar.add_cascade( label=self.appName, font=font, menu=appMenu ) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 612 | appMenu.add_command( label='About Mini-NDN', command=self.about, |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 613 | font=font) |
| 614 | #appMenu.add_separator() |
| 615 | #appMenu.add_command( label='Quit', command=self.quit, font=font ) |
| 616 | |
| 617 | # Canvas - TUDO IGUAL - OK |
| 618 | |
| 619 | def createCanvas( self ): |
| 620 | "Create and return our scrolling canvas frame." |
| 621 | f = Frame( self ) |
| 622 | |
| 623 | canvas = Canvas( f, width=self.cwidth, height=self.cheight, |
| 624 | bg=self.bg ) |
| 625 | |
| 626 | # Scroll bars |
| 627 | xbar = Scrollbar( f, orient='horizontal', command=canvas.xview ) |
| 628 | ybar = Scrollbar( f, orient='vertical', command=canvas.yview ) |
| 629 | canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set ) |
| 630 | |
| 631 | # Resize box |
| 632 | resize = Label( f, bg='white' ) |
| 633 | |
| 634 | # Layout |
| 635 | canvas.grid( row=0, column=1, sticky='nsew') |
| 636 | ybar.grid( row=0, column=2, sticky='ns') |
| 637 | xbar.grid( row=1, column=1, sticky='ew' ) |
| 638 | resize.grid( row=1, column=2, sticky='nsew' ) |
| 639 | |
| 640 | # Resize behavior |
| 641 | f.rowconfigure( 0, weight=1 ) |
| 642 | f.columnconfigure( 1, weight=1 ) |
| 643 | f.grid( row=0, column=0, sticky='nsew' ) |
| 644 | f.bind( '<Configure>', lambda event: self.updateScrollRegion() ) |
| 645 | |
| 646 | # Mouse bindings |
| 647 | canvas.bind( '<ButtonPress-1>', self.clickCanvas ) |
| 648 | canvas.bind( '<B1-Motion>', self.dragCanvas ) |
| 649 | canvas.bind( '<ButtonRelease-1>', self.releaseCanvas ) |
| 650 | |
| 651 | return f, canvas |
| 652 | |
| 653 | def updateScrollRegion( self ): |
| 654 | "Update canvas scroll region to hold everything." |
| 655 | bbox = self.canvas.bbox( 'all' ) |
| 656 | if bbox is not None: |
| 657 | self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], |
| 658 | bbox[ 3 ] ) ) |
| 659 | |
| 660 | def canvasx( self, x_root ): |
| 661 | "Convert root x coordinate to canvas coordinate." |
| 662 | c = self.canvas |
| 663 | return c.canvasx( x_root ) - c.winfo_rootx() |
| 664 | |
| 665 | def canvasy( self, y_root ): |
| 666 | "Convert root y coordinate to canvas coordinate." |
| 667 | c = self.canvas |
| 668 | return c.canvasy( y_root ) - c.winfo_rooty() |
| 669 | |
| 670 | # Toolbar |
| 671 | |
| 672 | def activate( self, toolName ): #IGUAL - OK |
| 673 | "Activate a tool and press its button." |
| 674 | # Adjust button appearance |
| 675 | if self.active: |
| 676 | self.buttons[ self.active ].configure( relief='raised' ) |
| 677 | self.buttons[ toolName ].configure( relief='sunken' ) |
| 678 | # Activate dynamic bindings |
| 679 | self.active = toolName |
| 680 | |
| 681 | |
| 682 | def createToolTip(self, widget, text): #NOVA - CRIA HINTS E TIPS |
| 683 | toolTip = ToolTip(widget) |
| 684 | def enter(event): |
| 685 | toolTip.showtip(text) |
| 686 | def leave(event): |
| 687 | toolTip.hidetip() |
| 688 | widget.bind('<Enter>', enter) |
| 689 | widget.bind('<Leave>', leave) |
| 690 | |
| 691 | def createToolbar( self ): #MODIFICADO - OK |
| 692 | "Create and return our toolbar frame." |
| 693 | |
| 694 | toolbar = Frame( self ) |
| 695 | |
| 696 | # Tools |
| 697 | for tool in self.tools: |
| 698 | cmd = ( lambda t=tool: self.activate( t ) ) |
| 699 | b = Button( toolbar, text=tool, font=self.smallFont, command=cmd) |
| 700 | if tool in self.images: |
| 701 | b.config( height=35, image=self.images[ tool ] ) |
| 702 | self.createToolTip(b, str(tool)) |
| 703 | # b.config( compound='top' ) |
| 704 | b.pack( fill='x' ) |
| 705 | self.buttons[ tool ] = b |
| 706 | self.activate( self.tools[ 0 ] ) |
| 707 | |
| 708 | # Spacer |
| 709 | Label( toolbar, text='' ).pack() |
| 710 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 711 | # abaixo copiado Mini-NDN para criar botao Generate |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 712 | |
| 713 | for cmd, color in [ ( 'Generate', 'darkGreen' ) ]: |
| 714 | doCmd = getattr( self, 'do' + cmd ) |
| 715 | b = Button( toolbar, text=cmd, font=self.smallFont, |
| 716 | fg=color, command=doCmd ) |
| 717 | b.pack( fill='x', side='bottom' ) |
| 718 | |
| 719 | return toolbar |
| 720 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 721 | def doGenerate( self ): #COPIA Mini-NDN - GERA TEMPLATE |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 722 | "Generate template." |
| 723 | self.activate( 'Select' ) |
| 724 | for tool in self.tools: |
| 725 | self.buttons[ tool ].config( state='disabled' ) |
| 726 | |
| 727 | self.buildTemplate() |
| 728 | |
| 729 | for tool in self.tools: |
| 730 | self.buttons[ tool ].config( state='normal' ) |
| 731 | |
| 732 | toplevel = Toplevel() |
| 733 | label1 = Label(toplevel, text="Template file generated successfully", height=0, width=30) |
| 734 | label1.pack() |
| 735 | b=Button(toplevel, text="Ok", width=5, command=toplevel.destroy) |
| 736 | b.pack(side='bottom', padx=0,pady=0) |
| 737 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 738 | def doRun( self ): |
| 739 | "Use current configuration to generate a template and run the topology" |
| 740 | |
| 741 | # Generate temporary template file |
| 742 | old_template_file = self.template_file |
| 743 | self.template_file = "/tmp/tmp.conf" |
| 744 | self.doGenerate() |
| 745 | |
| 746 | thread = threading.Thread(target=runMiniNdn, args=(self.master, self.template_file)) |
| 747 | thread.start() |
| 748 | |
| 749 | self.template_file = old_template_file |
| 750 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 751 | def parseFibEntries ( self, fibEntries ): |
| 752 | "Parse FIB Entries for write" |
| 753 | result='' |
| 754 | |
| 755 | for fibEntry in fibEntries: |
| 756 | entry = ','.join(map(str, fibEntry)) |
| 757 | result += entry + ' ' |
| 758 | |
| 759 | return result |
| 760 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 761 | def buildTemplate( self ): #COPIA Mini-NDN para criar Template |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 762 | "Generate template" |
| 763 | |
| 764 | template = open(self.template_file, 'w') |
| 765 | |
| 766 | # hosts |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 767 | template.write('[nodes]\n') |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 768 | for widget in self.widgetToItem: |
| 769 | name = widget[ 'text' ] |
| 770 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 771 | print self.hostOpts[name] |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 772 | if 'Host' in tags: |
| 773 | hOpts=self.hostOpts[name] |
| 774 | template.write(name + ': ') |
| 775 | if 'startCommand' in hOpts: |
Ashlesh Gawande | 557cb84 | 2015-07-01 15:39:44 -0500 | [diff] [blame] | 776 | cmds = hOpts['startCommand'].replace("\"", "\\\"") |
| 777 | template.write('apps="%s" ' % cmds) |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 778 | else: |
| 779 | template.write('_ ') |
| 780 | if 'cache' in hOpts: |
| 781 | template.write('cache=' + hOpts['cache'] + ' ') |
| 782 | if 'cpu' in hOpts: |
| 783 | cpu=float(hOpts['cpu'])/100 |
| 784 | template.write('cpu=' + repr(cpu) + ' ') |
| 785 | if 'mem' in hOpts: |
| 786 | mem=float(hOpts['mem'])/100 |
| 787 | template.write('mem=' + repr(mem) + ' ') |
| 788 | if 'fibEntries' in hOpts: |
| 789 | customFib = self.parseFibEntries(hOpts['fibEntries']) |
| 790 | template.write(customFib) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 791 | if 'nlsr' in hOpts: |
| 792 | values = hOpts['nlsr'] |
| 793 | |
| 794 | template.write('hyperbolic-state=' + values['hyperbolic-state'] + ' ') |
| 795 | template.write('radius=' + values['radius'] + ' ') |
| 796 | template.write('angle=' + values['angle'] + ' ') |
| 797 | template.write('network=' + values['network'] + ' ') |
| 798 | template.write('router=' + values['router'] + ' ') |
| 799 | template.write('site=' + values['site'] + ' ') |
Ashlesh Gawande | c3ed2b9 | 2015-07-01 12:58:08 -0500 | [diff] [blame] | 800 | template.write('nlsr-log-level=' + values['log-level'] + ' ') |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 801 | template.write('max-faces-per-prefix=' + values['max-faces-per-prefix'] + ' ') |
Ashlesh Gawande | 3a4afb1 | 2015-07-09 09:23:30 -0500 | [diff] [blame] | 802 | if 'nfd' in hOpts: |
| 803 | values = hOpts['nfd'] |
| 804 | |
| 805 | template.write('nfd-log-level=' + values['log-level'] + ' ') |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 806 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 807 | template.write('\n') |
| 808 | |
| 809 | # switches/routers |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 810 | #template.write('[routers]\n') |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 811 | |
| 812 | for router in self.routerOpts.values(): |
| 813 | |
| 814 | hasOpt='False' |
| 815 | routerName=router['hostname'] |
| 816 | #nodetype=router['nodetype'] |
| 817 | #nodenum=router['nodenum'] |
| 818 | |
| 819 | rOpts=self.routerOpts[routerName] |
| 820 | |
| 821 | template.write(routerName + ': ') |
| 822 | |
| 823 | if 'cpu' in rOpts: |
| 824 | cpu=float(rOpts['cpu'])/100 |
| 825 | template.write('cpu=' + repr(cpu) + ' ') |
| 826 | hasOpt='True' |
| 827 | if 'mem' in rOpts: |
| 828 | mem=float(rOpts['mem'])/100 |
| 829 | template.write('mem=' + repr(mem) + ' ') |
| 830 | hasOpt='True' |
| 831 | if 'cache' in rOpts: |
| 832 | template.write('cache=' + rOpts['cache'] + ' ') |
| 833 | hasOpt='True' |
| 834 | if 'fibEntries' in rOpts: |
| 835 | customFib = self.parseFibEntries(rOpts['fibEntries']) |
| 836 | template.write(customFib) |
| 837 | hasOpt='True' |
| 838 | if hasOpt == 'False': |
| 839 | template.write('_') |
| 840 | |
| 841 | template.write('\n') |
| 842 | |
| 843 | # Make links |
| 844 | template.write('[links]\n') |
| 845 | for link in self.links.values(): |
| 846 | dst=link['dest'] |
| 847 | src=link['src'] |
| 848 | linkopts=link['linkOpts'] |
| 849 | linktype=link['type'] |
| 850 | |
| 851 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 852 | template.write(srcName + ':' + dstName + ' ') |
| 853 | if 'bw' in linkopts: |
| 854 | template.write('bw=' + str(linkopts['bw']) + ' ' ) |
| 855 | if 'loss' in linkopts: |
| 856 | template.write('loss=' + repr(linkopts['loss']) + ' ' ) |
| 857 | if 'delay' in linkopts: |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 858 | template.write('delay=' + str(linkopts['delay'])+ 'ms' ) |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 859 | |
| 860 | template.write('\n') |
| 861 | |
| 862 | template.close() |
| 863 | |
| 864 | def addNode( self, node, nodeNum, x, y, name=None): |
| 865 | "Add a new node to our canvas." |
| 866 | |
| 867 | if 'LegacyRouter' == node: |
| 868 | self.routerCount += 1 |
| 869 | if 'Host' == node: |
| 870 | self.hostCount += 1 |
| 871 | if name is None: |
| 872 | name = self.nodePrefixes[ node ] + nodeNum |
| 873 | self.addNamedNode(node, name, x, y) |
| 874 | |
| 875 | def addNamedNode( self, node, name, x, y): |
| 876 | "Add a new node to our canvas." |
| 877 | c = self.canvas |
| 878 | icon = self.nodeIcon( node, name ) |
| 879 | item = self.canvas.create_window( x, y, anchor='c', window=icon, |
| 880 | tags=node ) |
| 881 | self.widgetToItem[ icon ] = item |
| 882 | self.itemToWidget[ item ] = icon |
| 883 | icon.links = {} |
| 884 | |
| 885 | def convertJsonUnicode(self, input): |
| 886 | "Some part of Mininet don't like Unicode" |
| 887 | if isinstance(input, dict): |
| 888 | return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in input.iteritems()} |
| 889 | elif isinstance(input, list): |
| 890 | return [self.convertJsonUnicode(element) for element in input] |
| 891 | elif isinstance(input, unicode): |
| 892 | return input.encode('utf-8') |
| 893 | else: |
| 894 | return input |
| 895 | |
| 896 | def loadTopology( self ): |
| 897 | "Load command." |
| 898 | c = self.canvas |
| 899 | |
| 900 | myFormats = [ |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 901 | ('MiniNDN Topology','*.mnndn'), |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 902 | ('All Files','*'), |
| 903 | ] |
| 904 | f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb') |
| 905 | if f == None: |
| 906 | return |
| 907 | self.newTopology() |
| 908 | loadedTopology = self.convertJsonUnicode(json.load(f)) |
| 909 | |
| 910 | # Load hosts |
Ashlesh Gawande | ab087da | 2015-07-09 15:10:02 -0500 | [diff] [blame] | 911 | hosts = loadedTopology['hosts'] |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 912 | for host in hosts: |
| 913 | nodeNum = host['number'] |
| 914 | hostname = 'h'+nodeNum |
| 915 | if 'hostname' in host['opts']: |
| 916 | hostname = host['opts']['hostname'] |
| 917 | else: |
| 918 | host['opts']['hostname'] = hostname |
| 919 | if 'nodeNum' not in host['opts']: |
| 920 | host['opts']['nodeNum'] = int(nodeNum) |
Ashlesh Gawande | ab087da | 2015-07-09 15:10:02 -0500 | [diff] [blame] | 921 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 922 | x = host['x'] |
| 923 | y = host['y'] |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 924 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 925 | self.addNode('Host', nodeNum, float(x), float(y), name=hostname) |
| 926 | |
| 927 | # Fix JSON converting tuple to list when saving |
| 928 | if 'privateDirectory' in host['opts']: |
| 929 | newDirList = [] |
| 930 | for privateDir in host['opts']['privateDirectory']: |
| 931 | if isinstance( privateDir, list ): |
| 932 | newDirList.append((privateDir[0],privateDir[1])) |
| 933 | else: |
| 934 | newDirList.append(privateDir) |
| 935 | host['opts']['privateDirectory'] = newDirList |
| 936 | self.hostOpts[hostname] = host['opts'] |
| 937 | icon = self.findWidgetByName(hostname) |
| 938 | icon.bind('<Button-3>', self.do_hostPopup ) |
| 939 | |
| 940 | # Load routers |
| 941 | routers = loadedTopology['routers'] |
| 942 | for router in routers: |
| 943 | nodeNum = router['number'] |
| 944 | hostname = 'r'+nodeNum |
| 945 | #print router |
| 946 | if 'nodeType' not in router['opts']: |
| 947 | router['opts']['nodeType'] = 'legacyRouter' |
| 948 | if 'hostname' in router['opts']: |
| 949 | hostname = router['opts']['hostname'] |
| 950 | else: |
| 951 | router['opts']['hostname'] = hostname |
| 952 | if 'nodeNum' not in router['opts']: |
| 953 | router['opts']['nodeNum'] = int(nodeNum) |
| 954 | x = router['x'] |
| 955 | y = router['y'] |
| 956 | if router['opts']['nodeType'] == "legacyRouter": |
| 957 | self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname) |
| 958 | icon = self.findWidgetByName(hostname) |
| 959 | icon.bind('<Button-3>', self.do_legacyRouterPopup ) |
| 960 | self.routerOpts[hostname] = router['opts'] |
| 961 | |
| 962 | # Load links |
| 963 | links = loadedTopology['links'] |
| 964 | for link in links: |
| 965 | srcNode = link['src'] |
| 966 | src = self.findWidgetByName(srcNode) |
| 967 | sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) |
| 968 | |
| 969 | destNode = link['dest'] |
| 970 | dest = self.findWidgetByName(destNode) |
| 971 | dx, dy = self.canvas.coords( self.widgetToItem[ dest ] ) |
| 972 | |
| 973 | self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, |
| 974 | fill='blue', tag='link' ) |
| 975 | c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) |
| 976 | self.addLink( src, dest, linkopts=link['opts'] ) |
| 977 | self.createDataLinkBindings() |
| 978 | self.link = self.linkWidget = None |
| 979 | |
| 980 | f.close |
| 981 | |
| 982 | def findWidgetByName( self, name ): |
| 983 | for widget in self.widgetToItem: |
| 984 | if name == widget[ 'text' ]: |
| 985 | return widget |
| 986 | |
| 987 | def newTopology( self ): |
| 988 | "New command." |
| 989 | for widget in self.widgetToItem.keys(): |
| 990 | self.deleteItem( self.widgetToItem[ widget ] ) |
| 991 | self.hostCount = 0 |
| 992 | self.routerCount = 0 |
| 993 | self.links = {} |
| 994 | self.hostOpts = {} |
| 995 | self.routerOpts = {} |
| 996 | |
| 997 | def saveTopology( self ): |
| 998 | "Save command." |
| 999 | myFormats = [ |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1000 | ('MiniNDN Topology','*.mnndn'), |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1001 | ('All Files','*'), |
| 1002 | ] |
| 1003 | |
| 1004 | savingDictionary = {} |
| 1005 | fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...") |
| 1006 | if len(fileName ) > 0: |
| 1007 | # Save Application preferences |
| 1008 | savingDictionary['version'] = '2' |
| 1009 | |
| 1010 | # Save routers and Hosts |
| 1011 | hostsToSave = [] |
| 1012 | routersToSave = [] |
| 1013 | |
| 1014 | for widget in self.widgetToItem: |
| 1015 | name = widget[ 'text' ] |
| 1016 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1017 | x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) |
| 1018 | if 'LegacyRouter' in tags: |
| 1019 | nodeNum = self.routerOpts[name]['nodeNum'] |
| 1020 | nodeToSave = {'number':str(nodeNum), |
| 1021 | 'x':str(x1), |
| 1022 | 'y':str(y1), |
| 1023 | 'opts':self.routerOpts[name] } |
| 1024 | routersToSave.append(nodeToSave) |
| 1025 | elif 'Host' in tags: |
| 1026 | nodeNum = self.hostOpts[name]['nodeNum'] |
| 1027 | nodeToSave = {'number':str(nodeNum), |
| 1028 | 'x':str(x1), |
| 1029 | 'y':str(y1), |
| 1030 | 'opts':self.hostOpts[name] } |
| 1031 | hostsToSave.append(nodeToSave) |
| 1032 | else: |
| 1033 | raise Exception( "Cannot create mystery node: " + name ) |
| 1034 | savingDictionary['hosts'] = hostsToSave |
| 1035 | savingDictionary['routers'] = routersToSave |
| 1036 | |
| 1037 | # Save Links |
| 1038 | linksToSave = [] |
| 1039 | for link in self.links.values(): |
| 1040 | src = link['src'] |
| 1041 | dst = link['dest'] |
| 1042 | linkopts = link['linkOpts'] |
| 1043 | |
| 1044 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 1045 | linkToSave = {'src':srcName, |
| 1046 | 'dest':dstName, |
| 1047 | 'opts':linkopts} |
| 1048 | if link['type'] == 'data': |
| 1049 | linksToSave.append(linkToSave) |
| 1050 | savingDictionary['links'] = linksToSave |
| 1051 | |
| 1052 | # Save Application preferences |
| 1053 | #savingDictionary['application'] = self.appPrefs |
| 1054 | |
| 1055 | try: |
| 1056 | f = open(fileName, 'wb') |
| 1057 | f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': '))) |
| 1058 | except Exception as er: |
| 1059 | print er |
| 1060 | finally: |
| 1061 | f.close() |
| 1062 | |
| 1063 | # Generic canvas handler |
| 1064 | # |
| 1065 | # We could have used bindtags, as in nodeIcon, but |
| 1066 | # the dynamic approach used here |
| 1067 | # may actually require less code. In any case, it's an |
| 1068 | # interesting introspection-based alternative to bindtags. |
| 1069 | |
| 1070 | def canvasHandle( self, eventName, event ): |
| 1071 | "Generic canvas event handler" |
| 1072 | if self.active is None: |
| 1073 | return |
| 1074 | toolName = self.active |
| 1075 | handler = getattr( self, eventName + toolName, None ) |
| 1076 | if handler is not None: |
| 1077 | handler( event ) |
| 1078 | |
| 1079 | def clickCanvas( self, event ): |
| 1080 | "Canvas click handler." |
| 1081 | self.canvasHandle( 'click', event ) |
| 1082 | |
| 1083 | def dragCanvas( self, event ): |
| 1084 | "Canvas drag handler." |
| 1085 | self.canvasHandle( 'drag', event ) |
| 1086 | |
| 1087 | def releaseCanvas( self, event ): |
| 1088 | "Canvas mouse up handler." |
| 1089 | self.canvasHandle( 'release', event ) |
| 1090 | |
| 1091 | # Currently the only items we can select directly are |
| 1092 | # links. Nodes are handled by bindings in the node icon. |
| 1093 | |
| 1094 | def findItem( self, x, y ): |
| 1095 | "Find items at a location in our canvas." |
| 1096 | items = self.canvas.find_overlapping( x, y, x, y ) |
| 1097 | if len( items ) == 0: |
| 1098 | return None |
| 1099 | else: |
| 1100 | return items[ 0 ] |
| 1101 | |
| 1102 | # Canvas bindings for Select, Host, Router and Link tools |
| 1103 | |
| 1104 | def clickSelect( self, event ): |
| 1105 | "Select an item." |
| 1106 | self.selectItem( self.findItem( event.x, event.y ) ) |
| 1107 | |
| 1108 | def deleteItem( self, item ): |
| 1109 | "Delete an item." |
| 1110 | # Don't delete while network is running |
| 1111 | if self.buttons[ 'Select' ][ 'state' ] == 'disabled': |
| 1112 | return |
| 1113 | # Delete from model |
| 1114 | if item in self.links: |
| 1115 | self.deleteLink( item ) |
| 1116 | if item in self.itemToWidget: |
| 1117 | self.deleteNode( item ) |
| 1118 | # Delete from view |
| 1119 | self.canvas.delete( item ) |
| 1120 | |
| 1121 | def deleteSelection( self, _event ): |
| 1122 | "Delete the selected item." |
| 1123 | if self.selection is not None: |
| 1124 | self.deleteItem( self.selection ) |
| 1125 | self.selectItem( None ) |
| 1126 | |
| 1127 | def clearPopups(self): |
| 1128 | print 'Entrou funcao clear_popups' |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1129 | if isHostPopup == True: |
| 1130 | print 'Hostpopup = true' |
| 1131 | self.hostPopup.unpost |
| 1132 | isHostPopup = False |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1133 | #if isRouterPopup == True |
| 1134 | #if isLinkPopup == True |
| 1135 | |
| 1136 | def nodeIcon( self, node, name ): |
| 1137 | "Create a new node icon." |
| 1138 | icon = Button( self.canvas, image=self.images[ node ], |
| 1139 | text=name, compound='top' ) |
| 1140 | # Unfortunately bindtags wants a tuple |
| 1141 | bindtags = [ str( self.nodeBindings ) ] |
| 1142 | bindtags += list( icon.bindtags() ) |
| 1143 | icon.bindtags( tuple( bindtags ) ) |
| 1144 | return icon |
| 1145 | |
| 1146 | def newNode( self, node, event ): |
| 1147 | "Add a new node to our canvas." |
| 1148 | c = self.canvas |
| 1149 | x, y = c.canvasx( event.x ), c.canvasy( event.y ) |
| 1150 | name = self.nodePrefixes[ node ] |
| 1151 | |
| 1152 | if 'LegacyRouter' == node: |
| 1153 | self.routerCount += 1 |
| 1154 | name = self.nodePrefixes[ node ] + str( self.routerCount ) |
| 1155 | self.routerOpts[name] = {} |
| 1156 | self.routerOpts[name]['nodeNum']=self.routerCount |
| 1157 | self.routerOpts[name]['hostname']=name |
| 1158 | self.routerOpts[name]['nodeType']='legacyRouter' |
| 1159 | |
| 1160 | if 'Host' == node: |
| 1161 | self.hostCount += 1 |
| 1162 | name = self.nodePrefixes[ node ] + str( self.hostCount ) |
| 1163 | self.hostOpts[name] = {'sched':'host'} |
| 1164 | self.hostOpts[name]['nodeNum']=self.hostCount |
| 1165 | self.hostOpts[name]['hostname']=name |
| 1166 | |
| 1167 | icon = self.nodeIcon( node, name ) |
| 1168 | item = self.canvas.create_window( x, y, anchor='c', window=icon, |
| 1169 | tags=node ) |
| 1170 | self.widgetToItem[ icon ] = item |
| 1171 | self.itemToWidget[ item ] = icon |
| 1172 | self.selectItem( item ) |
| 1173 | icon.links = {} |
| 1174 | if 'LegacyRouter' == node: |
| 1175 | icon.bind('<Button-3>', self.do_legacyRouterPopup ) |
| 1176 | if 'Host' == node: |
| 1177 | icon.bind('<Button-3>', self.do_hostPopup ) |
| 1178 | |
| 1179 | def clickHost( self, event ): |
| 1180 | "Add a new host to our canvas." |
| 1181 | self.newNode( 'Host', event ) |
| 1182 | |
| 1183 | def clickLegacyRouter( self, event ): |
| 1184 | "Add a new router to our canvas." |
| 1185 | self.newNode( 'LegacyRouter', event ) |
| 1186 | |
| 1187 | def dragNetLink( self, event ): |
| 1188 | "Drag a link's endpoint to another node." |
| 1189 | if self.link is None: |
| 1190 | return |
| 1191 | # Since drag starts in widget, we use root coords |
| 1192 | x = self.canvasx( event.x_root ) |
| 1193 | y = self.canvasy( event.y_root ) |
| 1194 | c = self.canvas |
| 1195 | c.coords( self.link, self.linkx, self.linky, x, y ) |
| 1196 | |
| 1197 | def releaseNetLink( self, _event ): |
| 1198 | "Give up on the current link." |
| 1199 | if self.link is not None: |
| 1200 | self.canvas.delete( self.link ) |
| 1201 | self.linkWidget = self.linkItem = self.link = None |
| 1202 | |
| 1203 | # Generic node handlers |
| 1204 | |
| 1205 | def createNodeBindings( self ): |
| 1206 | "Create a set of bindings for nodes." |
| 1207 | bindings = { |
| 1208 | '<ButtonPress-1>': self.clickNode, |
| 1209 | '<B1-Motion>': self.dragNode, |
| 1210 | '<ButtonRelease-1>': self.releaseNode, |
| 1211 | '<Enter>': self.enterNode, |
| 1212 | '<Leave>': self.leaveNode |
| 1213 | } |
| 1214 | l = Label() # lightweight-ish owner for bindings |
| 1215 | for event, binding in bindings.items(): |
| 1216 | l.bind( event, binding ) |
| 1217 | return l |
| 1218 | |
| 1219 | def selectItem( self, item ): |
| 1220 | "Select an item and remember old selection." |
| 1221 | self.lastSelection = self.selection |
| 1222 | self.selection = item |
| 1223 | |
| 1224 | def enterNode( self, event ): |
| 1225 | "Select node on entry." |
| 1226 | self.selectNode( event ) |
| 1227 | |
| 1228 | def leaveNode( self, _event ): |
| 1229 | "Restore old selection on exit." |
| 1230 | self.selectItem( self.lastSelection ) |
| 1231 | |
| 1232 | def clickNode( self, event ): |
| 1233 | "Node click handler." |
| 1234 | if self.active is 'NetLink': |
| 1235 | self.startLink( event ) |
| 1236 | else: |
| 1237 | self.selectNode( event ) |
| 1238 | return 'break' |
| 1239 | |
| 1240 | def dragNode( self, event ): |
| 1241 | "Node drag handler." |
| 1242 | if self.active is 'NetLink': |
| 1243 | self.dragNetLink( event ) |
| 1244 | else: |
| 1245 | self.dragNodeAround( event ) |
| 1246 | |
| 1247 | def releaseNode( self, event ): |
| 1248 | "Node release handler." |
| 1249 | if self.active is 'NetLink': |
| 1250 | self.finishLink( event ) |
| 1251 | |
| 1252 | # Specific node handlers |
| 1253 | |
| 1254 | def selectNode( self, event ): |
| 1255 | "Select the node that was clicked on." |
| 1256 | item = self.widgetToItem.get( event.widget, None ) |
| 1257 | self.selectItem( item ) |
| 1258 | |
| 1259 | def dragNodeAround( self, event ): |
| 1260 | "Drag a node around on the canvas." |
| 1261 | c = self.canvas |
| 1262 | # Convert global to local coordinates; |
| 1263 | # Necessary since x, y are widget-relative |
| 1264 | x = self.canvasx( event.x_root ) |
| 1265 | y = self.canvasy( event.y_root ) |
| 1266 | w = event.widget |
| 1267 | # Adjust node position |
| 1268 | item = self.widgetToItem[ w ] |
| 1269 | c.coords( item, x, y ) |
| 1270 | # Adjust link positions |
| 1271 | for dest in w.links: |
| 1272 | link = w.links[ dest ] |
| 1273 | item = self.widgetToItem[ dest ] |
| 1274 | x1, y1 = c.coords( item ) |
| 1275 | c.coords( link, x, y, x1, y1 ) |
| 1276 | self.updateScrollRegion() |
| 1277 | |
| 1278 | def createDataLinkBindings( self ): |
| 1279 | "Create a set of bindings for nodes." |
| 1280 | # Link bindings |
| 1281 | # Selection still needs a bit of work overall |
| 1282 | # Callbacks ignore event |
| 1283 | |
| 1284 | def select( _event, link=self.link ): |
| 1285 | "Select item on mouse entry." |
| 1286 | self.selectItem( link ) |
| 1287 | |
| 1288 | def highlight( _event, link=self.link ): |
| 1289 | "Highlight item on mouse entry." |
| 1290 | self.selectItem( link ) |
| 1291 | self.canvas.itemconfig( link, fill='green' ) |
| 1292 | |
| 1293 | def unhighlight( _event, link=self.link ): |
| 1294 | "Unhighlight item on mouse exit." |
| 1295 | self.canvas.itemconfig( link, fill='blue' ) |
| 1296 | #self.selectItem( None ) |
| 1297 | |
| 1298 | self.canvas.tag_bind( self.link, '<Enter>', highlight ) |
| 1299 | self.canvas.tag_bind( self.link, '<Leave>', unhighlight ) |
| 1300 | self.canvas.tag_bind( self.link, '<ButtonPress-1>', select ) |
| 1301 | self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup ) |
| 1302 | |
| 1303 | def startLink( self, event ): |
| 1304 | "Start a new link." |
| 1305 | if event.widget not in self.widgetToItem: |
| 1306 | # Didn't click on a node |
| 1307 | return |
| 1308 | |
| 1309 | w = event.widget |
| 1310 | item = self.widgetToItem[ w ] |
| 1311 | x, y = self.canvas.coords( item ) |
| 1312 | self.link = self.canvas.create_line( x, y, x, y, width=4, |
| 1313 | fill='blue', tag='link' ) |
| 1314 | self.linkx, self.linky = x, y |
| 1315 | self.linkWidget = w |
| 1316 | self.linkItem = item |
| 1317 | |
| 1318 | def finishLink( self, event ): |
| 1319 | "Finish creating a link" |
| 1320 | if self.link is None: |
| 1321 | return |
| 1322 | source = self.linkWidget |
| 1323 | c = self.canvas |
| 1324 | # Since we dragged from the widget, use root coords |
| 1325 | x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root ) |
| 1326 | target = self.findItem( x, y ) |
| 1327 | dest = self.itemToWidget.get( target, None ) |
| 1328 | if ( source is None or dest is None or source == dest |
| 1329 | or dest in source.links or source in dest.links ): |
| 1330 | self.releaseNetLink( event ) |
| 1331 | return |
| 1332 | # For now, don't allow hosts to be directly linked |
| 1333 | stags = self.canvas.gettags( self.widgetToItem[ source ] ) |
| 1334 | dtags = self.canvas.gettags( target ) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1335 | #if (('Host' in stags and 'Host' in dtags)): |
| 1336 | #self.releaseNetLink( event ) |
| 1337 | #return |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1338 | |
| 1339 | # Set link type |
| 1340 | linkType='data' |
| 1341 | |
| 1342 | self.createDataLinkBindings() |
| 1343 | c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,)) |
| 1344 | |
| 1345 | x, y = c.coords( target ) |
| 1346 | c.coords( self.link, self.linkx, self.linky, x, y ) |
| 1347 | self.addLink( source, dest, linktype=linkType ) |
| 1348 | |
| 1349 | # We're done |
| 1350 | self.link = self.linkWidget = None |
| 1351 | |
| 1352 | # Menu handlers |
| 1353 | |
| 1354 | def about( self ): |
| 1355 | "Display about box." |
| 1356 | about = self.aboutBox |
| 1357 | if about is None: |
| 1358 | bg = 'white' |
| 1359 | about = Toplevel( bg='white' ) |
| 1360 | about.title( 'About' ) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1361 | info = self.appName + ': a simple network editor for MiniNDN - based on Miniedit' |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1362 | warning = 'Development version - not entirely functional!' |
| 1363 | #version = 'MiniEdit '+MINIEDIT_VERSION |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1364 | author = 'Vince Lehman, Jan 2015' |
| 1365 | author2 = 'Ashlesh Gawande, Jan 2015' |
| 1366 | author3 = 'Carlos Cabral, Jan 2013' |
| 1367 | author4 = 'Caio Elias, Nov 2014' |
| 1368 | author5 = 'Originally by: Bob Lantz <rlantz@cs>, April 2010' |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1369 | enhancements = 'Enhancements by: Gregory Gee, Since July 2013' |
| 1370 | www = 'http://gregorygee.wordpress.com/category/miniedit/' |
| 1371 | line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg ) |
| 1372 | line2 = Label( about, text=warning, font='Helvetica 9', bg=bg ) |
| 1373 | line3 = Label( about, text=author, font='Helvetica 9', bg=bg ) |
| 1374 | line4 = Label( about, text=author2, font='Helvetica 9', bg=bg ) |
| 1375 | line5 = Label( about, text=author3, font='Helvetica 9', bg=bg ) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1376 | line6 = Label( about, text=author4, font='Helvetica 9', bg=bg ) |
| 1377 | line7 = Label( about, text=author5, font='Helvetica 9', bg=bg ) |
| 1378 | line8 = Label( about, text=enhancements, font='Helvetica 9', bg=bg ) |
| 1379 | line9 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER ) |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1380 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1381 | |
| 1382 | line9.insert(0, www) |
| 1383 | line9.configure(state='readonly') |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1384 | line1.pack( padx=20, pady=10 ) |
| 1385 | line2.pack(pady=10 ) |
| 1386 | line3.pack(pady=10 ) |
| 1387 | line4.pack(pady=10 ) |
| 1388 | line5.pack(pady=10 ) |
| 1389 | line6.pack(pady=10 ) |
| 1390 | line7.pack(pady=10 ) |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1391 | line8.pack(pady=10 ) |
| 1392 | line9.pack(pady=10 ) |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1393 | hide = ( lambda about=about: about.withdraw() ) |
| 1394 | self.aboutBox = about |
| 1395 | # Hide on close rather than destroying window |
| 1396 | Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide ) |
| 1397 | # Show (existing) window |
| 1398 | about.deiconify() |
| 1399 | |
| 1400 | def createToolImages( self ): |
| 1401 | "Create toolbar (and icon) images." |
| 1402 | |
| 1403 | def hostDetails( self, _ignore=None ): |
| 1404 | if ( self.selection is None or |
| 1405 | self.net is not None or |
| 1406 | self.selection not in self.itemToWidget ): |
| 1407 | return |
| 1408 | widget = self.itemToWidget[ self.selection ] |
| 1409 | name = widget[ 'text' ] |
| 1410 | tags = self.canvas.gettags( self.selection ) |
| 1411 | |
| 1412 | #print tags |
| 1413 | if 'Host' in tags: |
| 1414 | |
| 1415 | prefDefaults = self.hostOpts[name] |
| 1416 | hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults, isRouter='False') |
| 1417 | self.master.wait_window(hostBox.top) |
| 1418 | if hostBox.result: |
| 1419 | newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']} |
| 1420 | |
| 1421 | if len(hostBox.result['startCommand']) > 0: |
| 1422 | newHostOpts['startCommand'] = hostBox.result['startCommand'] |
| 1423 | if hostBox.result['cpu']: |
| 1424 | newHostOpts['cpu'] = hostBox.result['cpu'] |
| 1425 | if hostBox.result['mem']: |
| 1426 | newHostOpts['mem'] = hostBox.result['mem'] |
| 1427 | if len(hostBox.result['hostname']) > 0: |
| 1428 | newHostOpts['hostname'] = hostBox.result['hostname'] |
| 1429 | name = hostBox.result['hostname'] |
| 1430 | widget[ 'text' ] = name |
| 1431 | if len(hostBox.result['cache']) > 0: |
| 1432 | newHostOpts['cache'] = hostBox.result['cache'] |
| 1433 | if len(hostBox.result['fibEntries']) > 0: |
| 1434 | newHostOpts['fibEntries'] = hostBox.result['fibEntries'] |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1435 | |
| 1436 | newHostOpts['nlsr'] = hostBox.nlsrFrame.getValues() |
Ashlesh Gawande | 3a4afb1 | 2015-07-09 09:23:30 -0500 | [diff] [blame] | 1437 | newHostOpts['nfd'] = hostBox.nfdFrame.getValues() |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1438 | |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1439 | self.hostOpts[name] = newHostOpts |
| 1440 | |
| 1441 | print 'New host details for ' + name + ' = ' + str(newHostOpts) |
| 1442 | |
| 1443 | elif 'LegacyRouter' in tags: |
| 1444 | |
| 1445 | prefDefaults = self.routerOpts[name] |
| 1446 | hostBox = HostDialog(self, title='Router Details', prefDefaults=prefDefaults, isRouter='True') |
| 1447 | self.master.wait_window(hostBox.top) |
| 1448 | if hostBox.result: |
| 1449 | newRouterOpts = {'nodeNum':self.routerOpts[name]['nodeNum']} |
| 1450 | |
| 1451 | if hostBox.result['cpu']: |
| 1452 | newRouterOpts['cpu'] = hostBox.result['cpu'] |
| 1453 | if hostBox.result['mem']: |
| 1454 | newRouterOpts['mem'] = hostBox.result['mem'] |
| 1455 | if len(hostBox.result['hostname']) > 0: |
| 1456 | newRouterOpts['hostname'] = hostBox.result['hostname'] |
| 1457 | name = hostBox.result['hostname'] |
| 1458 | widget[ 'text' ] = name |
| 1459 | if len(hostBox.result['cache']) > 0: |
| 1460 | newRouterOpts['cache'] = hostBox.result['cache'] |
| 1461 | if len(hostBox.result['fibEntries']) > 0: |
| 1462 | newRouterOpts['fibEntries'] = hostBox.result['fibEntries'] |
| 1463 | self.routerOpts[name] = newRouterOpts |
| 1464 | |
| 1465 | print 'New host details for ' + name + ' = ' + str(newRouterOpts) |
| 1466 | |
| 1467 | def linkDetails( self, _ignore=None ): |
| 1468 | if ( self.selection is None or |
| 1469 | self.net is not None): |
| 1470 | return |
| 1471 | link = self.selection |
| 1472 | |
| 1473 | linkDetail = self.links[link] |
| 1474 | src = linkDetail['src'] |
| 1475 | dest = linkDetail['dest'] |
| 1476 | linkopts = linkDetail['linkOpts'] |
| 1477 | linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts) |
| 1478 | if linkBox.result is not None: |
| 1479 | linkDetail['linkOpts'] = linkBox.result |
| 1480 | print 'New link details = ' + str(linkBox.result) |
| 1481 | |
| 1482 | # Model interface |
| 1483 | # |
| 1484 | # Ultimately we will either want to use a topo or |
| 1485 | # mininet object here, probably. |
| 1486 | |
| 1487 | def addLink( self, source, dest, linktype='data', linkopts={} ): |
| 1488 | "Add link to model." |
| 1489 | source.links[ dest ] = self.link |
| 1490 | dest.links[ source ] = self.link |
| 1491 | self.links[ self.link ] = {'type' :linktype, |
| 1492 | 'src':source, |
| 1493 | 'dest':dest, |
| 1494 | 'linkOpts':linkopts} |
| 1495 | |
| 1496 | def deleteLink( self, link ): |
| 1497 | "Delete link from model." |
| 1498 | pair = self.links.get( link, None ) |
| 1499 | if pair is not None: |
| 1500 | source=pair['src'] |
| 1501 | dest=pair['dest'] |
| 1502 | del source.links[ dest ] |
| 1503 | del dest.links[ source ] |
| 1504 | stags = self.canvas.gettags( self.widgetToItem[ source ] ) |
| 1505 | dtags = self.canvas.gettags( self.widgetToItem[ dest ] ) |
| 1506 | ltags = self.canvas.gettags( link ) |
| 1507 | |
| 1508 | if link is not None: |
| 1509 | del self.links[ link ] |
| 1510 | |
| 1511 | def deleteNode( self, item ): |
| 1512 | "Delete node (and its links) from model." |
| 1513 | |
| 1514 | widget = self.itemToWidget[ item ] |
| 1515 | tags = self.canvas.gettags(item) |
| 1516 | |
| 1517 | for link in widget.links.values(): |
| 1518 | # Delete from view and model |
| 1519 | self.deleteItem( link ) |
| 1520 | del self.itemToWidget[ item ] |
| 1521 | del self.widgetToItem[ widget ] |
| 1522 | |
| 1523 | def do_linkPopup(self, event): |
| 1524 | # display the popup menu |
| 1525 | if ( self.net is None ): |
| 1526 | try: |
| 1527 | self.linkPopup.tk_popup(event.x_root, event.y_root) |
| 1528 | finally: |
| 1529 | # make sure to release the grab (Tk 8.0a1 only) |
| 1530 | self.linkPopup.grab_release() |
| 1531 | |
| 1532 | def do_legacyRouterPopup(self, event): |
| 1533 | # display the popup menu |
| 1534 | if ( self.net is None ): |
| 1535 | try: |
| 1536 | self.legacyRouterPopup.tk_popup(event.x_root, event.y_root) |
| 1537 | finally: |
| 1538 | # make sure to release the grab (Tk 8.0a1 only) |
| 1539 | self.legacyRouterPopup.grab_release() |
| 1540 | |
| 1541 | def do_hostPopup(self, event): |
| 1542 | # display the popup menu |
| 1543 | if ( self.net is None ): |
| 1544 | try: |
| 1545 | self.hostPopup.tk_popup(event.x_root, event.y_root) |
| 1546 | isHostPopup = True |
| 1547 | finally: |
| 1548 | # make sure to release the grab (Tk 8.0a1 only) |
| 1549 | self.hostPopup.grab_release() |
| 1550 | |
| 1551 | def xterm( self, _ignore=None ): |
| 1552 | "Make an xterm when a button is pressed." |
| 1553 | if ( self.selection is None or |
| 1554 | self.net is None or |
| 1555 | self.selection not in self.itemToWidget ): |
| 1556 | return |
| 1557 | name = self.itemToWidget[ self.selection ][ 'text' ] |
| 1558 | if name not in self.net.nameToNode: |
| 1559 | return |
| 1560 | term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] ) |
| 1561 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): |
| 1562 | self.net.terms += term |
| 1563 | else: |
| 1564 | self.net.terms.append(term) |
| 1565 | |
| 1566 | def iperf( self, _ignore=None ): |
| 1567 | "Make an xterm when a button is pressed." |
| 1568 | if ( self.selection is None or |
| 1569 | self.net is None or |
| 1570 | self.selection not in self.itemToWidget ): |
| 1571 | return |
| 1572 | name = self.itemToWidget[ self.selection ][ 'text' ] |
| 1573 | if name not in self.net.nameToNode: |
| 1574 | return |
| 1575 | self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' ) |
| 1576 | |
| 1577 | """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """ |
| 1578 | |
| 1579 | def parseArgs( self ): |
| 1580 | """Parse command-line args and return options object. |
| 1581 | returns: opts parse options dict""" |
| 1582 | |
| 1583 | if '--custom' in sys.argv: |
| 1584 | index = sys.argv.index( '--custom' ) |
| 1585 | if len( sys.argv ) > index + 1: |
| 1586 | filename = sys.argv[ index + 1 ] |
| 1587 | self.parseCustomFile( filename ) |
| 1588 | else: |
| 1589 | raise Exception( 'Custom file name not found' ) |
| 1590 | |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1591 | desc = ( "The %prog utility creates Minindn network from the\n" |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1592 | "command line. It can create parametrized topologies,\n" |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1593 | "invoke the Minindn CLI, and run tests." ) |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1594 | |
| 1595 | usage = ( '%prog [options] [template_file]\n' |
ashu | 7b6ba18 | 2015-04-17 15:02:37 -0500 | [diff] [blame] | 1596 | '\nIf no template_file is given, generated template will be written to the file minindn.conf in the current directory.\n' |
ashu | 01b62f7 | 2015-03-12 15:16:11 -0500 | [diff] [blame] | 1597 | 'Type %prog -h for details)' ) |
| 1598 | |
| 1599 | opts = OptionParser( description=desc, usage=usage ) |
| 1600 | |
| 1601 | addDictOption( opts, TOPOS, TOPODEF, 'topo' ) |
| 1602 | addDictOption( opts, LINKS, LINKDEF, 'link' ) |
| 1603 | |
| 1604 | opts.add_option( '--custom', type='string', default=None, |
| 1605 | help='read custom topo and node params from .py' + |
| 1606 | 'file' ) |
| 1607 | |
| 1608 | self.options, self.args = opts.parse_args() |
| 1609 | # We don't accept extra arguments after the options |
| 1610 | if self.args: |
| 1611 | if len(self.args) > 1: |
| 1612 | opts.print_help() |
| 1613 | exit() |
| 1614 | else: |
| 1615 | self.template_file=self.args[0] |
| 1616 | |
| 1617 | def setCustom( self, name, value ): |
| 1618 | "Set custom parameters for MininetRunner." |
| 1619 | if name in ( 'topos', 'switches', 'hosts', 'controllers' ): |
| 1620 | # Update dictionaries |
| 1621 | param = name.upper() |
| 1622 | globals()[ param ].update( value ) |
| 1623 | elif name == 'validate': |
| 1624 | # Add custom validate function |
| 1625 | self.validate = value |
| 1626 | else: |
| 1627 | # Add or modify global variable or class |
| 1628 | globals()[ name ] = value |
| 1629 | |
| 1630 | def parseCustomFile( self, fileName ): |
| 1631 | "Parse custom file and add params before parsing cmd-line options." |
| 1632 | customs = {} |
| 1633 | if os.path.isfile( fileName ): |
| 1634 | execfile( fileName, customs, customs ) |
| 1635 | for name, val in customs.iteritems(): |
| 1636 | self.setCustom( name, val ) |
| 1637 | else: |
| 1638 | raise Exception( 'could not find custom file: %s' % fileName ) |
| 1639 | |
| 1640 | def importTopo( self ): |
| 1641 | print 'topo='+self.options.topo |
| 1642 | if self.options.topo == 'none': |
| 1643 | return |
| 1644 | self.newTopology() |
| 1645 | topo = buildTopo( TOPOS, self.options.topo ) |
| 1646 | link = customConstructor( LINKS, self.options.link ) |
| 1647 | importNet = Mininet(topo=topo, build=False, link=link) |
| 1648 | importNet.build() |
| 1649 | |
| 1650 | c = self.canvas |
| 1651 | rowIncrement = 100 |
| 1652 | currentY = 100 |
| 1653 | |
| 1654 | # Add switches |
| 1655 | print 'switches:'+str(len(importNet.switches)) |
| 1656 | columnCount = 0 |
| 1657 | for switch in importNet.switches: |
| 1658 | name = switch.name |
| 1659 | self.switchOpts[name] = {} |
| 1660 | self.switchOpts[name]['nodeNum']=self.switchCount |
| 1661 | self.switchOpts[name]['hostname']=name |
| 1662 | self.switchOpts[name]['switchType']='default' |
| 1663 | self.switchOpts[name]['controllers']=[] |
| 1664 | |
| 1665 | x = columnCount*100+100 |
| 1666 | self.addNode('Switch', self.switchCount, |
| 1667 | float(x), float(currentY), name=name) |
| 1668 | icon = self.findWidgetByName(name) |
| 1669 | icon.bind('<Button-3>', self.do_switchPopup ) |
| 1670 | |
| 1671 | if columnCount == 9: |
| 1672 | columnCount = 0 |
| 1673 | currentY = currentY + rowIncrement |
| 1674 | else: |
| 1675 | columnCount =columnCount+1 |
| 1676 | |
| 1677 | currentY = currentY + rowIncrement |
| 1678 | # Add hosts |
| 1679 | print 'hosts:'+str(len(importNet.hosts)) |
| 1680 | columnCount = 0 |
| 1681 | for host in importNet.hosts: |
| 1682 | name = host.name |
| 1683 | self.hostOpts[name] = {'sched':'host'} |
| 1684 | self.hostOpts[name]['nodeNum']=self.hostCount |
| 1685 | self.hostOpts[name]['hostname']=name |
| 1686 | #self.hostOpts[name]['ip']=host.IP() |
| 1687 | |
| 1688 | x = columnCount*100+100 |
| 1689 | self.addNode('Host', self.hostCount, |
| 1690 | float(x), float(currentY), name=name) |
| 1691 | icon = self.findWidgetByName(name) |
| 1692 | icon.bind('<Button-3>', self.do_hostPopup ) |
| 1693 | if columnCount == 9: |
| 1694 | columnCount = 0 |
| 1695 | currentY = currentY + rowIncrement |
| 1696 | else: |
| 1697 | columnCount =columnCount+1 |
| 1698 | |
| 1699 | print 'links:'+str(len(topo.links())) |
| 1700 | #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')] |
| 1701 | for link in topo.links(): |
| 1702 | print str(link) |
| 1703 | srcNode = link[0] |
| 1704 | src = self.findWidgetByName(srcNode) |
| 1705 | sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) |
| 1706 | |
| 1707 | destNode = link[1] |
| 1708 | dest = self.findWidgetByName(destNode) |
| 1709 | dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) |
| 1710 | |
| 1711 | params = topo.linkInfo( srcNode, destNode ) |
| 1712 | print 'Link Parameters='+str(params) |
| 1713 | |
| 1714 | self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, |
| 1715 | fill='blue', tag='link' ) |
| 1716 | c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) |
| 1717 | self.addLink( src, dest, linkopts=params ) |
| 1718 | self.createDataLinkBindings() |
| 1719 | self.link = self.linkWidget = None |
| 1720 | |
| 1721 | importNet.stop() |
| 1722 | |
| 1723 | def miniEditImages(): |
| 1724 | "Create and return images for MiniEdit." |
| 1725 | |
| 1726 | # Image data. Git will be unhappy. However, the alternative |
| 1727 | # is to keep track of separate binary files, which is also |
| 1728 | # unappealing. |
| 1729 | |
| 1730 | return { |
| 1731 | 'Select': BitmapImage( |
| 1732 | file='/usr/include/X11/bitmaps/left_ptr' ), |
| 1733 | |
| 1734 | 'LegacyRouter': PhotoImage( data=r""" |
| 1735 | R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+ |
| 1736 | 3QFq1DmL3wJMmAMzZZW11dnZ2SFrtyNdmTSO6gIZMUKa8gJVqEOH |
| 1737 | zR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq6ymF |
| 1738 | 4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9 |
| 1739 | vwNgvwJZsX+69gsXJQFHjTtjizF0tvHx8VOm9z2V736Dhz2N3QM2 |
| 1740 | acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtgtktjfQFu |
| 1741 | 3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh3 |
| 1742 | 12Gt+VGm/AQIDTmByAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i5 |
| 1743 | 6gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia7DpeggFt2QNP |
| 1744 | m97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er |
| 1745 | /yVVhwJJktPh70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVV |
| 1746 | hQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18zKfP9wwcLAMHCwFF |
| 1747 | iS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh |
| 1748 | 7cve8pG/7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR |
| 1749 | 1RMjNTF3vU2X4TZupwRSolNne4nB+T+L2YGz4zJ/zYe99YGHjRdD |
| 1750 | cT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6Wl |
| 1751 | pW2t7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90 |
| 1752 | uvPz8wIVKBp42SV5zbfT7wtXpStVfwFWrBVvyTt3swFz5kGBv2+1 |
| 1753 | /QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t |
| 1754 | u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T |
| 1755 | 5yH5BAEAAAAALAAAAAAyABgABwj/AAEIHEiQYJY7Qwg9UsTplRIb |
| 1756 | ENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4cHeoI |
| 1757 | abJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPA |
| 1758 | TqEBoRB9gVJsxRlhPwHI0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o |
| 1759 | 9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkSrtwADuxC |
| 1760 | G/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmD |
| 1761 | jhTMmseoKQIFDx7RoxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7 |
| 1762 | VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq9dGvv09RHFhc |
| 1763 | IUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p |
| 1764 | 9HEUFhxgMSAvjbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJ |
| 1765 | ifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CFoVggmEgCyRf01WcF |
| 1766 | CYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57Ag |
| 1767 | yZckpKKPGFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemM |
| 1768 | IQggeaJywSQ/wgHOAmJskQEfWqBlFBEH1P/QaGY3QOpDZXA2+A6m |
| 1769 | 7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlB |
| 1770 | pZdiisd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtCh |
| 1771 | RmVPNWgpr+Be+Nc9icARww9TkIEuDAsQ0O7DzGIQzD2QdDEJHTsI |
| 1772 | AROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB |
| 1773 | xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE |
| 1774 | 42Q9jtFIp8z0Dy1jQMA1AGziz9VoW7310V0znYDTGMQgwUDXLDBO |
| 1775 | 2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOMLQkc |
| 1776 | jvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZ |
| 1777 | MDHKCTwI8EcQFHBBAAFcgGPLHwLwcMIo12Qxu0ABAQA7 |
| 1778 | """), |
| 1779 | |
| 1780 | 'Host': PhotoImage( data=r""" |
| 1781 | R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M |
| 1782 | mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m |
| 1783 | Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A |
| 1784 | M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM |
| 1785 | AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz |
| 1786 | /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ |
| 1787 | zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ |
| 1788 | mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz |
| 1789 | ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ |
| 1790 | M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ |
| 1791 | AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA |
| 1792 | /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM |
| 1793 | zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm |
| 1794 | mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA |
| 1795 | ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM |
| 1796 | MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm |
| 1797 | AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A |
| 1798 | ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI |
| 1799 | AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA |
| 1800 | RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA |
| 1801 | ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw |
| 1802 | BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2 |
| 1803 | HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO |
| 1804 | p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/ |
| 1805 | C8cSBBAQADs= |
| 1806 | """ ), |
| 1807 | |
| 1808 | 'NetLink': PhotoImage( data=r""" |
| 1809 | R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M |
| 1810 | mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m |
| 1811 | Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A |
| 1812 | M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM |
| 1813 | AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz |
| 1814 | /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ |
| 1815 | zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ |
| 1816 | mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz |
| 1817 | ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ |
| 1818 | M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ |
| 1819 | AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA |
| 1820 | /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM |
| 1821 | zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm |
| 1822 | mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA |
| 1823 | ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM |
| 1824 | MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm |
| 1825 | AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A |
| 1826 | ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI |
| 1827 | AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA |
| 1828 | RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA |
| 1829 | ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG |
| 1830 | Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy |
| 1831 | lBmxI8mSNknm1Dnx5sCAADs= |
| 1832 | """ ) |
| 1833 | } |
| 1834 | |
| 1835 | def addDictOption( opts, choicesDict, default, name, helpStr=None ): |
| 1836 | """Convenience function to add choices dicts to OptionParser. |
| 1837 | opts: OptionParser instance |
| 1838 | choicesDict: dictionary of valid choices, must include default |
| 1839 | default: default choice key |
| 1840 | name: long option name |
| 1841 | help: string""" |
| 1842 | if default not in choicesDict: |
| 1843 | raise Exception( 'Invalid default %s for choices dict: %s' % |
| 1844 | ( default, name ) ) |
| 1845 | if not helpStr: |
| 1846 | helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + |
| 1847 | '[,param=value...]' ) |
| 1848 | opts.add_option( '--' + name, |
| 1849 | type='string', |
| 1850 | default = default, |
| 1851 | help = helpStr ) |
| 1852 | |
| 1853 | if __name__ == '__main__': |
| 1854 | setLogLevel( 'info' ) |
| 1855 | app = MiniEdit() |
| 1856 | """ import topology if specified """ |
| 1857 | app.parseArgs() |
| 1858 | app.importTopo() |
| 1859 | |
| 1860 | global isHostPopup |
| 1861 | global isRouterPopup |
| 1862 | global isLinkPopup |
| 1863 | |
| 1864 | app.mainloop() |