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 | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 6 | Bob Lantz, April 2010 |
| 7 | Gregory Gee, July 2013 |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 8 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 9 | Controller icon from http://semlabs.co.uk/ |
| 10 | OpenFlow icon from https://www.opennetworking.org/ |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 11 | """ |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 12 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 13 | MINIEDIT_VERSION = '2.2.0.1' |
| 14 | |
| 15 | from optparse import OptionParser |
| 16 | from Tkinter import * |
| 17 | from ttk import Notebook |
| 18 | from tkMessageBox import showinfo, showerror, showwarning |
| 19 | from subprocess import call |
| 20 | import tkFont |
| 21 | import csv |
| 22 | import tkFileDialog |
| 23 | import tkSimpleDialog |
| 24 | import re |
| 25 | import json |
| 26 | from distutils.version import StrictVersion |
| 27 | import os |
| 28 | import sys |
| 29 | from functools import partial |
| 30 | |
| 31 | if 'PYTHONPATH' in os.environ: |
| 32 | sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 33 | |
| 34 | # someday: from ttk import * |
| 35 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 36 | from mininet.log import info, error, debug, output, setLogLevel |
| 37 | from mininet.net import Mininet, VERSION |
| 38 | from mininet.util import ipStr, netParse, ipAdd, quietRun |
| 39 | from mininet.util import buildTopo |
| 40 | from mininet.util import custom, customConstructor |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 41 | from mininet.term import makeTerm, cleanUpScreens |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 42 | from mininet.node import Controller, RemoteController, NOX, OVSController |
| 43 | from mininet.node import CPULimitedHost, Host, Node |
| 44 | from mininet.node import OVSKernelSwitch, OVSSwitch, UserSwitch |
| 45 | from mininet.link import TCLink, Intf, Link |
| 46 | from mininet.cli import CLI |
| 47 | from mininet.moduledeps import moduleDeps, pathCheck |
| 48 | from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo |
| 49 | from mininet.topolib import TreeTopo |
| 50 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 51 | print 'MiniCCNxEdit running...' #+VERSION |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 52 | MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION) |
| 53 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): |
| 54 | from mininet.node import IVSSwitch |
| 55 | |
| 56 | TOPODEF = 'none' |
| 57 | TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), |
| 58 | 'linear': LinearTopo, |
| 59 | 'reversed': SingleSwitchReversedTopo, |
| 60 | 'single': SingleSwitchTopo, |
| 61 | 'none': None, |
| 62 | 'tree': TreeTopo } |
| 63 | LINKDEF = 'default' |
| 64 | LINKS = { 'default': Link, |
| 65 | 'tc': TCLink } |
| 66 | HOSTDEF = 'proc' |
| 67 | HOSTS = { 'proc': Host, |
| 68 | 'rt': custom( CPULimitedHost, sched='rt' ), |
| 69 | 'cfs': custom( CPULimitedHost, sched='cfs' ) } |
| 70 | |
| 71 | class LegacyRouter( Node ): |
| 72 | |
| 73 | def __init__( self, name, inNamespace=True, **params ): |
| 74 | Node.__init__( self, name, inNamespace, **params ) |
| 75 | |
| 76 | def config( self, **_params ): |
| 77 | if self.intfs: |
| 78 | self.setParam( _params, 'setIP', ip='0.0.0.0' ) |
| 79 | r = Node.config( self, **_params ) |
| 80 | self.cmd('sysctl -w net.ipv4.ip_forward=1') |
| 81 | return r |
| 82 | |
| 83 | class CustomDialog(object): |
| 84 | |
| 85 | # TODO: Fix button placement and Title and window focus lock |
| 86 | def __init__(self, master, title): |
| 87 | self.top=Toplevel(master) |
| 88 | |
| 89 | self.bodyFrame = Frame(self.top) |
| 90 | self.bodyFrame.grid(row=0, column=0, sticky='nswe') |
| 91 | self.body(self.bodyFrame) |
| 92 | |
| 93 | #return self.b # initial focus |
| 94 | buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey') |
| 95 | buttonFrame.grid(row=1 , column=0, sticky='nswe') |
| 96 | |
| 97 | okButton = Button(buttonFrame, width=8, text='OK', relief='groove', |
| 98 | bd=4, command=self.okAction) |
| 99 | okButton.grid(row=0, column=0, sticky=E) |
| 100 | |
| 101 | canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove', |
| 102 | bd=4, command=self.cancelAction) |
| 103 | canlceButton.grid(row=0, column=1, sticky=W) |
| 104 | |
| 105 | def body(self, master): |
| 106 | self.rootFrame = master |
| 107 | |
| 108 | def apply(self): |
| 109 | self.top.destroy() |
| 110 | |
| 111 | def cancelAction(self): |
| 112 | self.top.destroy() |
| 113 | |
| 114 | def okAction(self): |
| 115 | self.apply() |
| 116 | self.top.destroy() |
| 117 | |
| 118 | class HostDialog(CustomDialog): |
| 119 | |
| 120 | def __init__(self, master, title, prefDefaults): |
| 121 | |
| 122 | self.prefValues = prefDefaults |
| 123 | self.result = None |
| 124 | |
| 125 | CustomDialog.__init__(self, master, title) |
| 126 | |
| 127 | def body(self, master): |
| 128 | self.rootFrame = master |
| 129 | n = Notebook(self.rootFrame) |
| 130 | self.propFrame = Frame(n) |
| 131 | self.vlanFrame = Frame(n) |
| 132 | self.interfaceFrame = Frame(n) |
| 133 | self.mountFrame = Frame(n) |
| 134 | n.add(self.propFrame, text='Properties') |
| 135 | n.add(self.vlanFrame, text='VLAN Interfaces') |
| 136 | n.add(self.interfaceFrame, text='External Interfaces') |
| 137 | n.add(self.mountFrame, text='Private Directories') |
| 138 | n.pack() |
| 139 | |
| 140 | ### TAB 1 |
| 141 | # Field for Hostname |
| 142 | Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E) |
| 143 | self.hostnameEntry = Entry(self.propFrame) |
| 144 | self.hostnameEntry.grid(row=0, column=1) |
| 145 | if 'hostname' in self.prefValues: |
| 146 | self.hostnameEntry.insert(0, self.prefValues['hostname']) |
| 147 | |
| 148 | # Field for Switch IP |
| 149 | Label(self.propFrame, text="IP Address:").grid(row=1, sticky=E) |
| 150 | self.ipEntry = Entry(self.propFrame) |
| 151 | self.ipEntry.grid(row=1, column=1) |
| 152 | if 'ip' in self.prefValues: |
| 153 | self.ipEntry.insert(0, self.prefValues['ip']) |
| 154 | |
| 155 | # Field for default route |
| 156 | Label(self.propFrame, text="Default Route:").grid(row=2, sticky=E) |
| 157 | self.routeEntry = Entry(self.propFrame) |
| 158 | self.routeEntry.grid(row=2, column=1) |
| 159 | if 'defaultRoute' in self.prefValues: |
| 160 | self.routeEntry.insert(0, self.prefValues['defaultRoute']) |
| 161 | |
| 162 | # Field for CPU |
| 163 | Label(self.propFrame, text="Amount CPU:").grid(row=3, sticky=E) |
| 164 | self.cpuEntry = Entry(self.propFrame) |
| 165 | self.cpuEntry.grid(row=3, column=1) |
| 166 | if 'cpu' in self.prefValues: |
| 167 | self.cpuEntry.insert(0, str(self.prefValues['cpu'])) |
| 168 | # Selection of Scheduler |
| 169 | if 'sched' in self.prefValues: |
| 170 | sched = self.prefValues['sched'] |
| 171 | else: |
| 172 | sched = 'host' |
| 173 | self.schedVar = StringVar(self.propFrame) |
| 174 | self.schedOption = OptionMenu(self.propFrame, self.schedVar, "host", "cfs", "rt") |
| 175 | self.schedOption.grid(row=3, column=2, sticky=W) |
| 176 | self.schedVar.set(sched) |
| 177 | |
| 178 | # Selection of Cores |
| 179 | Label(self.propFrame, text="Cores:").grid(row=4, sticky=E) |
| 180 | self.coreEntry = Entry(self.propFrame) |
| 181 | self.coreEntry.grid(row=4, column=1) |
| 182 | if 'cores' in self.prefValues: |
| 183 | self.coreEntry.insert(1, self.prefValues['cores']) |
| 184 | |
| 185 | # Start command |
| 186 | Label(self.propFrame, text="Start Command:").grid(row=5, sticky=E) |
| 187 | self.startEntry = Entry(self.propFrame) |
| 188 | self.startEntry.grid(row=5, column=1, sticky='nswe', columnspan=3) |
| 189 | if 'startCommand' in self.prefValues: |
| 190 | self.startEntry.insert(0, str(self.prefValues['startCommand'])) |
| 191 | # Stop command |
| 192 | Label(self.propFrame, text="Stop Command:").grid(row=6, sticky=E) |
| 193 | self.stopEntry = Entry(self.propFrame) |
| 194 | self.stopEntry.grid(row=6, column=1, sticky='nswe', columnspan=3) |
| 195 | if 'stopCommand' in self.prefValues: |
| 196 | self.stopEntry.insert(0, str(self.prefValues['stopCommand'])) |
| 197 | |
| 198 | ### TAB 2 |
| 199 | # External Interfaces |
| 200 | self.externalInterfaces = 0 |
| 201 | Label(self.interfaceFrame, text="External Interface:").grid(row=0, column=0, sticky=E) |
| 202 | self.b = Button( self.interfaceFrame, text='Add', command=self.addInterface) |
| 203 | self.b.grid(row=0, column=1) |
| 204 | |
| 205 | self.interfaceFrame = VerticalScrolledTable(self.interfaceFrame, rows=0, columns=1, title='External Interfaces') |
| 206 | self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) |
| 207 | self.tableFrame = self.interfaceFrame.interior |
| 208 | self.tableFrame.addRow(value=['Interface Name'], readonly=True) |
| 209 | |
| 210 | # Add defined interfaces |
| 211 | externalInterfaces = [] |
| 212 | if 'externalInterfaces' in self.prefValues: |
| 213 | externalInterfaces = self.prefValues['externalInterfaces'] |
| 214 | |
| 215 | for externalInterface in externalInterfaces: |
| 216 | self.tableFrame.addRow(value=[externalInterface]) |
| 217 | |
| 218 | ### TAB 3 |
| 219 | # VLAN Interfaces |
| 220 | self.vlanInterfaces = 0 |
| 221 | Label(self.vlanFrame, text="VLAN Interface:").grid(row=0, column=0, sticky=E) |
| 222 | self.vlanButton = Button( self.vlanFrame, text='Add', command=self.addVlanInterface) |
| 223 | self.vlanButton.grid(row=0, column=1) |
| 224 | |
| 225 | self.vlanFrame = VerticalScrolledTable(self.vlanFrame, rows=0, columns=2, title='VLAN Interfaces') |
| 226 | self.vlanFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) |
| 227 | self.vlanTableFrame = self.vlanFrame.interior |
| 228 | self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True) |
| 229 | |
| 230 | vlanInterfaces = [] |
| 231 | if 'vlanInterfaces' in self.prefValues: |
| 232 | vlanInterfaces = self.prefValues['vlanInterfaces'] |
| 233 | for vlanInterface in vlanInterfaces: |
| 234 | self.vlanTableFrame.addRow(value=vlanInterface) |
| 235 | |
| 236 | ### TAB 4 |
| 237 | # Private Directories |
| 238 | self.privateDirectories = 0 |
| 239 | Label(self.mountFrame, text="Private Directory:").grid(row=0, column=0, sticky=E) |
| 240 | self.mountButton = Button( self.mountFrame, text='Add', command=self.addDirectory) |
| 241 | self.mountButton.grid(row=0, column=1) |
| 242 | |
| 243 | self.mountFrame = VerticalScrolledTable(self.mountFrame, rows=0, columns=2, title='Directories') |
| 244 | self.mountFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) |
| 245 | self.mountTableFrame = self.mountFrame.interior |
| 246 | self.mountTableFrame.addRow(value=['Mount','Persistent Directory'], readonly=True) |
| 247 | |
| 248 | directoryList = [] |
| 249 | if 'privateDirectory' in self.prefValues: |
| 250 | directoryList = self.prefValues['privateDirectory'] |
| 251 | for privateDir in directoryList: |
| 252 | if isinstance( privateDir, tuple ): |
| 253 | self.mountTableFrame.addRow(value=privateDir) |
| 254 | else: |
| 255 | self.mountTableFrame.addRow(value=[privateDir,'']) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 256 | |
| 257 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 258 | def addDirectory( self ): |
| 259 | self.mountTableFrame.addRow() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 260 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 261 | def addVlanInterface( self ): |
| 262 | self.vlanTableFrame.addRow() |
| 263 | |
| 264 | def addInterface( self ): |
| 265 | self.tableFrame.addRow() |
| 266 | |
| 267 | def apply(self): |
| 268 | externalInterfaces = [] |
| 269 | for row in range(self.tableFrame.rows): |
| 270 | if (len(self.tableFrame.get(row, 0)) > 0 and |
| 271 | row > 0): |
| 272 | externalInterfaces.append(self.tableFrame.get(row, 0)) |
| 273 | vlanInterfaces = [] |
| 274 | for row in range(self.vlanTableFrame.rows): |
| 275 | if (len(self.vlanTableFrame.get(row, 0)) > 0 and |
| 276 | len(self.vlanTableFrame.get(row, 1)) > 0 and |
| 277 | row > 0): |
| 278 | vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)]) |
| 279 | privateDirectories = [] |
| 280 | for row in range(self.mountTableFrame.rows): |
| 281 | if (len(self.mountTableFrame.get(row, 0)) > 0 and row > 0): |
| 282 | if(len(self.mountTableFrame.get(row, 1)) > 0): |
| 283 | privateDirectories.append((self.mountTableFrame.get(row, 0), self.mountTableFrame.get(row, 1))) |
| 284 | else: |
| 285 | privateDirectories.append(self.mountTableFrame.get(row, 0)) |
| 286 | |
| 287 | results = {'cpu': self.cpuEntry.get(), |
| 288 | 'cores':self.coreEntry.get(), |
| 289 | 'sched':self.schedVar.get(), |
| 290 | 'hostname':self.hostnameEntry.get(), |
| 291 | 'ip':self.ipEntry.get(), |
| 292 | 'defaultRoute':self.routeEntry.get(), |
| 293 | 'startCommand':self.startEntry.get(), |
| 294 | 'stopCommand':self.stopEntry.get(), |
| 295 | 'privateDirectory':privateDirectories, |
| 296 | 'externalInterfaces':externalInterfaces, |
| 297 | 'vlanInterfaces':vlanInterfaces} |
| 298 | self.result = results |
| 299 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 300 | class VerticalScrolledTable(LabelFrame): |
| 301 | """A pure Tkinter scrollable frame that actually works! |
| 302 | |
| 303 | * Use the 'interior' attribute to place widgets inside the scrollable frame |
| 304 | * Construct and pack/place/grid normally |
| 305 | * This frame only allows vertical scrolling |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 306 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 307 | """ |
| 308 | def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw): |
| 309 | LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 310 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 311 | # create a canvas object and a vertical scrollbar for scrolling it |
| 312 | vscrollbar = Scrollbar(self, orient=VERTICAL) |
| 313 | vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) |
| 314 | canvas = Canvas(self, bd=0, highlightthickness=0, |
| 315 | yscrollcommand=vscrollbar.set) |
| 316 | canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) |
| 317 | vscrollbar.config(command=canvas.yview) |
| 318 | |
| 319 | # reset the view |
| 320 | canvas.xview_moveto(0) |
| 321 | canvas.yview_moveto(0) |
| 322 | |
| 323 | # create a frame inside the canvas which will be scrolled with it |
| 324 | self.interior = interior = TableFrame(canvas, rows=rows, columns=columns) |
| 325 | interior_id = canvas.create_window(0, 0, window=interior, |
| 326 | anchor=NW) |
| 327 | |
| 328 | # track changes to the canvas and frame width and sync them, |
| 329 | # also updating the scrollbar |
| 330 | def _configure_interior(event): |
| 331 | # update the scrollbars to match the size of the inner frame |
| 332 | size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) |
| 333 | canvas.config(scrollregion="0 0 %s %s" % size) |
| 334 | if interior.winfo_reqwidth() != canvas.winfo_width(): |
| 335 | # update the canvas's width to fit the inner frame |
| 336 | canvas.config(width=interior.winfo_reqwidth()) |
| 337 | interior.bind('<Configure>', _configure_interior) |
| 338 | |
| 339 | def _configure_canvas(event): |
| 340 | if interior.winfo_reqwidth() != canvas.winfo_width(): |
| 341 | # update the inner frame's width to fill the canvas |
| 342 | canvas.itemconfigure(interior_id, width=canvas.winfo_width()) |
| 343 | canvas.bind('<Configure>', _configure_canvas) |
| 344 | |
| 345 | return |
| 346 | |
| 347 | class TableFrame(Frame): |
| 348 | def __init__(self, parent, rows=2, columns=2): |
| 349 | |
| 350 | Frame.__init__(self, parent, background="black") |
| 351 | self._widgets = [] |
| 352 | self.rows = rows |
| 353 | self.columns = columns |
| 354 | for row in range(rows): |
| 355 | current_row = [] |
| 356 | for column in range(columns): |
| 357 | label = Entry(self, borderwidth=0) |
| 358 | label.grid(row=row, column=column, sticky="wens", padx=1, pady=1) |
| 359 | current_row.append(label) |
| 360 | self._widgets.append(current_row) |
| 361 | |
| 362 | def set(self, row, column, value): |
| 363 | widget = self._widgets[row][column] |
| 364 | widget.insert(0, value) |
| 365 | |
| 366 | def get(self, row, column): |
| 367 | widget = self._widgets[row][column] |
| 368 | return widget.get() |
| 369 | |
| 370 | def addRow( self, value=None, readonly=False ): |
| 371 | #print "Adding row " + str(self.rows +1) |
| 372 | current_row = [] |
| 373 | for column in range(self.columns): |
| 374 | label = Entry(self, borderwidth=0) |
| 375 | label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1) |
| 376 | if value is not None: |
| 377 | label.insert(0, value[column]) |
| 378 | if (readonly == True): |
| 379 | label.configure(state='readonly') |
| 380 | current_row.append(label) |
| 381 | self._widgets.append(current_row) |
| 382 | self.update_idletasks() |
| 383 | self.rows += 1 |
| 384 | |
| 385 | class LinkDialog(tkSimpleDialog.Dialog): |
| 386 | |
| 387 | def __init__(self, parent, title, linkDefaults): |
| 388 | |
| 389 | self.linkValues = linkDefaults |
| 390 | |
| 391 | tkSimpleDialog.Dialog.__init__(self, parent, title) |
| 392 | |
| 393 | def body(self, master): |
| 394 | |
| 395 | self.var = StringVar(master) |
| 396 | Label(master, text="Bandwidth:").grid(row=0, sticky=E) |
| 397 | self.e1 = Entry(master) |
| 398 | self.e1.grid(row=0, column=1) |
| 399 | Label(master, text="Mbit").grid(row=0, column=2, sticky=W) |
| 400 | if 'bw' in self.linkValues: |
| 401 | self.e1.insert(0,str(self.linkValues['bw'])) |
| 402 | |
| 403 | Label(master, text="Delay:").grid(row=1, sticky=E) |
| 404 | self.e2 = Entry(master) |
| 405 | self.e2.grid(row=1, column=1) |
| 406 | if 'delay' in self.linkValues: |
| 407 | self.e2.insert(0, self.linkValues['delay']) |
| 408 | |
| 409 | Label(master, text="Loss:").grid(row=2, sticky=E) |
| 410 | self.e3 = Entry(master) |
| 411 | self.e3.grid(row=2, column=1) |
| 412 | Label(master, text="%").grid(row=2, column=2, sticky=W) |
| 413 | if 'loss' in self.linkValues: |
| 414 | self.e3.insert(0, str(self.linkValues['loss'])) |
| 415 | |
| 416 | Label(master, text="Max Queue size:").grid(row=3, sticky=E) |
| 417 | self.e4 = Entry(master) |
| 418 | self.e4.grid(row=3, column=1) |
| 419 | if 'max_queue_size' in self.linkValues: |
| 420 | self.e4.insert(0, str(self.linkValues['max_queue_size'])) |
| 421 | |
| 422 | Label(master, text="Jitter:").grid(row=4, sticky=E) |
| 423 | self.e5 = Entry(master) |
| 424 | self.e5.grid(row=4, column=1) |
| 425 | if 'jitter' in self.linkValues: |
| 426 | self.e5.insert(0, self.linkValues['jitter']) |
| 427 | |
| 428 | Label(master, text="Speedup:").grid(row=5, sticky=E) |
| 429 | self.e6 = Entry(master) |
| 430 | self.e6.grid(row=5, column=1) |
| 431 | if 'speedup' in self.linkValues: |
| 432 | self.e6.insert(0, str(self.linkValues['speedup'])) |
| 433 | |
| 434 | return self.e1 # initial focus |
| 435 | |
| 436 | def apply(self): |
| 437 | self.result = {} |
| 438 | if (len(self.e1.get()) > 0): |
| 439 | self.result['bw'] = int(self.e1.get()) |
| 440 | if (len(self.e2.get()) > 0): |
| 441 | self.result['delay'] = self.e2.get() |
| 442 | if (len(self.e3.get()) > 0): |
| 443 | self.result['loss'] = int(self.e3.get()) |
| 444 | if (len(self.e4.get()) > 0): |
| 445 | self.result['max_queue_size'] = int(self.e4.get()) |
| 446 | if (len(self.e5.get()) > 0): |
| 447 | self.result['jitter'] = self.e5.get() |
| 448 | if (len(self.e6.get()) > 0): |
| 449 | self.result['speedup'] = int(self.e6.get()) |
| 450 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 451 | class ToolTip(object): |
| 452 | |
| 453 | def __init__(self, widget): |
| 454 | self.widget = widget |
| 455 | self.tipwindow = None |
| 456 | self.id = None |
| 457 | self.x = self.y = 0 |
| 458 | |
| 459 | def showtip(self, text): |
| 460 | "Display text in tooltip window" |
| 461 | self.text = text |
| 462 | if self.tipwindow or not self.text: |
| 463 | return |
| 464 | x, y, cx, cy = self.widget.bbox("insert") |
| 465 | x = x + self.widget.winfo_rootx() + 27 |
| 466 | y = y + cy + self.widget.winfo_rooty() +27 |
| 467 | self.tipwindow = tw = Toplevel(self.widget) |
| 468 | tw.wm_overrideredirect(1) |
| 469 | tw.wm_geometry("+%d+%d" % (x, y)) |
| 470 | try: |
| 471 | # For Mac OS |
| 472 | tw.tk.call("::tk::unsupported::MacWindowStyle", |
| 473 | "style", tw._w, |
| 474 | "help", "noActivates") |
| 475 | except TclError: |
| 476 | pass |
| 477 | label = Label(tw, text=self.text, justify=LEFT, |
| 478 | background="#ffffe0", relief=SOLID, borderwidth=1, |
| 479 | font=("tahoma", "8", "normal")) |
| 480 | label.pack(ipadx=1) |
| 481 | |
| 482 | def hidetip(self): |
| 483 | tw = self.tipwindow |
| 484 | self.tipwindow = None |
| 485 | if tw: |
| 486 | tw.destroy() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 487 | |
| 488 | class MiniEdit( Frame ): |
| 489 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 490 | "A simple network editor for MiniCCNx." |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 491 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 492 | def __init__( self, parent=None, cheight=600, cwidth=1000, template_file='miniccnx.conf' ): |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 493 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 494 | #self.defaultIpBase='10.0.0.0/8' |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 495 | |
| 496 | self.nflowDefaults = {'nflowTarget':'', |
| 497 | 'nflowTimeout':'600', |
| 498 | 'nflowAddId':'0'} |
| 499 | self.sflowDefaults = {'sflowTarget':'', |
| 500 | 'sflowSampling':'400', |
| 501 | 'sflowHeader':'128', |
| 502 | 'sflowPolling':'30'} |
| 503 | |
| 504 | self.appPrefs={ |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 505 | #"ipBase": self.defaultIpBase, |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 506 | "startCLI": "0", |
| 507 | "terminalType": 'xterm', |
| 508 | "switchType": 'ovs', |
| 509 | "dpctl": '', |
| 510 | 'sflow':self.sflowDefaults, |
| 511 | 'netflow':self.nflowDefaults, |
| 512 | 'openFlowVersions':{'ovsOf10':'1', |
| 513 | 'ovsOf11':'0', |
| 514 | 'ovsOf12':'0', |
| 515 | 'ovsOf13':'0'} |
| 516 | |
| 517 | } |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 518 | |
| 519 | self.template_file = template_file |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 520 | |
| 521 | Frame.__init__( self, parent ) |
| 522 | self.action = None |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 523 | self.appName = 'MiniccnxEdit' |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 524 | self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 525 | |
| 526 | # Style |
| 527 | self.font = ( 'Geneva', 9 ) |
| 528 | self.smallFont = ( 'Geneva', 7 ) |
| 529 | self.bg = 'white' |
| 530 | |
| 531 | # Title |
| 532 | self.top = self.winfo_toplevel() |
| 533 | self.top.title( self.appName ) |
| 534 | |
| 535 | # Menu bar |
| 536 | self.createMenubar() |
| 537 | |
| 538 | # Editing canvas |
| 539 | self.cheight, self.cwidth = cheight, cwidth |
| 540 | self.cframe, self.canvas = self.createCanvas() |
| 541 | |
| 542 | # Toolbar |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 543 | self.controllers = {} |
| 544 | |
| 545 | # Toolbar |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 546 | self.images = miniEditImages() |
| 547 | self.buttons = {} |
| 548 | self.active = None |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 549 | self.tools = ( 'Select', 'Host', 'LegacyRouter', 'NetLink' ) |
| 550 | self.customColors = { 'LegacyRouter': 'darkGreen', 'Host': 'blue' } |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 551 | self.toolbar = self.createToolbar() |
| 552 | |
| 553 | # Layout |
| 554 | self.toolbar.grid( column=0, row=0, sticky='nsew') |
| 555 | self.cframe.grid( column=1, row=0 ) |
| 556 | self.columnconfigure( 1, weight=1 ) |
| 557 | self.rowconfigure( 0, weight=1 ) |
| 558 | self.pack( expand=True, fill='both' ) |
| 559 | |
| 560 | # About box |
| 561 | self.aboutBox = None |
| 562 | |
| 563 | # Initialize node data |
| 564 | self.nodeBindings = self.createNodeBindings() |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 565 | self.nodePrefixes = { 'LegacyRouter': 'r', 'Host': 'h'} |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 566 | self.widgetToItem = {} |
| 567 | self.itemToWidget = {} |
| 568 | |
| 569 | # Initialize link tool |
| 570 | self.link = self.linkWidget = None |
| 571 | |
| 572 | # Selection support |
| 573 | self.selection = None |
| 574 | |
| 575 | # Keyboard bindings |
| 576 | self.bind( '<Control-q>', lambda event: self.quit() ) |
| 577 | self.bind( '<KeyPress-Delete>', self.deleteSelection ) |
| 578 | self.bind( '<KeyPress-BackSpace>', self.deleteSelection ) |
| 579 | self.focus() |
| 580 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 581 | #Mouse bindings |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 582 | self.bind( '<Button-1>', lambda event: self.clearPopups ) |
| 583 | |
| 584 | self.hostPopup = Menu(self.top, tearoff=0) |
| 585 | self.hostPopup.add_command(label='Host Options', font=self.font) |
| 586 | self.hostPopup.add_separator() |
| 587 | self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails ) |
| 588 | |
| 589 | self.legacyRouterPopup = Menu(self.top, tearoff=0) |
| 590 | self.legacyRouterPopup.add_command(label='Router Options', font=self.font) |
| 591 | |
| 592 | self.linkPopup = Menu(self.top, tearoff=0) |
| 593 | self.linkPopup.add_command(label='Link Options', font=self.font) |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 594 | #self.linkPopup.add_separator() |
| 595 | #self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 596 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 597 | # Event handling initalization |
| 598 | self.linkx = self.linky = self.linkItem = None |
| 599 | self.lastSelection = None |
| 600 | |
| 601 | # Model initialization |
| 602 | self.links = {} |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 603 | self.hostOpts = {} |
| 604 | self.switchOpts = {} |
| 605 | self.hostCount = 0 |
| 606 | self.switchCount = 0 |
| 607 | self.controllerCount = 0 |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 608 | self.net = None |
| 609 | |
| 610 | # Close window gracefully |
| 611 | Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit ) |
| 612 | |
| 613 | def quit( self ): |
| 614 | "Stop our network, if any, then quit." |
| 615 | self.stop() |
| 616 | Frame.quit( self ) |
| 617 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 618 | def createMenubar( self ): # MODIFICADO - OK |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 619 | "Create our menu bar." |
| 620 | |
| 621 | font = self.font |
| 622 | |
| 623 | mbar = Menu( self.top, font=font ) |
| 624 | self.top.configure( menu=mbar ) |
| 625 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 626 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 627 | fileMenu = Menu( mbar, tearoff=False ) |
| 628 | mbar.add_cascade( label="File", font=font, menu=fileMenu ) |
| 629 | fileMenu.add_command( label="New", font=font, command=self.newTopology ) |
| 630 | fileMenu.add_command( label="Open", font=font, command=self.loadTopology ) |
| 631 | fileMenu.add_command( label="Save", font=font, command=self.saveTopology ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 632 | #fileMenu.add_command( label="Export Level 2 Script", font=font, command=self.exportScript ) |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 633 | fileMenu.add_separator() |
| 634 | fileMenu.add_command( label='Quit', command=self.quit, font=font ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 635 | |
| 636 | #editMenu.add_command( label="Preferences", font=font, command=self.prefDetails) |
| 637 | |
| 638 | #runMenu = Menu( mbar, tearoff=False ) |
| 639 | #mbar.add_cascade( label="Run", font=font, menu=runMenu ) |
| 640 | #runMenu.add_command( label="Run", font=font, command=self.doRun ) |
| 641 | #runMenu.add_command( label="Stop", font=font, command=self.doStop ) |
| 642 | #fileMenu.add_separator() |
| 643 | #runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow ) |
| 644 | #runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 645 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 646 | editMenu = Menu( mbar, tearoff=False ) |
| 647 | mbar.add_cascade( label="Edit", font=font, menu=editMenu ) |
| 648 | editMenu.add_command( label="Cut", font=font, |
| 649 | command=lambda: self.deleteSelection( None ) ) |
| 650 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 651 | # Application menu |
| 652 | appMenu = Menu( mbar, tearoff=False ) |
| 653 | mbar.add_cascade( label=self.appName, font=font, menu=appMenu ) |
| 654 | appMenu.add_command( label='About Mini-CCNx', command=self.about, |
| 655 | font=font) |
| 656 | #appMenu.add_separator() |
| 657 | #appMenu.add_command( label='Quit', command=self.quit, font=font ) |
| 658 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 659 | # Canvas - TUDO IGUAL - OK |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 660 | |
| 661 | def createCanvas( self ): |
| 662 | "Create and return our scrolling canvas frame." |
| 663 | f = Frame( self ) |
| 664 | |
| 665 | canvas = Canvas( f, width=self.cwidth, height=self.cheight, |
| 666 | bg=self.bg ) |
| 667 | |
| 668 | # Scroll bars |
| 669 | xbar = Scrollbar( f, orient='horizontal', command=canvas.xview ) |
| 670 | ybar = Scrollbar( f, orient='vertical', command=canvas.yview ) |
| 671 | canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set ) |
| 672 | |
| 673 | # Resize box |
| 674 | resize = Label( f, bg='white' ) |
| 675 | |
| 676 | # Layout |
| 677 | canvas.grid( row=0, column=1, sticky='nsew') |
| 678 | ybar.grid( row=0, column=2, sticky='ns') |
| 679 | xbar.grid( row=1, column=1, sticky='ew' ) |
| 680 | resize.grid( row=1, column=2, sticky='nsew' ) |
| 681 | |
| 682 | # Resize behavior |
| 683 | f.rowconfigure( 0, weight=1 ) |
| 684 | f.columnconfigure( 1, weight=1 ) |
| 685 | f.grid( row=0, column=0, sticky='nsew' ) |
| 686 | f.bind( '<Configure>', lambda event: self.updateScrollRegion() ) |
| 687 | |
| 688 | # Mouse bindings |
| 689 | canvas.bind( '<ButtonPress-1>', self.clickCanvas ) |
| 690 | canvas.bind( '<B1-Motion>', self.dragCanvas ) |
| 691 | canvas.bind( '<ButtonRelease-1>', self.releaseCanvas ) |
| 692 | |
| 693 | return f, canvas |
| 694 | |
| 695 | def updateScrollRegion( self ): |
| 696 | "Update canvas scroll region to hold everything." |
| 697 | bbox = self.canvas.bbox( 'all' ) |
| 698 | if bbox is not None: |
| 699 | self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], |
| 700 | bbox[ 3 ] ) ) |
| 701 | |
| 702 | def canvasx( self, x_root ): |
| 703 | "Convert root x coordinate to canvas coordinate." |
| 704 | c = self.canvas |
| 705 | return c.canvasx( x_root ) - c.winfo_rootx() |
| 706 | |
| 707 | def canvasy( self, y_root ): |
| 708 | "Convert root y coordinate to canvas coordinate." |
| 709 | c = self.canvas |
| 710 | return c.canvasy( y_root ) - c.winfo_rooty() |
| 711 | |
| 712 | # Toolbar |
| 713 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 714 | def activate( self, toolName ): #IGUAL - OK |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 715 | "Activate a tool and press its button." |
| 716 | # Adjust button appearance |
| 717 | if self.active: |
| 718 | self.buttons[ self.active ].configure( relief='raised' ) |
| 719 | self.buttons[ toolName ].configure( relief='sunken' ) |
| 720 | # Activate dynamic bindings |
| 721 | self.active = toolName |
| 722 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 723 | |
| 724 | def createToolTip(self, widget, text): #NOVA - CRIA HINTS E TIPS |
| 725 | toolTip = ToolTip(widget) |
| 726 | def enter(event): |
| 727 | toolTip.showtip(text) |
| 728 | def leave(event): |
| 729 | toolTip.hidetip() |
| 730 | widget.bind('<Enter>', enter) |
| 731 | widget.bind('<Leave>', leave) |
| 732 | |
| 733 | def createToolbar( self ): #MODIFICADO - OK |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 734 | "Create and return our toolbar frame." |
| 735 | |
| 736 | toolbar = Frame( self ) |
| 737 | |
| 738 | # Tools |
| 739 | for tool in self.tools: |
| 740 | cmd = ( lambda t=tool: self.activate( t ) ) |
| 741 | b = Button( toolbar, text=tool, font=self.smallFont, command=cmd) |
| 742 | if tool in self.images: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 743 | b.config( height=35, image=self.images[ tool ] ) |
| 744 | self.createToolTip(b, str(tool)) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 745 | # b.config( compound='top' ) |
| 746 | b.pack( fill='x' ) |
| 747 | self.buttons[ tool ] = b |
| 748 | self.activate( self.tools[ 0 ] ) |
| 749 | |
| 750 | # Spacer |
| 751 | Label( toolbar, text='' ).pack() |
| 752 | |
| 753 | # Commands |
| 754 | #for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]: |
| 755 | # doCmd = getattr( self, 'do' + cmd ) |
| 756 | # b = Button( toolbar, text=cmd, font=self.smallFont, |
| 757 | # fg=color, command=doCmd ) |
| 758 | # b.pack( fill='x', side='bottom' ) |
| 759 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 760 | # abaixo copiado Mini-CCNx para criar botao Generate |
| 761 | |
| 762 | for cmd, color in [ ( 'Generate', 'darkGreen' ) ]: |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 763 | doCmd = getattr( self, 'do' + cmd ) |
| 764 | b = Button( toolbar, text=cmd, font=self.smallFont, |
| 765 | fg=color, command=doCmd ) |
| 766 | b.pack( fill='x', side='bottom' ) |
| 767 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 768 | return toolbar |
| 769 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 770 | def doGenerate( self ): #COPIA Mini-CCNx - GERA TEMPLATE |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 771 | "Generate template." |
| 772 | self.activate( 'Select' ) |
| 773 | for tool in self.tools: |
| 774 | self.buttons[ tool ].config( state='disabled' ) |
| 775 | |
| 776 | self.buildTemplate() |
| 777 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 778 | for tool in self.tools: |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 779 | self.buttons[ tool ].config( state='normal' ) |
| 780 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 781 | #def doRun( self ): |
| 782 | # "Run command." |
| 783 | # self.activate( 'Select' ) |
| 784 | # for tool in self.tools: |
| 785 | # self.buttons[ tool ].config( state='disabled' ) |
| 786 | # self.start() |
| 787 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 788 | def doStop( self ): |
| 789 | "Stop command." |
| 790 | self.stop() |
| 791 | for tool in self.tools: |
| 792 | self.buttons[ tool ].config( state='normal' ) |
| 793 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 794 | def buildTemplate( self ): #COPIA Mini-CCNx para criar Template |
| 795 | "Generate template" |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 796 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 797 | template = open(self.template_file, 'w') |
| 798 | |
| 799 | # hosts |
| 800 | template.write('[hosts]\n') |
| 801 | for widget in self.widgetToItem: |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 802 | name = widget[ 'text' ] |
| 803 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 804 | if 'Host' in tags: |
| 805 | template.write(name + ':\n') |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 806 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 807 | # switches/routers |
| 808 | template.write('[routers]\n') |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 809 | for router in self.switchOpts.values(): |
| 810 | #print router |
| 811 | hostname=router['hostname'] |
| 812 | nodetype=router['switchType'] |
| 813 | nodenum=router['nodeNum'] |
| 814 | |
| 815 | template.write(hostname + ':\n') |
| 816 | |
| 817 | #name = widget[ 'text' ] |
| 818 | #tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 819 | #if 'Switch' in tags: |
| 820 | #template.write(name + ':\n') |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 821 | |
| 822 | # Make links |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 823 | template.write('[links]\n') |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 824 | for link in self.links.values(): |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 825 | dst=link['dest'] |
| 826 | src=link['src'] |
| 827 | linkopts=link['linkOpts'] |
| 828 | linktype=link['type'] |
| 829 | |
| 830 | #print linkopts |
| 831 | #print linktype |
| 832 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 833 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 834 | template.write(srcName + ':' + dstName + '\n') |
| 835 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 836 | template.close() |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 837 | |
| 838 | def addNode( self, node, nodeNum, x, y, name=None): |
| 839 | "Add a new node to our canvas." |
| 840 | if 'Switch' == node: |
| 841 | self.switchCount += 1 |
| 842 | if 'Host' == node: |
| 843 | self.hostCount += 1 |
| 844 | if 'Controller' == node: |
| 845 | self.controllerCount += 1 |
| 846 | if name is None: |
| 847 | name = self.nodePrefixes[ node ] + nodeNum |
| 848 | self.addNamedNode(node, name, x, y) |
| 849 | |
| 850 | def addNamedNode( self, node, name, x, y): |
| 851 | "Add a new node to our canvas." |
| 852 | c = self.canvas |
| 853 | icon = self.nodeIcon( node, name ) |
| 854 | item = self.canvas.create_window( x, y, anchor='c', window=icon, |
| 855 | tags=node ) |
| 856 | self.widgetToItem[ icon ] = item |
| 857 | self.itemToWidget[ item ] = icon |
| 858 | icon.links = {} |
| 859 | |
| 860 | def convertJsonUnicode(self, input): |
| 861 | "Some part of Mininet don't like Unicode" |
| 862 | if isinstance(input, dict): |
| 863 | return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in input.iteritems()} |
| 864 | elif isinstance(input, list): |
| 865 | return [self.convertJsonUnicode(element) for element in input] |
| 866 | elif isinstance(input, unicode): |
| 867 | return input.encode('utf-8') |
| 868 | else: |
| 869 | return input |
| 870 | |
| 871 | def loadTopology( self ): |
| 872 | "Load command." |
| 873 | c = self.canvas |
| 874 | |
| 875 | myFormats = [ |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 876 | ('Miniccnx Topology','*.mnccnx'), |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 877 | ('All Files','*'), |
| 878 | ] |
| 879 | f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb') |
| 880 | if f == None: |
| 881 | return |
| 882 | self.newTopology() |
| 883 | loadedTopology = self.convertJsonUnicode(json.load(f)) |
| 884 | |
| 885 | # Load application preferences |
| 886 | if 'application' in loadedTopology: |
| 887 | self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items()) |
| 888 | if "ovsOf10" not in self.appPrefs["openFlowVersions"]: |
| 889 | self.appPrefs["openFlowVersions"]["ovsOf10"] = '0' |
| 890 | if "ovsOf11" not in self.appPrefs["openFlowVersions"]: |
| 891 | self.appPrefs["openFlowVersions"]["ovsOf11"] = '0' |
| 892 | if "ovsOf12" not in self.appPrefs["openFlowVersions"]: |
| 893 | self.appPrefs["openFlowVersions"]["ovsOf12"] = '0' |
| 894 | if "ovsOf13" not in self.appPrefs["openFlowVersions"]: |
| 895 | self.appPrefs["openFlowVersions"]["ovsOf13"] = '0' |
| 896 | if "sflow" not in self.appPrefs: |
| 897 | self.appPrefs["sflow"] = self.sflowDefaults |
| 898 | if "netflow" not in self.appPrefs: |
| 899 | self.appPrefs["netflow"] = self.nflowDefaults |
| 900 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 901 | # Load hosts |
| 902 | hosts = loadedTopology['hosts'] |
| 903 | for host in hosts: |
| 904 | nodeNum = host['number'] |
| 905 | hostname = 'h'+nodeNum |
| 906 | if 'hostname' in host['opts']: |
| 907 | hostname = host['opts']['hostname'] |
| 908 | else: |
| 909 | host['opts']['hostname'] = hostname |
| 910 | if 'nodeNum' not in host['opts']: |
| 911 | host['opts']['nodeNum'] = int(nodeNum) |
| 912 | x = host['x'] |
| 913 | y = host['y'] |
| 914 | self.addNode('Host', nodeNum, float(x), float(y), name=hostname) |
| 915 | |
| 916 | # Fix JSON converting tuple to list when saving |
| 917 | if 'privateDirectory' in host['opts']: |
| 918 | newDirList = [] |
| 919 | for privateDir in host['opts']['privateDirectory']: |
| 920 | if isinstance( privateDir, list ): |
| 921 | newDirList.append((privateDir[0],privateDir[1])) |
| 922 | else: |
| 923 | newDirList.append(privateDir) |
| 924 | host['opts']['privateDirectory'] = newDirList |
| 925 | self.hostOpts[hostname] = host['opts'] |
| 926 | icon = self.findWidgetByName(hostname) |
| 927 | icon.bind('<Button-3>', self.do_hostPopup ) |
| 928 | |
| 929 | # Load switches |
| 930 | switches = loadedTopology['switches'] |
| 931 | for switch in switches: |
| 932 | nodeNum = switch['number'] |
| 933 | hostname = 's'+nodeNum |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 934 | if 'switchType' not in switch['opts']: |
| 935 | switch['opts']['switchType'] = 'default' |
| 936 | if 'hostname' in switch['opts']: |
| 937 | hostname = switch['opts']['hostname'] |
| 938 | else: |
| 939 | switch['opts']['hostname'] = hostname |
| 940 | if 'nodeNum' not in switch['opts']: |
| 941 | switch['opts']['nodeNum'] = int(nodeNum) |
| 942 | x = switch['x'] |
| 943 | y = switch['y'] |
| 944 | if switch['opts']['switchType'] == "legacyRouter": |
| 945 | self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname) |
| 946 | icon = self.findWidgetByName(hostname) |
| 947 | icon.bind('<Button-3>', self.do_legacyRouterPopup ) |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 948 | self.switchOpts[hostname] = switch['opts'] |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 949 | |
| 950 | # Load links |
| 951 | links = loadedTopology['links'] |
| 952 | for link in links: |
| 953 | srcNode = link['src'] |
| 954 | src = self.findWidgetByName(srcNode) |
| 955 | sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) |
| 956 | |
| 957 | destNode = link['dest'] |
| 958 | dest = self.findWidgetByName(destNode) |
| 959 | dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) |
| 960 | |
| 961 | self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, |
| 962 | fill='blue', tag='link' ) |
| 963 | c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) |
| 964 | self.addLink( src, dest, linkopts=link['opts'] ) |
| 965 | self.createDataLinkBindings() |
| 966 | self.link = self.linkWidget = None |
| 967 | |
| 968 | f.close |
| 969 | |
| 970 | def findWidgetByName( self, name ): |
| 971 | for widget in self.widgetToItem: |
| 972 | if name == widget[ 'text' ]: |
| 973 | return widget |
| 974 | |
| 975 | def newTopology( self ): |
| 976 | "New command." |
| 977 | for widget in self.widgetToItem.keys(): |
| 978 | self.deleteItem( self.widgetToItem[ widget ] ) |
| 979 | self.hostCount = 0 |
| 980 | self.switchCount = 0 |
| 981 | self.links = {} |
| 982 | self.hostOpts = {} |
| 983 | self.switchOpts = {} |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 984 | #self.appPrefs["ipBase"]= self.defaultIpBase |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 985 | |
| 986 | def saveTopology( self ): |
| 987 | "Save command." |
| 988 | myFormats = [ |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 989 | ('Miniccnx Topology','*.mnccnx'), |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 990 | ('All Files','*'), |
| 991 | ] |
| 992 | |
| 993 | savingDictionary = {} |
| 994 | fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...") |
| 995 | if len(fileName ) > 0: |
| 996 | # Save Application preferences |
| 997 | savingDictionary['version'] = '2' |
| 998 | |
| 999 | # Save Switches and Hosts |
| 1000 | hostsToSave = [] |
| 1001 | switchesToSave = [] |
| 1002 | controllersToSave = [] |
| 1003 | for widget in self.widgetToItem: |
| 1004 | name = widget[ 'text' ] |
| 1005 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1006 | x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) |
| 1007 | if 'LegacyRouter' in tags: |
| 1008 | nodeNum = self.switchOpts[name]['nodeNum'] |
| 1009 | nodeToSave = {'number':str(nodeNum), |
| 1010 | 'x':str(x1), |
| 1011 | 'y':str(y1), |
| 1012 | 'opts':self.switchOpts[name] } |
| 1013 | switchesToSave.append(nodeToSave) |
| 1014 | elif 'Host' in tags: |
| 1015 | nodeNum = self.hostOpts[name]['nodeNum'] |
| 1016 | nodeToSave = {'number':str(nodeNum), |
| 1017 | 'x':str(x1), |
| 1018 | 'y':str(y1), |
| 1019 | 'opts':self.hostOpts[name] } |
| 1020 | hostsToSave.append(nodeToSave) |
| 1021 | else: |
| 1022 | raise Exception( "Cannot create mystery node: " + name ) |
| 1023 | savingDictionary['hosts'] = hostsToSave |
| 1024 | savingDictionary['switches'] = switchesToSave |
| 1025 | savingDictionary['controllers'] = controllersToSave |
| 1026 | |
| 1027 | # Save Links |
| 1028 | linksToSave = [] |
| 1029 | for link in self.links.values(): |
| 1030 | src = link['src'] |
| 1031 | dst = link['dest'] |
| 1032 | linkopts = link['linkOpts'] |
| 1033 | |
| 1034 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 1035 | linkToSave = {'src':srcName, |
| 1036 | 'dest':dstName, |
| 1037 | 'opts':linkopts} |
| 1038 | if link['type'] == 'data': |
| 1039 | linksToSave.append(linkToSave) |
| 1040 | savingDictionary['links'] = linksToSave |
| 1041 | |
| 1042 | # Save Application preferences |
| 1043 | savingDictionary['application'] = self.appPrefs |
| 1044 | |
| 1045 | try: |
| 1046 | f = open(fileName, 'wb') |
| 1047 | f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': '))) |
| 1048 | except Exception as er: |
| 1049 | print er |
| 1050 | finally: |
| 1051 | f.close() |
| 1052 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1053 | # Generic canvas handler |
| 1054 | # |
| 1055 | # We could have used bindtags, as in nodeIcon, but |
| 1056 | # the dynamic approach used here |
| 1057 | # may actually require less code. In any case, it's an |
| 1058 | # interesting introspection-based alternative to bindtags. |
| 1059 | |
| 1060 | def canvasHandle( self, eventName, event ): |
| 1061 | "Generic canvas event handler" |
| 1062 | if self.active is None: |
| 1063 | return |
| 1064 | toolName = self.active |
| 1065 | handler = getattr( self, eventName + toolName, None ) |
| 1066 | if handler is not None: |
| 1067 | handler( event ) |
| 1068 | |
| 1069 | def clickCanvas( self, event ): |
| 1070 | "Canvas click handler." |
| 1071 | self.canvasHandle( 'click', event ) |
| 1072 | |
| 1073 | def dragCanvas( self, event ): |
| 1074 | "Canvas drag handler." |
| 1075 | self.canvasHandle( 'drag', event ) |
| 1076 | |
| 1077 | def releaseCanvas( self, event ): |
| 1078 | "Canvas mouse up handler." |
| 1079 | self.canvasHandle( 'release', event ) |
| 1080 | |
| 1081 | # Currently the only items we can select directly are |
| 1082 | # links. Nodes are handled by bindings in the node icon. |
| 1083 | |
| 1084 | def findItem( self, x, y ): |
| 1085 | "Find items at a location in our canvas." |
| 1086 | items = self.canvas.find_overlapping( x, y, x, y ) |
| 1087 | if len( items ) == 0: |
| 1088 | return None |
| 1089 | else: |
| 1090 | return items[ 0 ] |
| 1091 | |
| 1092 | # Canvas bindings for Select, Host, Switch and Link tools |
| 1093 | |
| 1094 | def clickSelect( self, event ): |
| 1095 | "Select an item." |
| 1096 | self.selectItem( self.findItem( event.x, event.y ) ) |
| 1097 | |
| 1098 | def deleteItem( self, item ): |
| 1099 | "Delete an item." |
| 1100 | # Don't delete while network is running |
| 1101 | if self.buttons[ 'Select' ][ 'state' ] == 'disabled': |
| 1102 | return |
| 1103 | # Delete from model |
| 1104 | if item in self.links: |
| 1105 | self.deleteLink( item ) |
| 1106 | if item in self.itemToWidget: |
| 1107 | self.deleteNode( item ) |
| 1108 | # Delete from view |
| 1109 | self.canvas.delete( item ) |
| 1110 | |
| 1111 | def deleteSelection( self, _event ): |
| 1112 | "Delete the selected item." |
| 1113 | if self.selection is not None: |
| 1114 | self.deleteItem( self.selection ) |
| 1115 | self.selectItem( None ) |
| 1116 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1117 | def clearPopups(self): |
| 1118 | print 'Entrou funcao clear_popups' |
| 1119 | |
| 1120 | if isHostPopup == True: |
| 1121 | print 'Hostpopup = true' |
| 1122 | self.hostPopup.unpost |
| 1123 | isHostPopup = False |
| 1124 | #if isRouterPopup == True |
| 1125 | #if isLinkPopup == True |
| 1126 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1127 | def nodeIcon( self, node, name ): |
| 1128 | "Create a new node icon." |
| 1129 | icon = Button( self.canvas, image=self.images[ node ], |
| 1130 | text=name, compound='top' ) |
| 1131 | # Unfortunately bindtags wants a tuple |
| 1132 | bindtags = [ str( self.nodeBindings ) ] |
| 1133 | bindtags += list( icon.bindtags() ) |
| 1134 | icon.bindtags( tuple( bindtags ) ) |
| 1135 | return icon |
| 1136 | |
| 1137 | def newNode( self, node, event ): |
| 1138 | "Add a new node to our canvas." |
| 1139 | c = self.canvas |
| 1140 | x, y = c.canvasx( event.x ), c.canvasy( event.y ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1141 | name = self.nodePrefixes[ node ] |
| 1142 | if 'Switch' == node: |
| 1143 | self.switchCount += 1 |
| 1144 | name = self.nodePrefixes[ node ] + str( self.switchCount ) |
| 1145 | self.switchOpts[name] = {} |
| 1146 | self.switchOpts[name]['nodeNum']=self.switchCount |
| 1147 | self.switchOpts[name]['hostname']=name |
| 1148 | self.switchOpts[name]['switchType']='default' |
| 1149 | self.switchOpts[name]['controllers']=[] |
| 1150 | if 'LegacyRouter' == node: |
| 1151 | self.switchCount += 1 |
| 1152 | name = self.nodePrefixes[ node ] + str( self.switchCount ) |
| 1153 | self.switchOpts[name] = {} |
| 1154 | self.switchOpts[name]['nodeNum']=self.switchCount |
| 1155 | self.switchOpts[name]['hostname']=name |
| 1156 | self.switchOpts[name]['switchType']='legacyRouter' |
| 1157 | if 'LegacySwitch' == node: |
| 1158 | self.switchCount += 1 |
| 1159 | name = self.nodePrefixes[ node ] + str( self.switchCount ) |
| 1160 | self.switchOpts[name] = {} |
| 1161 | self.switchOpts[name]['nodeNum']=self.switchCount |
| 1162 | self.switchOpts[name]['hostname']=name |
| 1163 | self.switchOpts[name]['switchType']='legacySwitch' |
| 1164 | self.switchOpts[name]['controllers']=[] |
| 1165 | if 'Host' == node: |
| 1166 | self.hostCount += 1 |
| 1167 | name = self.nodePrefixes[ node ] + str( self.hostCount ) |
| 1168 | self.hostOpts[name] = {'sched':'host'} |
| 1169 | self.hostOpts[name]['nodeNum']=self.hostCount |
| 1170 | self.hostOpts[name]['hostname']=name |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1171 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1172 | icon = self.nodeIcon( node, name ) |
| 1173 | item = self.canvas.create_window( x, y, anchor='c', window=icon, |
| 1174 | tags=node ) |
| 1175 | self.widgetToItem[ icon ] = item |
| 1176 | self.itemToWidget[ item ] = icon |
| 1177 | self.selectItem( item ) |
| 1178 | icon.links = {} |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1179 | if 'LegacyRouter' == node: |
| 1180 | icon.bind('<Button-3>', self.do_legacyRouterPopup ) |
| 1181 | if 'Host' == node: |
| 1182 | icon.bind('<Button-3>', self.do_hostPopup ) |
| 1183 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1184 | def clickHost( self, event ): |
| 1185 | "Add a new host to our canvas." |
| 1186 | self.newNode( 'Host', event ) |
| 1187 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1188 | def clickLegacyRouter( self, event ): |
| 1189 | "Add a new switch to our canvas." |
| 1190 | self.newNode( 'LegacyRouter', event ) |
| 1191 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1192 | def dragNetLink( self, event ): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1193 | "Drag a link's endpoint to another node." |
| 1194 | if self.link is None: |
| 1195 | return |
| 1196 | # Since drag starts in widget, we use root coords |
| 1197 | x = self.canvasx( event.x_root ) |
| 1198 | y = self.canvasy( event.y_root ) |
| 1199 | c = self.canvas |
| 1200 | c.coords( self.link, self.linkx, self.linky, x, y ) |
| 1201 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1202 | def releaseNetLink( self, _event ): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1203 | "Give up on the current link." |
| 1204 | if self.link is not None: |
| 1205 | self.canvas.delete( self.link ) |
| 1206 | self.linkWidget = self.linkItem = self.link = None |
| 1207 | |
| 1208 | # Generic node handlers |
| 1209 | |
| 1210 | def createNodeBindings( self ): |
| 1211 | "Create a set of bindings for nodes." |
| 1212 | bindings = { |
| 1213 | '<ButtonPress-1>': self.clickNode, |
| 1214 | '<B1-Motion>': self.dragNode, |
| 1215 | '<ButtonRelease-1>': self.releaseNode, |
| 1216 | '<Enter>': self.enterNode, |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1217 | '<Leave>': self.leaveNode |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1218 | } |
| 1219 | l = Label() # lightweight-ish owner for bindings |
| 1220 | for event, binding in bindings.items(): |
| 1221 | l.bind( event, binding ) |
| 1222 | return l |
| 1223 | |
| 1224 | def selectItem( self, item ): |
| 1225 | "Select an item and remember old selection." |
| 1226 | self.lastSelection = self.selection |
| 1227 | self.selection = item |
| 1228 | |
| 1229 | def enterNode( self, event ): |
| 1230 | "Select node on entry." |
| 1231 | self.selectNode( event ) |
| 1232 | |
| 1233 | def leaveNode( self, _event ): |
| 1234 | "Restore old selection on exit." |
| 1235 | self.selectItem( self.lastSelection ) |
| 1236 | |
| 1237 | def clickNode( self, event ): |
| 1238 | "Node click handler." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1239 | if self.active is 'NetLink': |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1240 | self.startLink( event ) |
| 1241 | else: |
| 1242 | self.selectNode( event ) |
| 1243 | return 'break' |
| 1244 | |
| 1245 | def dragNode( self, event ): |
| 1246 | "Node drag handler." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1247 | if self.active is 'NetLink': |
| 1248 | self.dragNetLink( event ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1249 | else: |
| 1250 | self.dragNodeAround( event ) |
| 1251 | |
| 1252 | def releaseNode( self, event ): |
| 1253 | "Node release handler." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1254 | if self.active is 'NetLink': |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1255 | self.finishLink( event ) |
| 1256 | |
| 1257 | # Specific node handlers |
| 1258 | |
| 1259 | def selectNode( self, event ): |
| 1260 | "Select the node that was clicked on." |
| 1261 | item = self.widgetToItem.get( event.widget, None ) |
| 1262 | self.selectItem( item ) |
| 1263 | |
| 1264 | def dragNodeAround( self, event ): |
| 1265 | "Drag a node around on the canvas." |
| 1266 | c = self.canvas |
| 1267 | # Convert global to local coordinates; |
| 1268 | # Necessary since x, y are widget-relative |
| 1269 | x = self.canvasx( event.x_root ) |
| 1270 | y = self.canvasy( event.y_root ) |
| 1271 | w = event.widget |
| 1272 | # Adjust node position |
| 1273 | item = self.widgetToItem[ w ] |
| 1274 | c.coords( item, x, y ) |
| 1275 | # Adjust link positions |
| 1276 | for dest in w.links: |
| 1277 | link = w.links[ dest ] |
| 1278 | item = self.widgetToItem[ dest ] |
| 1279 | x1, y1 = c.coords( item ) |
| 1280 | c.coords( link, x, y, x1, y1 ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1281 | self.updateScrollRegion() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1282 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1283 | def createControlLinkBindings( self ): |
| 1284 | "Create a set of bindings for nodes." |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1285 | # Link bindings |
| 1286 | # Selection still needs a bit of work overall |
| 1287 | # Callbacks ignore event |
| 1288 | |
| 1289 | def select( _event, link=self.link ): |
| 1290 | "Select item on mouse entry." |
| 1291 | self.selectItem( link ) |
| 1292 | |
| 1293 | def highlight( _event, link=self.link ): |
| 1294 | "Highlight item on mouse entry." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1295 | self.selectItem( link ) |
| 1296 | self.canvas.itemconfig( link, fill='green' ) |
| 1297 | |
| 1298 | def unhighlight( _event, link=self.link ): |
| 1299 | "Unhighlight item on mouse exit." |
| 1300 | self.canvas.itemconfig( link, fill='red' ) |
| 1301 | #self.selectItem( None ) |
| 1302 | |
| 1303 | self.canvas.tag_bind( self.link, '<Enter>', highlight ) |
| 1304 | self.canvas.tag_bind( self.link, '<Leave>', unhighlight ) |
| 1305 | self.canvas.tag_bind( self.link, '<ButtonPress-1>', select ) |
| 1306 | |
| 1307 | def createDataLinkBindings( self ): |
| 1308 | "Create a set of bindings for nodes." |
| 1309 | # Link bindings |
| 1310 | # Selection still needs a bit of work overall |
| 1311 | # Callbacks ignore event |
| 1312 | |
| 1313 | def select( _event, link=self.link ): |
| 1314 | "Select item on mouse entry." |
| 1315 | self.selectItem( link ) |
| 1316 | |
| 1317 | def highlight( _event, link=self.link ): |
| 1318 | "Highlight item on mouse entry." |
| 1319 | self.selectItem( link ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1320 | self.canvas.itemconfig( link, fill='green' ) |
| 1321 | |
| 1322 | def unhighlight( _event, link=self.link ): |
| 1323 | "Unhighlight item on mouse exit." |
| 1324 | self.canvas.itemconfig( link, fill='blue' ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1325 | #self.selectItem( None ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1326 | |
| 1327 | self.canvas.tag_bind( self.link, '<Enter>', highlight ) |
| 1328 | self.canvas.tag_bind( self.link, '<Leave>', unhighlight ) |
| 1329 | self.canvas.tag_bind( self.link, '<ButtonPress-1>', select ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1330 | self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup ) |
| 1331 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1332 | def startLink( self, event ): |
| 1333 | "Start a new link." |
| 1334 | if event.widget not in self.widgetToItem: |
| 1335 | # Didn't click on a node |
| 1336 | return |
| 1337 | |
| 1338 | w = event.widget |
| 1339 | item = self.widgetToItem[ w ] |
| 1340 | x, y = self.canvas.coords( item ) |
| 1341 | self.link = self.canvas.create_line( x, y, x, y, width=4, |
| 1342 | fill='blue', tag='link' ) |
| 1343 | self.linkx, self.linky = x, y |
| 1344 | self.linkWidget = w |
| 1345 | self.linkItem = item |
| 1346 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1347 | def finishLink( self, event ): |
| 1348 | "Finish creating a link" |
| 1349 | if self.link is None: |
| 1350 | return |
| 1351 | source = self.linkWidget |
| 1352 | c = self.canvas |
| 1353 | # Since we dragged from the widget, use root coords |
| 1354 | x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root ) |
| 1355 | target = self.findItem( x, y ) |
| 1356 | dest = self.itemToWidget.get( target, None ) |
| 1357 | if ( source is None or dest is None or source == dest |
| 1358 | or dest in source.links or source in dest.links ): |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1359 | self.releaseNetLink( event ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1360 | return |
| 1361 | # For now, don't allow hosts to be directly linked |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1362 | stags = self.canvas.gettags( self.widgetToItem[ source ] ) |
| 1363 | dtags = self.canvas.gettags( target ) |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1364 | if (('Host' in stags and 'Host' in dtags)): |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1365 | self.releaseNetLink( event ) |
| 1366 | return |
| 1367 | |
| 1368 | # Set link type |
| 1369 | linkType='data' |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1370 | |
| 1371 | self.createDataLinkBindings() |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1372 | c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,)) |
| 1373 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1374 | x, y = c.coords( target ) |
| 1375 | c.coords( self.link, self.linkx, self.linky, x, y ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1376 | self.addLink( source, dest, linktype=linkType ) |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1377 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1378 | # We're done |
| 1379 | self.link = self.linkWidget = None |
| 1380 | |
| 1381 | # Menu handlers |
| 1382 | |
| 1383 | def about( self ): |
| 1384 | "Display about box." |
| 1385 | about = self.aboutBox |
| 1386 | if about is None: |
| 1387 | bg = 'white' |
| 1388 | about = Toplevel( bg='white' ) |
| 1389 | about.title( 'About' ) |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1390 | info = self.appName + ': a simple network editor for MiniCCNx' |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1391 | version = 'MiniEdit '+MINIEDIT_VERSION |
| 1392 | author = 'Originally by: Bob Lantz <rlantz@cs>, April 2010' |
| 1393 | enhancements = 'Enhancements by: Gregory Gee, Since July 2013' |
| 1394 | www = 'http://gregorygee.wordpress.com/category/miniedit/' |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1395 | line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1396 | line2 = Label( about, text=version, font='Helvetica 9', bg=bg ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1397 | line3 = Label( about, text=author, font='Helvetica 9', bg=bg ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1398 | line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg ) |
| 1399 | line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER ) |
| 1400 | line5.insert(0, www) |
| 1401 | line5.configure(state='readonly') |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1402 | line1.pack( padx=20, pady=10 ) |
| 1403 | line2.pack(pady=10 ) |
| 1404 | line3.pack(pady=10 ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1405 | line4.pack(pady=10 ) |
| 1406 | line5.pack(pady=10 ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1407 | hide = ( lambda about=about: about.withdraw() ) |
| 1408 | self.aboutBox = about |
| 1409 | # Hide on close rather than destroying window |
| 1410 | Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide ) |
| 1411 | # Show (existing) window |
| 1412 | about.deiconify() |
| 1413 | |
| 1414 | def createToolImages( self ): |
| 1415 | "Create toolbar (and icon) images." |
| 1416 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1417 | def checkIntf( self, intf ): |
| 1418 | "Make sure intf exists and is not configured." |
| 1419 | if ( ' %s:' % intf ) not in quietRun( 'ip link show' ): |
| 1420 | showerror(title="Error", |
| 1421 | message='External interface ' +intf + ' does not exist! Skipping.') |
| 1422 | return False |
| 1423 | ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) ) |
| 1424 | if ips: |
| 1425 | showerror(title="Error", |
| 1426 | message= intf + ' has an IP address and is probably in use! Skipping.' ) |
| 1427 | return False |
| 1428 | return True |
| 1429 | |
| 1430 | def hostDetails( self, _ignore=None ): |
| 1431 | if ( self.selection is None or |
| 1432 | self.net is not None or |
| 1433 | self.selection not in self.itemToWidget ): |
| 1434 | return |
| 1435 | widget = self.itemToWidget[ self.selection ] |
| 1436 | name = widget[ 'text' ] |
| 1437 | tags = self.canvas.gettags( self.selection ) |
| 1438 | if 'Host' not in tags: |
| 1439 | return |
| 1440 | |
| 1441 | prefDefaults = self.hostOpts[name] |
| 1442 | hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults) |
| 1443 | self.master.wait_window(hostBox.top) |
| 1444 | if hostBox.result: |
| 1445 | newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']} |
| 1446 | newHostOpts['sched'] = hostBox.result['sched'] |
| 1447 | if len(hostBox.result['startCommand']) > 0: |
| 1448 | newHostOpts['startCommand'] = hostBox.result['startCommand'] |
| 1449 | if len(hostBox.result['stopCommand']) > 0: |
| 1450 | newHostOpts['stopCommand'] = hostBox.result['stopCommand'] |
| 1451 | if len(hostBox.result['cpu']) > 0: |
| 1452 | newHostOpts['cpu'] = float(hostBox.result['cpu']) |
| 1453 | if len(hostBox.result['cores']) > 0: |
| 1454 | newHostOpts['cores'] = hostBox.result['cores'] |
| 1455 | if len(hostBox.result['hostname']) > 0: |
| 1456 | newHostOpts['hostname'] = hostBox.result['hostname'] |
| 1457 | name = hostBox.result['hostname'] |
| 1458 | widget[ 'text' ] = name |
| 1459 | if len(hostBox.result['defaultRoute']) > 0: |
| 1460 | newHostOpts['defaultRoute'] = hostBox.result['defaultRoute'] |
| 1461 | if len(hostBox.result['ip']) > 0: |
| 1462 | newHostOpts['ip'] = hostBox.result['ip'] |
| 1463 | if len(hostBox.result['externalInterfaces']) > 0: |
| 1464 | newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces'] |
| 1465 | if len(hostBox.result['vlanInterfaces']) > 0: |
| 1466 | newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces'] |
| 1467 | if len(hostBox.result['privateDirectory']) > 0: |
| 1468 | newHostOpts['privateDirectory'] = hostBox.result['privateDirectory'] |
| 1469 | self.hostOpts[name] = newHostOpts |
| 1470 | print 'New host details for ' + name + ' = ' + str(newHostOpts) |
| 1471 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1472 | def linkUp( self ): |
| 1473 | if ( self.selection is None or |
| 1474 | self.net is None): |
| 1475 | return |
| 1476 | link = self.selection |
| 1477 | linkDetail = self.links[link] |
| 1478 | src = linkDetail['src'] |
| 1479 | dst = linkDetail['dest'] |
| 1480 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 1481 | self.net.configLinkStatus(srcName, dstName, 'up') |
| 1482 | self.canvas.itemconfig(link, dash=()) |
| 1483 | |
| 1484 | def linkDown( self ): |
| 1485 | if ( self.selection is None or |
| 1486 | self.net is None): |
| 1487 | return |
| 1488 | link = self.selection |
| 1489 | linkDetail = self.links[link] |
| 1490 | src = linkDetail['src'] |
| 1491 | dst = linkDetail['dest'] |
| 1492 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 1493 | self.net.configLinkStatus(srcName, dstName, 'down') |
| 1494 | self.canvas.itemconfig(link, dash=(4, 4)) |
| 1495 | |
| 1496 | def linkDetails( self, _ignore=None ): |
| 1497 | if ( self.selection is None or |
| 1498 | self.net is not None): |
| 1499 | return |
| 1500 | link = self.selection |
| 1501 | |
| 1502 | linkDetail = self.links[link] |
| 1503 | src = linkDetail['src'] |
| 1504 | dest = linkDetail['dest'] |
| 1505 | linkopts = linkDetail['linkOpts'] |
| 1506 | linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts) |
| 1507 | if linkBox.result is not None: |
| 1508 | linkDetail['linkOpts'] = linkBox.result |
| 1509 | print 'New link details = ' + str(linkBox.result) |
| 1510 | |
| 1511 | def prefDetails( self ): |
| 1512 | prefDefaults = self.appPrefs |
| 1513 | prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults) |
| 1514 | print 'New Prefs = ' + str(prefBox.result) |
| 1515 | if prefBox.result: |
| 1516 | self.appPrefs = prefBox.result |
| 1517 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1518 | def listBridge( self, _ignore=None ): |
| 1519 | if ( self.selection is None or |
| 1520 | self.net is None or |
| 1521 | self.selection not in self.itemToWidget ): |
| 1522 | return |
| 1523 | name = self.itemToWidget[ self.selection ][ 'text' ] |
| 1524 | tags = self.canvas.gettags( self.selection ) |
| 1525 | |
| 1526 | if name not in self.net.nameToNode: |
| 1527 | return |
| 1528 | if 'Switch' in tags or 'LegacySwitch' in tags: |
| 1529 | call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True) |
| 1530 | |
| 1531 | def ovsShow( self, _ignore=None ): |
| 1532 | call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True) |
| 1533 | |
| 1534 | def rootTerminal( self, _ignore=None ): |
| 1535 | call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True) |
| 1536 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1537 | # Model interface |
| 1538 | # |
| 1539 | # Ultimately we will either want to use a topo or |
| 1540 | # mininet object here, probably. |
| 1541 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1542 | def addLink( self, source, dest, linktype='data', linkopts={} ): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1543 | "Add link to model." |
| 1544 | source.links[ dest ] = self.link |
| 1545 | dest.links[ source ] = self.link |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1546 | self.links[ self.link ] = {'type' :linktype, |
| 1547 | 'src':source, |
| 1548 | 'dest':dest, |
| 1549 | 'linkOpts':linkopts} |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1550 | |
| 1551 | def deleteLink( self, link ): |
| 1552 | "Delete link from model." |
| 1553 | pair = self.links.get( link, None ) |
| 1554 | if pair is not None: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1555 | source=pair['src'] |
| 1556 | dest=pair['dest'] |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1557 | del source.links[ dest ] |
| 1558 | del dest.links[ source ] |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1559 | stags = self.canvas.gettags( self.widgetToItem[ source ] ) |
| 1560 | dtags = self.canvas.gettags( self.widgetToItem[ dest ] ) |
| 1561 | ltags = self.canvas.gettags( link ) |
| 1562 | |
| 1563 | if 'control' in ltags: |
| 1564 | controllerName = '' |
| 1565 | switchName = '' |
| 1566 | if 'Controller' in stags: |
| 1567 | controllerName = source[ 'text' ] |
| 1568 | switchName = dest[ 'text' ] |
| 1569 | else: |
| 1570 | controllerName = dest[ 'text' ] |
| 1571 | switchName = source[ 'text' ] |
| 1572 | |
| 1573 | if controllerName in self.switchOpts[switchName]['controllers']: |
| 1574 | self.switchOpts[switchName]['controllers'].remove(controllerName) |
| 1575 | |
| 1576 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1577 | if link is not None: |
| 1578 | del self.links[ link ] |
| 1579 | |
| 1580 | def deleteNode( self, item ): |
| 1581 | "Delete node (and its links) from model." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1582 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1583 | widget = self.itemToWidget[ item ] |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1584 | tags = self.canvas.gettags(item) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1585 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1586 | for link in widget.links.values(): |
| 1587 | # Delete from view and model |
| 1588 | self.deleteItem( link ) |
| 1589 | del self.itemToWidget[ item ] |
| 1590 | del self.widgetToItem[ widget ] |
| 1591 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1592 | def buildNodes( self, net): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1593 | # Make nodes |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1594 | print "Getting Hosts and Switches." |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1595 | for widget in self.widgetToItem: |
| 1596 | name = widget[ 'text' ] |
| 1597 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1598 | #print name+' has '+str(tags) |
| 1599 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1600 | if 'LegacyRouter' in tags: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1601 | newSwitch = net.addHost( name , cls=LegacyRouter) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1602 | elif 'Host' in tags: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1603 | opts = self.hostOpts[name] |
| 1604 | #print str(opts) |
| 1605 | ip = None |
| 1606 | defaultRoute = None |
| 1607 | if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: |
| 1608 | defaultRoute = 'via '+opts['defaultRoute'] |
| 1609 | if 'ip' in opts and len(opts['ip']) > 0: |
| 1610 | ip = opts['ip'] |
| 1611 | else: |
| 1612 | nodeNum = self.hostOpts[name]['nodeNum'] |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1613 | #ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) |
| 1614 | #ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1615 | |
| 1616 | # Create the correct host class |
| 1617 | if 'cores' in opts or 'cpu' in opts: |
| 1618 | if ('privateDirectory' in opts): |
| 1619 | hostCls = partial( CPULimitedHost, |
| 1620 | privateDirs=opts['privateDirectory'] ) |
| 1621 | else: |
| 1622 | hostCls=CPULimitedHost |
| 1623 | else: |
| 1624 | if ('privateDirectory' in opts): |
| 1625 | hostCls = partial( Host, |
| 1626 | privateDirs=opts['privateDirectory'] ) |
| 1627 | else: |
| 1628 | hostCls=Host |
| 1629 | print hostCls |
| 1630 | newHost = net.addHost( name, |
| 1631 | cls=hostCls, |
| 1632 | ip=ip, |
| 1633 | defaultRoute=defaultRoute |
| 1634 | ) |
| 1635 | |
| 1636 | # Set the CPULimitedHost specific options |
| 1637 | if 'cores' in opts: |
| 1638 | newHost.setCPUs(cores = opts['cores']) |
| 1639 | if 'cpu' in opts: |
| 1640 | newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched']) |
| 1641 | |
| 1642 | # Attach external interfaces |
| 1643 | if ('externalInterfaces' in opts): |
| 1644 | for extInterface in opts['externalInterfaces']: |
| 1645 | if self.checkIntf(extInterface): |
| 1646 | Intf( extInterface, node=newHost ) |
| 1647 | if ('vlanInterfaces' in opts): |
| 1648 | if len(opts['vlanInterfaces']) > 0: |
| 1649 | print 'Checking that OS is VLAN prepared' |
| 1650 | self.pathCheck('vconfig', moduleName='vlan package') |
| 1651 | moduleDeps( add='8021q' ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1652 | else: |
| 1653 | raise Exception( "Cannot create mystery node: " + name ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1654 | |
| 1655 | def pathCheck( self, *args, **kwargs ): |
| 1656 | "Make sure each program in *args can be found in $PATH." |
| 1657 | moduleName = kwargs.get( 'moduleName', 'it' ) |
| 1658 | for arg in args: |
| 1659 | if not quietRun( 'which ' + arg ): |
| 1660 | showerror(title="Error", |
| 1661 | message= 'Cannot find required executable %s.\n' % arg + |
| 1662 | 'Please make sure that %s is installed ' % moduleName + |
| 1663 | 'and available in your $PATH.' ) |
| 1664 | |
| 1665 | def buildLinks( self, net): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1666 | # Make links |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1667 | print "Getting Links." |
| 1668 | for key,link in self.links.iteritems(): |
| 1669 | tags = self.canvas.gettags(key) |
| 1670 | if 'data' in tags: |
| 1671 | src=link['src'] |
| 1672 | dst=link['dest'] |
| 1673 | linkopts=link['linkOpts'] |
| 1674 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 1675 | srcNode, dstNode = net.nameToNode[ srcName ], net.nameToNode[ dstName ] |
| 1676 | if linkopts: |
| 1677 | net.addLink(srcNode, dstNode, cls=TCLink, **linkopts) |
| 1678 | else: |
| 1679 | #print str(srcNode) |
| 1680 | #print str(dstNode) |
| 1681 | net.addLink(srcNode, dstNode) |
| 1682 | self.canvas.itemconfig(key, dash=()) |
| 1683 | |
| 1684 | |
| 1685 | def build( self ): |
| 1686 | print "Build network based on our topology." |
| 1687 | |
| 1688 | dpctl = None |
| 1689 | if len(self.appPrefs['dpctl']) > 0: |
| 1690 | dpctl = int(self.appPrefs['dpctl']) |
| 1691 | net = Mininet( topo=None, |
| 1692 | listenPort=dpctl, |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1693 | build=False) |
| 1694 | #ipBase=self.appPrefs['ipBase'] ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1695 | |
| 1696 | self.buildNodes(net) |
| 1697 | self.buildLinks(net) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1698 | |
| 1699 | # Build network (we have to do this separately at the moment ) |
| 1700 | net.build() |
| 1701 | |
| 1702 | return net |
| 1703 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1704 | |
| 1705 | def postStartSetup( self ): |
| 1706 | |
| 1707 | # Setup host details |
| 1708 | for widget in self.widgetToItem: |
| 1709 | name = widget[ 'text' ] |
| 1710 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1711 | if 'Host' in tags: |
| 1712 | newHost = self.net.get(name) |
| 1713 | opts = self.hostOpts[name] |
| 1714 | # Attach vlan interfaces |
| 1715 | if ('vlanInterfaces' in opts): |
| 1716 | for vlanInterface in opts['vlanInterfaces']: |
| 1717 | print 'adding vlan interface '+vlanInterface[1] |
| 1718 | newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0]) |
| 1719 | # Run User Defined Start Command |
| 1720 | if ('startCommand' in opts): |
| 1721 | newHost.cmdPrint(opts['startCommand']) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1722 | |
| 1723 | # Configure NetFlow |
| 1724 | nflowValues = self.appPrefs['netflow'] |
| 1725 | if len(nflowValues['nflowTarget']) > 0: |
| 1726 | nflowEnabled = False |
| 1727 | nflowSwitches = '' |
| 1728 | for widget in self.widgetToItem: |
| 1729 | name = widget[ 'text' ] |
| 1730 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1731 | |
| 1732 | if 'Switch' in tags: |
| 1733 | opts = self.switchOpts[name] |
| 1734 | if 'netflow' in opts: |
| 1735 | if opts['netflow'] == '1': |
| 1736 | print name+' has Netflow enabled' |
| 1737 | nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF' |
| 1738 | nflowEnabled=True |
| 1739 | if nflowEnabled: |
| 1740 | nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout'] |
| 1741 | if nflowValues['nflowAddId'] == '1': |
| 1742 | nflowCmd = nflowCmd + ' add_id_to_interface=true' |
| 1743 | else: |
| 1744 | nflowCmd = nflowCmd + ' add_id_to_interface=false' |
| 1745 | print 'cmd = '+nflowCmd+nflowSwitches |
| 1746 | call(nflowCmd+nflowSwitches, shell=True) |
| 1747 | |
| 1748 | else: |
| 1749 | print 'No switches with Netflow' |
| 1750 | else: |
| 1751 | print 'No NetFlow targets specified.' |
| 1752 | |
| 1753 | # Configure sFlow |
| 1754 | sflowValues = self.appPrefs['sflow'] |
| 1755 | if len(sflowValues['sflowTarget']) > 0: |
| 1756 | sflowEnabled = False |
| 1757 | sflowSwitches = '' |
| 1758 | for widget in self.widgetToItem: |
| 1759 | name = widget[ 'text' ] |
| 1760 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1761 | |
| 1762 | if 'Switch' in tags: |
| 1763 | opts = self.switchOpts[name] |
| 1764 | if 'sflow' in opts: |
| 1765 | if opts['sflow'] == '1': |
| 1766 | print name+' has sflow enabled' |
| 1767 | sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF' |
| 1768 | sflowEnabled=True |
| 1769 | if sflowEnabled: |
| 1770 | sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling'] |
| 1771 | print 'cmd = '+sflowCmd+sflowSwitches |
| 1772 | call(sflowCmd+sflowSwitches, shell=True) |
| 1773 | |
| 1774 | else: |
| 1775 | print 'No switches with sflow' |
| 1776 | else: |
| 1777 | print 'No sFlow targets specified.' |
| 1778 | |
| 1779 | ## NOTE: MAKE SURE THIS IS LAST THING CALLED |
| 1780 | # Start the CLI if enabled |
| 1781 | if self.appPrefs['startCLI'] == '1': |
| 1782 | info( "\n\n NOTE: PLEASE REMEMBER TO EXIT THE CLI BEFORE YOU PRESS THE STOP BUTTON. Not exiting will prevent MiniEdit from quitting and will prevent you from starting the network again during this sessoin.\n\n") |
| 1783 | CLI(self.net) |
| 1784 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1785 | def start( self ): |
| 1786 | "Start network." |
| 1787 | if self.net is None: |
| 1788 | self.net = self.build() |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1789 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1790 | for widget in self.widgetToItem: |
| 1791 | name = widget[ 'text' ] |
| 1792 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1793 | |
| 1794 | self.postStartSetup() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1795 | |
| 1796 | def stop( self ): |
| 1797 | "Stop network." |
| 1798 | if self.net is not None: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1799 | # Stop host details |
| 1800 | for widget in self.widgetToItem: |
| 1801 | name = widget[ 'text' ] |
| 1802 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1803 | if 'Host' in tags: |
| 1804 | newHost = self.net.get(name) |
| 1805 | opts = self.hostOpts[name] |
| 1806 | # Run User Defined Stop Command |
| 1807 | if ('stopCommand' in opts): |
| 1808 | newHost.cmdPrint(opts['stopCommand']) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1809 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1810 | self.net.stop() |
| 1811 | cleanUpScreens() |
| 1812 | self.net = None |
| 1813 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1814 | def do_linkPopup(self, event): |
| 1815 | # display the popup menu |
| 1816 | if ( self.net is None ): |
| 1817 | try: |
| 1818 | self.linkPopup.tk_popup(event.x_root, event.y_root) |
| 1819 | finally: |
| 1820 | # make sure to release the grab (Tk 8.0a1 only) |
| 1821 | self.linkPopup.grab_release() |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1822 | |
| 1823 | def do_legacyRouterPopup(self, event): |
| 1824 | # display the popup menu |
| 1825 | if ( self.net is None ): |
| 1826 | try: |
| 1827 | self.legacyRouterPopup.tk_popup(event.x_root, event.y_root) |
| 1828 | finally: |
| 1829 | # make sure to release the grab (Tk 8.0a1 only) |
| 1830 | self.legacyRouterPopup.grab_release() |
| 1831 | |
| 1832 | def do_hostPopup(self, event): |
| 1833 | # display the popup menu |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1834 | if ( self.net is None ): |
| 1835 | try: |
| 1836 | self.hostPopup.tk_popup(event.x_root, event.y_root) |
| 1837 | isHostPopup = True |
| 1838 | finally: |
| 1839 | # make sure to release the grab (Tk 8.0a1 only) |
| 1840 | self.hostPopup.grab_release() |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1841 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1842 | def xterm( self, _ignore=None ): |
| 1843 | "Make an xterm when a button is pressed." |
| 1844 | if ( self.selection is None or |
| 1845 | self.net is None or |
| 1846 | self.selection not in self.itemToWidget ): |
| 1847 | return |
| 1848 | name = self.itemToWidget[ self.selection ][ 'text' ] |
| 1849 | if name not in self.net.nameToNode: |
| 1850 | return |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1851 | term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] ) |
| 1852 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): |
| 1853 | self.net.terms += term |
| 1854 | else: |
| 1855 | self.net.terms.append(term) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1856 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1857 | def iperf( self, _ignore=None ): |
| 1858 | "Make an xterm when a button is pressed." |
| 1859 | if ( self.selection is None or |
| 1860 | self.net is None or |
| 1861 | self.selection not in self.itemToWidget ): |
| 1862 | return |
| 1863 | name = self.itemToWidget[ self.selection ][ 'text' ] |
| 1864 | if name not in self.net.nameToNode: |
| 1865 | return |
| 1866 | self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' ) |
| 1867 | |
| 1868 | """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """ |
| 1869 | |
| 1870 | def parseArgs( self ): |
| 1871 | """Parse command-line args and return options object. |
| 1872 | returns: opts parse options dict""" |
| 1873 | |
| 1874 | if '--custom' in sys.argv: |
| 1875 | index = sys.argv.index( '--custom' ) |
| 1876 | if len( sys.argv ) > index + 1: |
| 1877 | filename = sys.argv[ index + 1 ] |
| 1878 | self.parseCustomFile( filename ) |
| 1879 | else: |
| 1880 | raise Exception( 'Custom file name not found' ) |
| 1881 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1882 | desc = ( "The %prog utility creates Miniccnx network from the\n" |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1883 | "command line. It can create parametrized topologies,\n" |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1884 | "invoke the Miniccnx CLI, and run tests." ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1885 | |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1886 | usage = ( '%prog [options] [template_file]\n' |
| 1887 | '\nIf no template_file is given, generated template will be written to the file miniccnx.conf in the current directory.\n' |
| 1888 | 'Type %prog -h for details)' ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1889 | |
| 1890 | opts = OptionParser( description=desc, usage=usage ) |
| 1891 | |
| 1892 | addDictOption( opts, TOPOS, TOPODEF, 'topo' ) |
| 1893 | addDictOption( opts, LINKS, LINKDEF, 'link' ) |
| 1894 | |
| 1895 | opts.add_option( '--custom', type='string', default=None, |
| 1896 | help='read custom topo and node params from .py' + |
| 1897 | 'file' ) |
| 1898 | |
| 1899 | self.options, self.args = opts.parse_args() |
| 1900 | # We don't accept extra arguments after the options |
| 1901 | if self.args: |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1902 | if len(self.args) > 1: |
| 1903 | opts.print_help() |
| 1904 | exit() |
| 1905 | else: |
| 1906 | self.template_file=self.args[0] |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1907 | |
| 1908 | def setCustom( self, name, value ): |
| 1909 | "Set custom parameters for MininetRunner." |
| 1910 | if name in ( 'topos', 'switches', 'hosts', 'controllers' ): |
| 1911 | # Update dictionaries |
| 1912 | param = name.upper() |
| 1913 | globals()[ param ].update( value ) |
| 1914 | elif name == 'validate': |
| 1915 | # Add custom validate function |
| 1916 | self.validate = value |
| 1917 | else: |
| 1918 | # Add or modify global variable or class |
| 1919 | globals()[ name ] = value |
| 1920 | |
| 1921 | def parseCustomFile( self, fileName ): |
| 1922 | "Parse custom file and add params before parsing cmd-line options." |
| 1923 | customs = {} |
| 1924 | if os.path.isfile( fileName ): |
| 1925 | execfile( fileName, customs, customs ) |
| 1926 | for name, val in customs.iteritems(): |
| 1927 | self.setCustom( name, val ) |
| 1928 | else: |
| 1929 | raise Exception( 'could not find custom file: %s' % fileName ) |
| 1930 | |
| 1931 | def importTopo( self ): |
| 1932 | print 'topo='+self.options.topo |
| 1933 | if self.options.topo == 'none': |
| 1934 | return |
| 1935 | self.newTopology() |
| 1936 | topo = buildTopo( TOPOS, self.options.topo ) |
| 1937 | link = customConstructor( LINKS, self.options.link ) |
| 1938 | importNet = Mininet(topo=topo, build=False, link=link) |
| 1939 | importNet.build() |
| 1940 | |
| 1941 | c = self.canvas |
| 1942 | rowIncrement = 100 |
| 1943 | currentY = 100 |
| 1944 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1945 | # Add switches |
| 1946 | print 'switches:'+str(len(importNet.switches)) |
| 1947 | columnCount = 0 |
| 1948 | for switch in importNet.switches: |
| 1949 | name = switch.name |
| 1950 | self.switchOpts[name] = {} |
| 1951 | self.switchOpts[name]['nodeNum']=self.switchCount |
| 1952 | self.switchOpts[name]['hostname']=name |
| 1953 | self.switchOpts[name]['switchType']='default' |
| 1954 | self.switchOpts[name]['controllers']=[] |
| 1955 | |
| 1956 | x = columnCount*100+100 |
| 1957 | self.addNode('Switch', self.switchCount, |
| 1958 | float(x), float(currentY), name=name) |
| 1959 | icon = self.findWidgetByName(name) |
| 1960 | icon.bind('<Button-3>', self.do_switchPopup ) |
Caio Elias | e904f53 | 2014-12-01 16:19:22 -0200 | [diff] [blame^] | 1961 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1962 | if columnCount == 9: |
| 1963 | columnCount = 0 |
| 1964 | currentY = currentY + rowIncrement |
| 1965 | else: |
| 1966 | columnCount =columnCount+1 |
| 1967 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 1968 | currentY = currentY + rowIncrement |
| 1969 | # Add hosts |
| 1970 | print 'hosts:'+str(len(importNet.hosts)) |
| 1971 | columnCount = 0 |
| 1972 | for host in importNet.hosts: |
| 1973 | name = host.name |
| 1974 | self.hostOpts[name] = {'sched':'host'} |
| 1975 | self.hostOpts[name]['nodeNum']=self.hostCount |
| 1976 | self.hostOpts[name]['hostname']=name |
| 1977 | self.hostOpts[name]['ip']=host.IP() |
| 1978 | |
| 1979 | x = columnCount*100+100 |
| 1980 | self.addNode('Host', self.hostCount, |
| 1981 | float(x), float(currentY), name=name) |
| 1982 | icon = self.findWidgetByName(name) |
| 1983 | icon.bind('<Button-3>', self.do_hostPopup ) |
| 1984 | if columnCount == 9: |
| 1985 | columnCount = 0 |
| 1986 | currentY = currentY + rowIncrement |
| 1987 | else: |
| 1988 | columnCount =columnCount+1 |
| 1989 | |
| 1990 | print 'links:'+str(len(topo.links())) |
| 1991 | #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')] |
| 1992 | for link in topo.links(): |
| 1993 | print str(link) |
| 1994 | srcNode = link[0] |
| 1995 | src = self.findWidgetByName(srcNode) |
| 1996 | sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) |
| 1997 | |
| 1998 | destNode = link[1] |
| 1999 | dest = self.findWidgetByName(destNode) |
| 2000 | dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) |
| 2001 | |
| 2002 | params = topo.linkInfo( srcNode, destNode ) |
| 2003 | print 'Link Parameters='+str(params) |
| 2004 | |
| 2005 | self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, |
| 2006 | fill='blue', tag='link' ) |
| 2007 | c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) |
| 2008 | self.addLink( src, dest, linkopts=params ) |
| 2009 | self.createDataLinkBindings() |
| 2010 | self.link = self.linkWidget = None |
| 2011 | |
| 2012 | importNet.stop() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2013 | |
| 2014 | def miniEditImages(): |
| 2015 | "Create and return images for MiniEdit." |
| 2016 | |
| 2017 | # Image data. Git will be unhappy. However, the alternative |
| 2018 | # is to keep track of separate binary files, which is also |
| 2019 | # unappealing. |
| 2020 | |
| 2021 | return { |
| 2022 | 'Select': BitmapImage( |
| 2023 | file='/usr/include/X11/bitmaps/left_ptr' ), |
| 2024 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 2025 | 'LegacyRouter': PhotoImage( data=r""" |
| 2026 | R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+3QFq1DmL3wJMmAMzZZW11dnZ |
| 2027 | 2SFrtyNdmTSO6gIZMUKa8gJVqEOHzR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq |
| 2028 | 6ymF4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9vwNgvwJZsX+69gsXJQFH |
| 2029 | jTtjizF0tvHx8VOm9z2V736Dhz2N3QM2acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtg |
| 2030 | tktjfQFu3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh312Gt+VGm/AQIDTmB |
| 2031 | yAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i56gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia |
| 2032 | 7DpeggFt2QNPm97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er/yVVhwJJktPh |
| 2033 | 70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVVhQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18 |
| 2034 | zKfP9wwcLAMHCwFFiS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh7cve8pG/ |
| 2035 | 7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR1RMjNTF3vU2X4TZupwRSolNne4nB+T+L |
| 2036 | 2YGz4zJ/zYe99YGHjRdDcT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6WlpW2t |
| 2037 | 7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90uvPz8wIVKBp42SV5zbfT7wtXpStV |
| 2038 | fwFWrBVvyTt3swFz5kGBv2+1/QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t |
| 2039 | u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T5yH5BAEAAAAALAAAAAAyABgA |
| 2040 | Bwj/AAEIHEiQYJY7Qwg9UsTplRIbENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4c |
| 2041 | HeoIabJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPATqEBoRB9gVJsxRlhPwHI |
| 2042 | 0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkS |
| 2043 | rtwADuxCG/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmDjhTMmseoKQIFDx7R |
| 2044 | oxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq |
| 2045 | 9dGvv09RHFhcIUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p9HEUFhxgMSAv |
| 2046 | jbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CF |
| 2047 | oVggmEgCyRf01WcFCYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57AgyZckpKKP |
| 2048 | GFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemMIQggeaJywSQ/wgHOAmJskQEfWqBlFBEH |
| 2049 | 1P/QaGY3QOpDZXA2+A6m7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlBpZdi |
| 2050 | isd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtChRmVPNWgpr+Be+Nc9icARww9TkIEu |
| 2051 | DAsQ0O7DzGIQzD2QdDEJHTsIAROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB |
| 2052 | xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE42Q9jtFIp8z0Dy1jQMA1AGzi |
| 2053 | z9VoW7310V0znYDTGMQgwUDXLDBO2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOM |
| 2054 | LQkcjvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZMDHKCTwI8EcQFHBBAAFc |
| 2055 | gGPLHwLwcMIo12Qxu0ABAQA7 |
| 2056 | """), |
| 2057 | |
| 2058 | 'Host': PhotoImage( data=r""" |
| 2059 | R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M |
| 2060 | mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m |
| 2061 | Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A |
| 2062 | M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM |
| 2063 | AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz |
| 2064 | /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ |
| 2065 | zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ |
| 2066 | mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz |
| 2067 | ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ |
| 2068 | M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ |
| 2069 | AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA |
| 2070 | /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM |
| 2071 | zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm |
| 2072 | mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA |
| 2073 | ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM |
| 2074 | MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm |
| 2075 | AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A |
| 2076 | ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI |
| 2077 | AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA |
| 2078 | RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA |
| 2079 | ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw |
| 2080 | BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2 |
| 2081 | HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO |
| 2082 | p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/ |
| 2083 | C8cSBBAQADs= |
| 2084 | """ ), |
| 2085 | |
| 2086 | 'NetLink': PhotoImage( data=r""" |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2087 | R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M |
| 2088 | mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m |
| 2089 | Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A |
| 2090 | M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM |
| 2091 | AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz |
| 2092 | /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ |
| 2093 | zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ |
| 2094 | mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz |
| 2095 | ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ |
| 2096 | M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ |
| 2097 | AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA |
| 2098 | /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM |
| 2099 | zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm |
| 2100 | mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA |
| 2101 | ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM |
| 2102 | MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm |
| 2103 | AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A |
| 2104 | ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI |
| 2105 | AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA |
| 2106 | RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA |
| 2107 | ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG |
| 2108 | Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy |
| 2109 | lBmxI8mSNknm1Dnx5sCAADs= |
| 2110 | """ ) |
| 2111 | } |
| 2112 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 2113 | def addDictOption( opts, choicesDict, default, name, helpStr=None ): |
| 2114 | """Convenience function to add choices dicts to OptionParser. |
| 2115 | opts: OptionParser instance |
| 2116 | choicesDict: dictionary of valid choices, must include default |
| 2117 | default: default choice key |
| 2118 | name: long option name |
| 2119 | help: string""" |
| 2120 | if default not in choicesDict: |
| 2121 | raise Exception( 'Invalid default %s for choices dict: %s' % |
| 2122 | ( default, name ) ) |
| 2123 | if not helpStr: |
| 2124 | helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + |
| 2125 | '[,param=value...]' ) |
| 2126 | opts.add_option( '--' + name, |
| 2127 | type='string', |
| 2128 | default = default, |
| 2129 | help = helpStr ) |
| 2130 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2131 | if __name__ == '__main__': |
| 2132 | setLogLevel( 'info' ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame] | 2133 | app = MiniEdit() |
| 2134 | """ import topology if specified """ |
| 2135 | app.parseArgs() |
| 2136 | app.importTopo() |
| 2137 | |
| 2138 | global isHostPopup |
| 2139 | global isRouterPopup |
| 2140 | global isLinkPopup |
| 2141 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2142 | app.mainloop() |