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