carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | |
| 3 | """ |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 4 | MiniEdit: a simple network editor for Mininet |
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 | This is a simple demonstration of how one might build a |
| 7 | GUI application using Mininet as the network model. |
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 | Bob Lantz, April 2010 |
| 10 | Gregory Gee, July 2013 |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 11 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 12 | Controller icon from http://semlabs.co.uk/ |
| 13 | OpenFlow icon from https://www.opennetworking.org/ |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 14 | """ |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 15 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 16 | MINIEDIT_VERSION = '2.2.0.1' |
| 17 | |
| 18 | from optparse import OptionParser |
| 19 | from Tkinter import * |
| 20 | from ttk import Notebook |
| 21 | from tkMessageBox import showinfo, showerror, showwarning |
| 22 | from subprocess import call |
| 23 | import tkFont |
| 24 | import csv |
| 25 | import tkFileDialog |
| 26 | import tkSimpleDialog |
| 27 | import re |
| 28 | import json |
| 29 | from distutils.version import StrictVersion |
| 30 | import os |
| 31 | import sys |
| 32 | from functools import partial |
| 33 | |
| 34 | if 'PYTHONPATH' in os.environ: |
| 35 | sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 36 | |
| 37 | # someday: from ttk import * |
| 38 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 39 | from mininet.log import info, error, debug, output, setLogLevel |
| 40 | from mininet.net import Mininet, VERSION |
| 41 | from mininet.util import ipStr, netParse, ipAdd, quietRun |
| 42 | from mininet.util import buildTopo |
| 43 | from mininet.util import custom, customConstructor |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 44 | from mininet.term import makeTerm, cleanUpScreens |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 45 | from mininet.node import Controller, RemoteController, NOX, OVSController |
| 46 | from mininet.node import CPULimitedHost, Host, Node |
| 47 | from mininet.node import OVSKernelSwitch, OVSSwitch, UserSwitch |
| 48 | from mininet.link import TCLink, Intf, Link |
| 49 | from mininet.cli import CLI |
| 50 | from mininet.moduledeps import moduleDeps, pathCheck |
| 51 | from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo |
| 52 | from mininet.topolib import TreeTopo |
| 53 | |
| 54 | print 'MiniEdit running against MiniNet '+VERSION |
| 55 | MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION) |
| 56 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): |
| 57 | from mininet.node import IVSSwitch |
| 58 | |
| 59 | TOPODEF = 'none' |
| 60 | TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), |
| 61 | 'linear': LinearTopo, |
| 62 | 'reversed': SingleSwitchReversedTopo, |
| 63 | 'single': SingleSwitchTopo, |
| 64 | 'none': None, |
| 65 | 'tree': TreeTopo } |
| 66 | LINKDEF = 'default' |
| 67 | LINKS = { 'default': Link, |
| 68 | 'tc': TCLink } |
| 69 | HOSTDEF = 'proc' |
| 70 | HOSTS = { 'proc': Host, |
| 71 | 'rt': custom( CPULimitedHost, sched='rt' ), |
| 72 | 'cfs': custom( CPULimitedHost, sched='cfs' ) } |
| 73 | |
| 74 | class LegacyRouter( Node ): |
| 75 | |
| 76 | def __init__( self, name, inNamespace=True, **params ): |
| 77 | Node.__init__( self, name, inNamespace, **params ) |
| 78 | |
| 79 | def config( self, **_params ): |
| 80 | if self.intfs: |
| 81 | self.setParam( _params, 'setIP', ip='0.0.0.0' ) |
| 82 | r = Node.config( self, **_params ) |
| 83 | self.cmd('sysctl -w net.ipv4.ip_forward=1') |
| 84 | return r |
| 85 | |
| 86 | class CustomDialog(object): |
| 87 | |
| 88 | # TODO: Fix button placement and Title and window focus lock |
| 89 | def __init__(self, master, title): |
| 90 | self.top=Toplevel(master) |
| 91 | |
| 92 | self.bodyFrame = Frame(self.top) |
| 93 | self.bodyFrame.grid(row=0, column=0, sticky='nswe') |
| 94 | self.body(self.bodyFrame) |
| 95 | |
| 96 | #return self.b # initial focus |
| 97 | buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey') |
| 98 | buttonFrame.grid(row=1 , column=0, sticky='nswe') |
| 99 | |
| 100 | okButton = Button(buttonFrame, width=8, text='OK', relief='groove', |
| 101 | bd=4, command=self.okAction) |
| 102 | okButton.grid(row=0, column=0, sticky=E) |
| 103 | |
| 104 | canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove', |
| 105 | bd=4, command=self.cancelAction) |
| 106 | canlceButton.grid(row=0, column=1, sticky=W) |
| 107 | |
| 108 | def body(self, master): |
| 109 | self.rootFrame = master |
| 110 | |
| 111 | def apply(self): |
| 112 | self.top.destroy() |
| 113 | |
| 114 | def cancelAction(self): |
| 115 | self.top.destroy() |
| 116 | |
| 117 | def okAction(self): |
| 118 | self.apply() |
| 119 | self.top.destroy() |
| 120 | |
| 121 | class HostDialog(CustomDialog): |
| 122 | |
| 123 | def __init__(self, master, title, prefDefaults): |
| 124 | |
| 125 | self.prefValues = prefDefaults |
| 126 | self.result = None |
| 127 | |
| 128 | CustomDialog.__init__(self, master, title) |
| 129 | |
| 130 | def body(self, master): |
| 131 | self.rootFrame = master |
| 132 | n = Notebook(self.rootFrame) |
| 133 | self.propFrame = Frame(n) |
| 134 | self.vlanFrame = Frame(n) |
| 135 | self.interfaceFrame = Frame(n) |
| 136 | self.mountFrame = Frame(n) |
| 137 | n.add(self.propFrame, text='Properties') |
| 138 | n.add(self.vlanFrame, text='VLAN Interfaces') |
| 139 | n.add(self.interfaceFrame, text='External Interfaces') |
| 140 | n.add(self.mountFrame, text='Private Directories') |
| 141 | n.pack() |
| 142 | |
| 143 | ### TAB 1 |
| 144 | # Field for Hostname |
| 145 | Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E) |
| 146 | self.hostnameEntry = Entry(self.propFrame) |
| 147 | self.hostnameEntry.grid(row=0, column=1) |
| 148 | if 'hostname' in self.prefValues: |
| 149 | self.hostnameEntry.insert(0, self.prefValues['hostname']) |
| 150 | |
| 151 | # Field for Switch IP |
| 152 | Label(self.propFrame, text="IP Address:").grid(row=1, sticky=E) |
| 153 | self.ipEntry = Entry(self.propFrame) |
| 154 | self.ipEntry.grid(row=1, column=1) |
| 155 | if 'ip' in self.prefValues: |
| 156 | self.ipEntry.insert(0, self.prefValues['ip']) |
| 157 | |
| 158 | # Field for default route |
| 159 | Label(self.propFrame, text="Default Route:").grid(row=2, sticky=E) |
| 160 | self.routeEntry = Entry(self.propFrame) |
| 161 | self.routeEntry.grid(row=2, column=1) |
| 162 | if 'defaultRoute' in self.prefValues: |
| 163 | self.routeEntry.insert(0, self.prefValues['defaultRoute']) |
| 164 | |
| 165 | # Field for CPU |
| 166 | Label(self.propFrame, text="Amount CPU:").grid(row=3, sticky=E) |
| 167 | self.cpuEntry = Entry(self.propFrame) |
| 168 | self.cpuEntry.grid(row=3, column=1) |
| 169 | if 'cpu' in self.prefValues: |
| 170 | self.cpuEntry.insert(0, str(self.prefValues['cpu'])) |
| 171 | # Selection of Scheduler |
| 172 | if 'sched' in self.prefValues: |
| 173 | sched = self.prefValues['sched'] |
| 174 | else: |
| 175 | sched = 'host' |
| 176 | self.schedVar = StringVar(self.propFrame) |
| 177 | self.schedOption = OptionMenu(self.propFrame, self.schedVar, "host", "cfs", "rt") |
| 178 | self.schedOption.grid(row=3, column=2, sticky=W) |
| 179 | self.schedVar.set(sched) |
| 180 | |
| 181 | # Selection of Cores |
| 182 | Label(self.propFrame, text="Cores:").grid(row=4, sticky=E) |
| 183 | self.coreEntry = Entry(self.propFrame) |
| 184 | self.coreEntry.grid(row=4, column=1) |
| 185 | if 'cores' in self.prefValues: |
| 186 | self.coreEntry.insert(1, self.prefValues['cores']) |
| 187 | |
| 188 | # Start command |
| 189 | Label(self.propFrame, text="Start Command:").grid(row=5, sticky=E) |
| 190 | self.startEntry = Entry(self.propFrame) |
| 191 | self.startEntry.grid(row=5, column=1, sticky='nswe', columnspan=3) |
| 192 | if 'startCommand' in self.prefValues: |
| 193 | self.startEntry.insert(0, str(self.prefValues['startCommand'])) |
| 194 | # Stop command |
| 195 | Label(self.propFrame, text="Stop Command:").grid(row=6, sticky=E) |
| 196 | self.stopEntry = Entry(self.propFrame) |
| 197 | self.stopEntry.grid(row=6, column=1, sticky='nswe', columnspan=3) |
| 198 | if 'stopCommand' in self.prefValues: |
| 199 | self.stopEntry.insert(0, str(self.prefValues['stopCommand'])) |
| 200 | |
| 201 | ### TAB 2 |
| 202 | # External Interfaces |
| 203 | self.externalInterfaces = 0 |
| 204 | Label(self.interfaceFrame, text="External Interface:").grid(row=0, column=0, sticky=E) |
| 205 | self.b = Button( self.interfaceFrame, text='Add', command=self.addInterface) |
| 206 | self.b.grid(row=0, column=1) |
| 207 | |
| 208 | self.interfaceFrame = VerticalScrolledTable(self.interfaceFrame, rows=0, columns=1, title='External Interfaces') |
| 209 | self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) |
| 210 | self.tableFrame = self.interfaceFrame.interior |
| 211 | self.tableFrame.addRow(value=['Interface Name'], readonly=True) |
| 212 | |
| 213 | # Add defined interfaces |
| 214 | externalInterfaces = [] |
| 215 | if 'externalInterfaces' in self.prefValues: |
| 216 | externalInterfaces = self.prefValues['externalInterfaces'] |
| 217 | |
| 218 | for externalInterface in externalInterfaces: |
| 219 | self.tableFrame.addRow(value=[externalInterface]) |
| 220 | |
| 221 | ### TAB 3 |
| 222 | # VLAN Interfaces |
| 223 | self.vlanInterfaces = 0 |
| 224 | Label(self.vlanFrame, text="VLAN Interface:").grid(row=0, column=0, sticky=E) |
| 225 | self.vlanButton = Button( self.vlanFrame, text='Add', command=self.addVlanInterface) |
| 226 | self.vlanButton.grid(row=0, column=1) |
| 227 | |
| 228 | self.vlanFrame = VerticalScrolledTable(self.vlanFrame, rows=0, columns=2, title='VLAN Interfaces') |
| 229 | self.vlanFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) |
| 230 | self.vlanTableFrame = self.vlanFrame.interior |
| 231 | self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True) |
| 232 | |
| 233 | vlanInterfaces = [] |
| 234 | if 'vlanInterfaces' in self.prefValues: |
| 235 | vlanInterfaces = self.prefValues['vlanInterfaces'] |
| 236 | for vlanInterface in vlanInterfaces: |
| 237 | self.vlanTableFrame.addRow(value=vlanInterface) |
| 238 | |
| 239 | ### TAB 4 |
| 240 | # Private Directories |
| 241 | self.privateDirectories = 0 |
| 242 | Label(self.mountFrame, text="Private Directory:").grid(row=0, column=0, sticky=E) |
| 243 | self.mountButton = Button( self.mountFrame, text='Add', command=self.addDirectory) |
| 244 | self.mountButton.grid(row=0, column=1) |
| 245 | |
| 246 | self.mountFrame = VerticalScrolledTable(self.mountFrame, rows=0, columns=2, title='Directories') |
| 247 | self.mountFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) |
| 248 | self.mountTableFrame = self.mountFrame.interior |
| 249 | self.mountTableFrame.addRow(value=['Mount','Persistent Directory'], readonly=True) |
| 250 | |
| 251 | directoryList = [] |
| 252 | if 'privateDirectory' in self.prefValues: |
| 253 | directoryList = self.prefValues['privateDirectory'] |
| 254 | for privateDir in directoryList: |
| 255 | if isinstance( privateDir, tuple ): |
| 256 | self.mountTableFrame.addRow(value=privateDir) |
| 257 | else: |
| 258 | self.mountTableFrame.addRow(value=[privateDir,'']) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 259 | |
| 260 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 261 | def addDirectory( self ): |
| 262 | self.mountTableFrame.addRow() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 263 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 264 | def addVlanInterface( self ): |
| 265 | self.vlanTableFrame.addRow() |
| 266 | |
| 267 | def addInterface( self ): |
| 268 | self.tableFrame.addRow() |
| 269 | |
| 270 | def apply(self): |
| 271 | externalInterfaces = [] |
| 272 | for row in range(self.tableFrame.rows): |
| 273 | if (len(self.tableFrame.get(row, 0)) > 0 and |
| 274 | row > 0): |
| 275 | externalInterfaces.append(self.tableFrame.get(row, 0)) |
| 276 | vlanInterfaces = [] |
| 277 | for row in range(self.vlanTableFrame.rows): |
| 278 | if (len(self.vlanTableFrame.get(row, 0)) > 0 and |
| 279 | len(self.vlanTableFrame.get(row, 1)) > 0 and |
| 280 | row > 0): |
| 281 | vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)]) |
| 282 | privateDirectories = [] |
| 283 | for row in range(self.mountTableFrame.rows): |
| 284 | if (len(self.mountTableFrame.get(row, 0)) > 0 and row > 0): |
| 285 | if(len(self.mountTableFrame.get(row, 1)) > 0): |
| 286 | privateDirectories.append((self.mountTableFrame.get(row, 0), self.mountTableFrame.get(row, 1))) |
| 287 | else: |
| 288 | privateDirectories.append(self.mountTableFrame.get(row, 0)) |
| 289 | |
| 290 | results = {'cpu': self.cpuEntry.get(), |
| 291 | 'cores':self.coreEntry.get(), |
| 292 | 'sched':self.schedVar.get(), |
| 293 | 'hostname':self.hostnameEntry.get(), |
| 294 | 'ip':self.ipEntry.get(), |
| 295 | 'defaultRoute':self.routeEntry.get(), |
| 296 | 'startCommand':self.startEntry.get(), |
| 297 | 'stopCommand':self.stopEntry.get(), |
| 298 | 'privateDirectory':privateDirectories, |
| 299 | 'externalInterfaces':externalInterfaces, |
| 300 | 'vlanInterfaces':vlanInterfaces} |
| 301 | self.result = results |
| 302 | |
| 303 | |
| 304 | class VerticalScrolledTable(LabelFrame): |
| 305 | """A pure Tkinter scrollable frame that actually works! |
| 306 | |
| 307 | * Use the 'interior' attribute to place widgets inside the scrollable frame |
| 308 | * Construct and pack/place/grid normally |
| 309 | * This frame only allows vertical scrolling |
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 | """ |
| 312 | def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw): |
| 313 | LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 314 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 315 | # create a canvas object and a vertical scrollbar for scrolling it |
| 316 | vscrollbar = Scrollbar(self, orient=VERTICAL) |
| 317 | vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) |
| 318 | canvas = Canvas(self, bd=0, highlightthickness=0, |
| 319 | yscrollcommand=vscrollbar.set) |
| 320 | canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) |
| 321 | vscrollbar.config(command=canvas.yview) |
| 322 | |
| 323 | # reset the view |
| 324 | canvas.xview_moveto(0) |
| 325 | canvas.yview_moveto(0) |
| 326 | |
| 327 | # create a frame inside the canvas which will be scrolled with it |
| 328 | self.interior = interior = TableFrame(canvas, rows=rows, columns=columns) |
| 329 | interior_id = canvas.create_window(0, 0, window=interior, |
| 330 | anchor=NW) |
| 331 | |
| 332 | # track changes to the canvas and frame width and sync them, |
| 333 | # also updating the scrollbar |
| 334 | def _configure_interior(event): |
| 335 | # update the scrollbars to match the size of the inner frame |
| 336 | size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) |
| 337 | canvas.config(scrollregion="0 0 %s %s" % size) |
| 338 | if interior.winfo_reqwidth() != canvas.winfo_width(): |
| 339 | # update the canvas's width to fit the inner frame |
| 340 | canvas.config(width=interior.winfo_reqwidth()) |
| 341 | interior.bind('<Configure>', _configure_interior) |
| 342 | |
| 343 | def _configure_canvas(event): |
| 344 | if interior.winfo_reqwidth() != canvas.winfo_width(): |
| 345 | # update the inner frame's width to fill the canvas |
| 346 | canvas.itemconfigure(interior_id, width=canvas.winfo_width()) |
| 347 | canvas.bind('<Configure>', _configure_canvas) |
| 348 | |
| 349 | return |
| 350 | |
| 351 | class TableFrame(Frame): |
| 352 | def __init__(self, parent, rows=2, columns=2): |
| 353 | |
| 354 | Frame.__init__(self, parent, background="black") |
| 355 | self._widgets = [] |
| 356 | self.rows = rows |
| 357 | self.columns = columns |
| 358 | for row in range(rows): |
| 359 | current_row = [] |
| 360 | for column in range(columns): |
| 361 | label = Entry(self, borderwidth=0) |
| 362 | label.grid(row=row, column=column, sticky="wens", padx=1, pady=1) |
| 363 | current_row.append(label) |
| 364 | self._widgets.append(current_row) |
| 365 | |
| 366 | def set(self, row, column, value): |
| 367 | widget = self._widgets[row][column] |
| 368 | widget.insert(0, value) |
| 369 | |
| 370 | def get(self, row, column): |
| 371 | widget = self._widgets[row][column] |
| 372 | return widget.get() |
| 373 | |
| 374 | def addRow( self, value=None, readonly=False ): |
| 375 | #print "Adding row " + str(self.rows +1) |
| 376 | current_row = [] |
| 377 | for column in range(self.columns): |
| 378 | label = Entry(self, borderwidth=0) |
| 379 | label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1) |
| 380 | if value is not None: |
| 381 | label.insert(0, value[column]) |
| 382 | if (readonly == True): |
| 383 | label.configure(state='readonly') |
| 384 | current_row.append(label) |
| 385 | self._widgets.append(current_row) |
| 386 | self.update_idletasks() |
| 387 | self.rows += 1 |
| 388 | |
| 389 | class LinkDialog(tkSimpleDialog.Dialog): |
| 390 | |
| 391 | def __init__(self, parent, title, linkDefaults): |
| 392 | |
| 393 | self.linkValues = linkDefaults |
| 394 | |
| 395 | tkSimpleDialog.Dialog.__init__(self, parent, title) |
| 396 | |
| 397 | def body(self, master): |
| 398 | |
| 399 | self.var = StringVar(master) |
| 400 | Label(master, text="Bandwidth:").grid(row=0, sticky=E) |
| 401 | self.e1 = Entry(master) |
| 402 | self.e1.grid(row=0, column=1) |
| 403 | Label(master, text="Mbit").grid(row=0, column=2, sticky=W) |
| 404 | if 'bw' in self.linkValues: |
| 405 | self.e1.insert(0,str(self.linkValues['bw'])) |
| 406 | |
| 407 | Label(master, text="Delay:").grid(row=1, sticky=E) |
| 408 | self.e2 = Entry(master) |
| 409 | self.e2.grid(row=1, column=1) |
| 410 | if 'delay' in self.linkValues: |
| 411 | self.e2.insert(0, self.linkValues['delay']) |
| 412 | |
| 413 | Label(master, text="Loss:").grid(row=2, sticky=E) |
| 414 | self.e3 = Entry(master) |
| 415 | self.e3.grid(row=2, column=1) |
| 416 | Label(master, text="%").grid(row=2, column=2, sticky=W) |
| 417 | if 'loss' in self.linkValues: |
| 418 | self.e3.insert(0, str(self.linkValues['loss'])) |
| 419 | |
| 420 | Label(master, text="Max Queue size:").grid(row=3, sticky=E) |
| 421 | self.e4 = Entry(master) |
| 422 | self.e4.grid(row=3, column=1) |
| 423 | if 'max_queue_size' in self.linkValues: |
| 424 | self.e4.insert(0, str(self.linkValues['max_queue_size'])) |
| 425 | |
| 426 | Label(master, text="Jitter:").grid(row=4, sticky=E) |
| 427 | self.e5 = Entry(master) |
| 428 | self.e5.grid(row=4, column=1) |
| 429 | if 'jitter' in self.linkValues: |
| 430 | self.e5.insert(0, self.linkValues['jitter']) |
| 431 | |
| 432 | Label(master, text="Speedup:").grid(row=5, sticky=E) |
| 433 | self.e6 = Entry(master) |
| 434 | self.e6.grid(row=5, column=1) |
| 435 | if 'speedup' in self.linkValues: |
| 436 | self.e6.insert(0, str(self.linkValues['speedup'])) |
| 437 | |
| 438 | return self.e1 # initial focus |
| 439 | |
| 440 | def apply(self): |
| 441 | self.result = {} |
| 442 | if (len(self.e1.get()) > 0): |
| 443 | self.result['bw'] = int(self.e1.get()) |
| 444 | if (len(self.e2.get()) > 0): |
| 445 | self.result['delay'] = self.e2.get() |
| 446 | if (len(self.e3.get()) > 0): |
| 447 | self.result['loss'] = int(self.e3.get()) |
| 448 | if (len(self.e4.get()) > 0): |
| 449 | self.result['max_queue_size'] = int(self.e4.get()) |
| 450 | if (len(self.e5.get()) > 0): |
| 451 | self.result['jitter'] = self.e5.get() |
| 452 | if (len(self.e6.get()) > 0): |
| 453 | self.result['speedup'] = int(self.e6.get()) |
| 454 | |
| 455 | class ControllerDialog(tkSimpleDialog.Dialog): |
| 456 | |
| 457 | def __init__(self, parent, title, ctrlrDefaults=None): |
| 458 | |
| 459 | if ctrlrDefaults: |
| 460 | self.ctrlrValues = ctrlrDefaults |
| 461 | |
| 462 | tkSimpleDialog.Dialog.__init__(self, parent, title) |
| 463 | |
| 464 | def body(self, master): |
| 465 | |
| 466 | self.var = StringVar(master) |
| 467 | self.protcolvar = StringVar(master) |
| 468 | |
| 469 | rowCount=0 |
| 470 | # Field for Hostname |
| 471 | Label(master, text="Name:").grid(row=rowCount, sticky=E) |
| 472 | self.hostnameEntry = Entry(master) |
| 473 | self.hostnameEntry.grid(row=rowCount, column=1) |
| 474 | self.hostnameEntry.insert(0, self.ctrlrValues['hostname']) |
| 475 | rowCount+=1 |
| 476 | |
| 477 | # Field for Remove Controller Port |
| 478 | Label(master, text="Controller Port:").grid(row=rowCount, sticky=E) |
| 479 | self.e2 = Entry(master) |
| 480 | self.e2.grid(row=rowCount, column=1) |
| 481 | self.e2.insert(0, self.ctrlrValues['remotePort']) |
| 482 | rowCount+=1 |
| 483 | |
| 484 | # Field for Controller Type |
| 485 | Label(master, text="Controller Type:").grid(row=rowCount, sticky=E) |
| 486 | controllerType = self.ctrlrValues['controllerType'] |
| 487 | self.o1 = OptionMenu(master, self.var, "Remote Controller", "In-Band Controller", "OpenFlow Reference", "OVS Controller") |
| 488 | self.o1.grid(row=rowCount, column=1, sticky=W) |
| 489 | if controllerType == 'ref': |
| 490 | self.var.set("OpenFlow Reference") |
| 491 | elif controllerType == 'inband': |
| 492 | self.var.set("In-Band Controller") |
| 493 | elif controllerType == 'remote': |
| 494 | self.var.set("Remote Controller") |
| 495 | else: |
| 496 | self.var.set("OVS Controller") |
| 497 | rowCount+=1 |
| 498 | |
| 499 | # Field for Controller Protcol |
| 500 | Label(master, text="Protocol:").grid(row=rowCount, sticky=E) |
| 501 | if 'controllerProtocol' in self.ctrlrValues: |
| 502 | controllerProtocol = self.ctrlrValues['controllerProtocol'] |
| 503 | else: |
| 504 | controllerProtocol = 'tcp' |
| 505 | self.protcol = OptionMenu(master, self.protcolvar, "TCP", "SSL") |
| 506 | self.protcol.grid(row=rowCount, column=1, sticky=W) |
| 507 | if controllerProtocol == 'ssl': |
| 508 | self.protcolvar.set("SSL") |
| 509 | else: |
| 510 | self.protcolvar.set("TCP") |
| 511 | rowCount+=1 |
| 512 | |
| 513 | # Field for Remove Controller IP |
| 514 | remoteFrame= LabelFrame(master, text='Remote/In-Band Controller', padx=5, pady=5) |
| 515 | remoteFrame.grid(row=rowCount, column=0, columnspan=2, sticky=W) |
| 516 | |
| 517 | Label(remoteFrame, text="IP Address:").grid(row=0, sticky=E) |
| 518 | self.e1 = Entry(remoteFrame) |
| 519 | self.e1.grid(row=0, column=1) |
| 520 | self.e1.insert(0, self.ctrlrValues['remoteIP']) |
| 521 | rowCount+=1 |
| 522 | |
| 523 | return self.hostnameEntry # initial focus |
| 524 | |
| 525 | def apply(self): |
| 526 | self.result = { 'hostname': self.hostnameEntry.get(), |
| 527 | 'remoteIP': self.e1.get(), |
| 528 | 'remotePort': int(self.e2.get())} |
| 529 | |
| 530 | controllerType = self.var.get() |
| 531 | if controllerType == 'Remote Controller': |
| 532 | self.result['controllerType'] = 'remote' |
| 533 | elif controllerType == 'In-Band Controller': |
| 534 | self.result['controllerType'] = 'inband' |
| 535 | elif controllerType == 'OpenFlow Reference': |
| 536 | self.result['controllerType'] = 'ref' |
| 537 | else: |
| 538 | self.result['controllerType'] = 'ovsc' |
| 539 | controllerProtocol = self.protcolvar.get() |
| 540 | if controllerProtocol == 'SSL': |
| 541 | self.result['controllerProtocol'] = 'ssl' |
| 542 | else: |
| 543 | self.result['controllerProtocol'] = 'tcp' |
| 544 | |
| 545 | class ToolTip(object): |
| 546 | |
| 547 | def __init__(self, widget): |
| 548 | self.widget = widget |
| 549 | self.tipwindow = None |
| 550 | self.id = None |
| 551 | self.x = self.y = 0 |
| 552 | |
| 553 | def showtip(self, text): |
| 554 | "Display text in tooltip window" |
| 555 | self.text = text |
| 556 | if self.tipwindow or not self.text: |
| 557 | return |
| 558 | x, y, cx, cy = self.widget.bbox("insert") |
| 559 | x = x + self.widget.winfo_rootx() + 27 |
| 560 | y = y + cy + self.widget.winfo_rooty() +27 |
| 561 | self.tipwindow = tw = Toplevel(self.widget) |
| 562 | tw.wm_overrideredirect(1) |
| 563 | tw.wm_geometry("+%d+%d" % (x, y)) |
| 564 | try: |
| 565 | # For Mac OS |
| 566 | tw.tk.call("::tk::unsupported::MacWindowStyle", |
| 567 | "style", tw._w, |
| 568 | "help", "noActivates") |
| 569 | except TclError: |
| 570 | pass |
| 571 | label = Label(tw, text=self.text, justify=LEFT, |
| 572 | background="#ffffe0", relief=SOLID, borderwidth=1, |
| 573 | font=("tahoma", "8", "normal")) |
| 574 | label.pack(ipadx=1) |
| 575 | |
| 576 | def hidetip(self): |
| 577 | tw = self.tipwindow |
| 578 | self.tipwindow = None |
| 579 | if tw: |
| 580 | tw.destroy() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 581 | |
| 582 | class MiniEdit( Frame ): |
| 583 | |
| 584 | "A simple network editor for Mininet." |
| 585 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 586 | def __init__( self, parent=None, cheight=600, cwidth=1000 ): |
| 587 | |
| 588 | self.defaultIpBase='10.0.0.0/8' |
| 589 | |
| 590 | self.nflowDefaults = {'nflowTarget':'', |
| 591 | 'nflowTimeout':'600', |
| 592 | 'nflowAddId':'0'} |
| 593 | self.sflowDefaults = {'sflowTarget':'', |
| 594 | 'sflowSampling':'400', |
| 595 | 'sflowHeader':'128', |
| 596 | 'sflowPolling':'30'} |
| 597 | |
| 598 | self.appPrefs={ |
| 599 | "ipBase": self.defaultIpBase, |
| 600 | "startCLI": "0", |
| 601 | "terminalType": 'xterm', |
| 602 | "switchType": 'ovs', |
| 603 | "dpctl": '', |
| 604 | 'sflow':self.sflowDefaults, |
| 605 | 'netflow':self.nflowDefaults, |
| 606 | 'openFlowVersions':{'ovsOf10':'1', |
| 607 | 'ovsOf11':'0', |
| 608 | 'ovsOf12':'0', |
| 609 | 'ovsOf13':'0'} |
| 610 | |
| 611 | } |
| 612 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 613 | |
| 614 | Frame.__init__( self, parent ) |
| 615 | self.action = None |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 616 | self.appName = 'MiniEdit' |
| 617 | self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 618 | |
| 619 | # Style |
| 620 | self.font = ( 'Geneva', 9 ) |
| 621 | self.smallFont = ( 'Geneva', 7 ) |
| 622 | self.bg = 'white' |
| 623 | |
| 624 | # Title |
| 625 | self.top = self.winfo_toplevel() |
| 626 | self.top.title( self.appName ) |
| 627 | |
| 628 | # Menu bar |
| 629 | self.createMenubar() |
| 630 | |
| 631 | # Editing canvas |
| 632 | self.cheight, self.cwidth = cheight, cwidth |
| 633 | self.cframe, self.canvas = self.createCanvas() |
| 634 | |
| 635 | # Toolbar |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 636 | self.controllers = {} |
| 637 | |
| 638 | # Toolbar |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 639 | self.images = miniEditImages() |
| 640 | self.buttons = {} |
| 641 | self.active = None |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 642 | self.tools = ( 'Select', 'Host', 'LegacyRouter', 'NetLink' ) |
| 643 | self.customColors = { 'LegacyRouter': 'darkGreen', 'Host': 'blue' } |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 644 | self.toolbar = self.createToolbar() |
| 645 | |
| 646 | # Layout |
| 647 | self.toolbar.grid( column=0, row=0, sticky='nsew') |
| 648 | self.cframe.grid( column=1, row=0 ) |
| 649 | self.columnconfigure( 1, weight=1 ) |
| 650 | self.rowconfigure( 0, weight=1 ) |
| 651 | self.pack( expand=True, fill='both' ) |
| 652 | |
| 653 | # About box |
| 654 | self.aboutBox = None |
| 655 | |
| 656 | # Initialize node data |
| 657 | self.nodeBindings = self.createNodeBindings() |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 658 | self.nodePrefixes = { 'LegacyRouter': 'r', 'Host': 'h'} |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 659 | self.widgetToItem = {} |
| 660 | self.itemToWidget = {} |
| 661 | |
| 662 | # Initialize link tool |
| 663 | self.link = self.linkWidget = None |
| 664 | |
| 665 | # Selection support |
| 666 | self.selection = None |
| 667 | |
| 668 | # Keyboard bindings |
| 669 | self.bind( '<Control-q>', lambda event: self.quit() ) |
| 670 | self.bind( '<KeyPress-Delete>', self.deleteSelection ) |
| 671 | self.bind( '<KeyPress-BackSpace>', self.deleteSelection ) |
| 672 | self.focus() |
| 673 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 674 | #Mouse bindings |
| 675 | |
| 676 | self.bind( '<Button-1>', lambda event: self.clearPopups ) |
| 677 | |
| 678 | self.hostPopup = Menu(self.top, tearoff=0) |
| 679 | self.hostPopup.add_command(label='Host Options', font=self.font) |
| 680 | self.hostPopup.add_separator() |
| 681 | self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails ) |
| 682 | |
| 683 | self.legacyRouterPopup = Menu(self.top, tearoff=0) |
| 684 | self.legacyRouterPopup.add_command(label='Router Options', font=self.font) |
| 685 | |
| 686 | self.linkPopup = Menu(self.top, tearoff=0) |
| 687 | self.linkPopup.add_command(label='Link Options', font=self.font) |
| 688 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 689 | # Event handling initalization |
| 690 | self.linkx = self.linky = self.linkItem = None |
| 691 | self.lastSelection = None |
| 692 | |
| 693 | # Model initialization |
| 694 | self.links = {} |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 695 | self.hostOpts = {} |
| 696 | self.switchOpts = {} |
| 697 | self.hostCount = 0 |
| 698 | self.switchCount = 0 |
| 699 | self.controllerCount = 0 |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 700 | self.net = None |
| 701 | |
| 702 | # Close window gracefully |
| 703 | Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit ) |
| 704 | |
| 705 | def quit( self ): |
| 706 | "Stop our network, if any, then quit." |
| 707 | self.stop() |
| 708 | Frame.quit( self ) |
| 709 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 710 | def createMenubar( self ): # MODIFICADO - OK |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 711 | "Create our menu bar." |
| 712 | |
| 713 | font = self.font |
| 714 | |
| 715 | mbar = Menu( self.top, font=font ) |
| 716 | self.top.configure( menu=mbar ) |
| 717 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 718 | |
| 719 | #fileMenu = Menu( mbar, tearoff=False ) |
| 720 | #mbar.add_cascade( label="File", font=font, menu=fileMenu ) |
| 721 | #fileMenu.add_command( label="New", font=font, command=self.newTopology ) |
| 722 | #fileMenu.add_command( label="Open", font=font, command=self.loadTopology ) |
| 723 | #fileMenu.add_command( label="Save", font=font, command=self.saveTopology ) |
| 724 | #fileMenu.add_command( label="Export Level 2 Script", font=font, command=self.exportScript ) |
| 725 | #fileMenu.add_separator() |
| 726 | #fileMenu.add_command( label='Quit', command=self.quit, font=font ) |
| 727 | |
| 728 | #editMenu.add_command( label="Preferences", font=font, command=self.prefDetails) |
| 729 | |
| 730 | #runMenu = Menu( mbar, tearoff=False ) |
| 731 | #mbar.add_cascade( label="Run", font=font, menu=runMenu ) |
| 732 | #runMenu.add_command( label="Run", font=font, command=self.doRun ) |
| 733 | #runMenu.add_command( label="Stop", font=font, command=self.doStop ) |
| 734 | #fileMenu.add_separator() |
| 735 | #runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow ) |
| 736 | #runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal ) |
| 737 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 738 | # Application menu |
| 739 | appMenu = Menu( mbar, tearoff=False ) |
| 740 | mbar.add_cascade( label=self.appName, font=font, menu=appMenu ) |
| 741 | appMenu.add_command( label='About Mini-CCNx', command=self.about, |
| 742 | font=font) |
| 743 | appMenu.add_separator() |
| 744 | appMenu.add_command( label='Quit', command=self.quit, font=font ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 745 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 746 | editMenu = Menu( mbar, tearoff=False ) |
| 747 | mbar.add_cascade( label="Edit", font=font, menu=editMenu ) |
| 748 | editMenu.add_command( label="Cut", font=font, |
| 749 | command=lambda: self.deleteSelection( None ) ) |
| 750 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 751 | # Canvas - TUDO IGUAL - OK |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 752 | |
| 753 | def createCanvas( self ): |
| 754 | "Create and return our scrolling canvas frame." |
| 755 | f = Frame( self ) |
| 756 | |
| 757 | canvas = Canvas( f, width=self.cwidth, height=self.cheight, |
| 758 | bg=self.bg ) |
| 759 | |
| 760 | # Scroll bars |
| 761 | xbar = Scrollbar( f, orient='horizontal', command=canvas.xview ) |
| 762 | ybar = Scrollbar( f, orient='vertical', command=canvas.yview ) |
| 763 | canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set ) |
| 764 | |
| 765 | # Resize box |
| 766 | resize = Label( f, bg='white' ) |
| 767 | |
| 768 | # Layout |
| 769 | canvas.grid( row=0, column=1, sticky='nsew') |
| 770 | ybar.grid( row=0, column=2, sticky='ns') |
| 771 | xbar.grid( row=1, column=1, sticky='ew' ) |
| 772 | resize.grid( row=1, column=2, sticky='nsew' ) |
| 773 | |
| 774 | # Resize behavior |
| 775 | f.rowconfigure( 0, weight=1 ) |
| 776 | f.columnconfigure( 1, weight=1 ) |
| 777 | f.grid( row=0, column=0, sticky='nsew' ) |
| 778 | f.bind( '<Configure>', lambda event: self.updateScrollRegion() ) |
| 779 | |
| 780 | # Mouse bindings |
| 781 | canvas.bind( '<ButtonPress-1>', self.clickCanvas ) |
| 782 | canvas.bind( '<B1-Motion>', self.dragCanvas ) |
| 783 | canvas.bind( '<ButtonRelease-1>', self.releaseCanvas ) |
| 784 | |
| 785 | return f, canvas |
| 786 | |
| 787 | def updateScrollRegion( self ): |
| 788 | "Update canvas scroll region to hold everything." |
| 789 | bbox = self.canvas.bbox( 'all' ) |
| 790 | if bbox is not None: |
| 791 | self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], |
| 792 | bbox[ 3 ] ) ) |
| 793 | |
| 794 | def canvasx( self, x_root ): |
| 795 | "Convert root x coordinate to canvas coordinate." |
| 796 | c = self.canvas |
| 797 | return c.canvasx( x_root ) - c.winfo_rootx() |
| 798 | |
| 799 | def canvasy( self, y_root ): |
| 800 | "Convert root y coordinate to canvas coordinate." |
| 801 | c = self.canvas |
| 802 | return c.canvasy( y_root ) - c.winfo_rooty() |
| 803 | |
| 804 | # Toolbar |
| 805 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 806 | def activate( self, toolName ): #IGUAL - OK |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 807 | "Activate a tool and press its button." |
| 808 | # Adjust button appearance |
| 809 | if self.active: |
| 810 | self.buttons[ self.active ].configure( relief='raised' ) |
| 811 | self.buttons[ toolName ].configure( relief='sunken' ) |
| 812 | # Activate dynamic bindings |
| 813 | self.active = toolName |
| 814 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 815 | |
| 816 | def createToolTip(self, widget, text): #NOVA - CRIA HINTS E TIPS |
| 817 | toolTip = ToolTip(widget) |
| 818 | def enter(event): |
| 819 | toolTip.showtip(text) |
| 820 | def leave(event): |
| 821 | toolTip.hidetip() |
| 822 | widget.bind('<Enter>', enter) |
| 823 | widget.bind('<Leave>', leave) |
| 824 | |
| 825 | def createToolbar( self ): #MODIFICADO - OK |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 826 | "Create and return our toolbar frame." |
| 827 | |
| 828 | toolbar = Frame( self ) |
| 829 | |
| 830 | # Tools |
| 831 | for tool in self.tools: |
| 832 | cmd = ( lambda t=tool: self.activate( t ) ) |
| 833 | b = Button( toolbar, text=tool, font=self.smallFont, command=cmd) |
| 834 | if tool in self.images: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 835 | b.config( height=35, image=self.images[ tool ] ) |
| 836 | self.createToolTip(b, str(tool)) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 837 | # b.config( compound='top' ) |
| 838 | b.pack( fill='x' ) |
| 839 | self.buttons[ tool ] = b |
| 840 | self.activate( self.tools[ 0 ] ) |
| 841 | |
| 842 | # Spacer |
| 843 | Label( toolbar, text='' ).pack() |
| 844 | |
| 845 | # Commands |
| 846 | #for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]: |
| 847 | # doCmd = getattr( self, 'do' + cmd ) |
| 848 | # b = Button( toolbar, text=cmd, font=self.smallFont, |
| 849 | # fg=color, command=doCmd ) |
| 850 | # b.pack( fill='x', side='bottom' ) |
| 851 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 852 | # abaixo copiado Mini-CCNx para criar botao Generate |
| 853 | |
| 854 | for cmd, color in [ ( 'Generate', 'darkGreen' ) ]: |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 855 | doCmd = getattr( self, 'do' + cmd ) |
| 856 | b = Button( toolbar, text=cmd, font=self.smallFont, |
| 857 | fg=color, command=doCmd ) |
| 858 | b.pack( fill='x', side='bottom' ) |
| 859 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 860 | return toolbar |
| 861 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 862 | def doGenerate( self ): #COPIA Mini-CCNx - GERA TEMPLATE |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 863 | "Generate template." |
| 864 | self.activate( 'Select' ) |
| 865 | for tool in self.tools: |
| 866 | self.buttons[ tool ].config( state='disabled' ) |
| 867 | |
| 868 | self.buildTemplate() |
| 869 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 870 | for tool in self.tools: |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 871 | self.buttons[ tool ].config( state='normal' ) |
| 872 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 873 | #def doRun( self ): |
| 874 | # "Run command." |
| 875 | # self.activate( 'Select' ) |
| 876 | # for tool in self.tools: |
| 877 | # self.buttons[ tool ].config( state='disabled' ) |
| 878 | # self.start() |
| 879 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 880 | def doStop( self ): |
| 881 | "Stop command." |
| 882 | self.stop() |
| 883 | for tool in self.tools: |
| 884 | self.buttons[ tool ].config( state='normal' ) |
| 885 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 886 | def buildTemplate( self ): #COPIA Mini-CCNx para criar Template |
| 887 | "Generate template" |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 888 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 889 | template = open(self.template_file, 'w') |
| 890 | |
| 891 | # hosts |
| 892 | template.write('[hosts]\n') |
| 893 | for widget in self.widgetToItem: |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 894 | name = widget[ 'text' ] |
| 895 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 896 | if 'Host' in tags: |
| 897 | template.write(name + ':\n') |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 898 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 899 | # switches/routers |
| 900 | template.write('[routers]\n') |
| 901 | for widget in self.widgetToItem: |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 902 | name = widget[ 'text' ] |
| 903 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 904 | if 'Switch' in tags: |
| 905 | template.write(name + ':\n') |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 906 | |
| 907 | # Make links |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 908 | template.write('[links]\n') |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 909 | for link in self.links.values(): |
| 910 | ( src, dst ) = link |
| 911 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 912 | template.write(srcName + ':' + dstName + '\n') |
| 913 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 914 | template.close() |
| 915 | |
| 916 | def addNode( self, node, nodeNum, x, y, name=None): |
| 917 | "Add a new node to our canvas." |
| 918 | if 'Switch' == node: |
| 919 | self.switchCount += 1 |
| 920 | if 'Host' == node: |
| 921 | self.hostCount += 1 |
| 922 | if 'Controller' == node: |
| 923 | self.controllerCount += 1 |
| 924 | if name is None: |
| 925 | name = self.nodePrefixes[ node ] + nodeNum |
| 926 | self.addNamedNode(node, name, x, y) |
| 927 | |
| 928 | def addNamedNode( self, node, name, x, y): |
| 929 | "Add a new node to our canvas." |
| 930 | c = self.canvas |
| 931 | icon = self.nodeIcon( node, name ) |
| 932 | item = self.canvas.create_window( x, y, anchor='c', window=icon, |
| 933 | tags=node ) |
| 934 | self.widgetToItem[ icon ] = item |
| 935 | self.itemToWidget[ item ] = icon |
| 936 | icon.links = {} |
| 937 | |
| 938 | def convertJsonUnicode(self, input): |
| 939 | "Some part of Mininet don't like Unicode" |
| 940 | if isinstance(input, dict): |
| 941 | return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in input.iteritems()} |
| 942 | elif isinstance(input, list): |
| 943 | return [self.convertJsonUnicode(element) for element in input] |
| 944 | elif isinstance(input, unicode): |
| 945 | return input.encode('utf-8') |
| 946 | else: |
| 947 | return input |
| 948 | |
| 949 | def loadTopology( self ): |
| 950 | "Load command." |
| 951 | c = self.canvas |
| 952 | |
| 953 | myFormats = [ |
| 954 | ('Mininet Topology','*.mn'), |
| 955 | ('All Files','*'), |
| 956 | ] |
| 957 | f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb') |
| 958 | if f == None: |
| 959 | return |
| 960 | self.newTopology() |
| 961 | loadedTopology = self.convertJsonUnicode(json.load(f)) |
| 962 | |
| 963 | # Load application preferences |
| 964 | if 'application' in loadedTopology: |
| 965 | self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items()) |
| 966 | if "ovsOf10" not in self.appPrefs["openFlowVersions"]: |
| 967 | self.appPrefs["openFlowVersions"]["ovsOf10"] = '0' |
| 968 | if "ovsOf11" not in self.appPrefs["openFlowVersions"]: |
| 969 | self.appPrefs["openFlowVersions"]["ovsOf11"] = '0' |
| 970 | if "ovsOf12" not in self.appPrefs["openFlowVersions"]: |
| 971 | self.appPrefs["openFlowVersions"]["ovsOf12"] = '0' |
| 972 | if "ovsOf13" not in self.appPrefs["openFlowVersions"]: |
| 973 | self.appPrefs["openFlowVersions"]["ovsOf13"] = '0' |
| 974 | if "sflow" not in self.appPrefs: |
| 975 | self.appPrefs["sflow"] = self.sflowDefaults |
| 976 | if "netflow" not in self.appPrefs: |
| 977 | self.appPrefs["netflow"] = self.nflowDefaults |
| 978 | |
| 979 | # Load controllers |
| 980 | if ('controllers' in loadedTopology): |
| 981 | if (loadedTopology['version'] == '1'): |
| 982 | # This is old location of controller info |
| 983 | hostname = 'c0' |
| 984 | self.controllers = {} |
| 985 | self.controllers[hostname] = loadedTopology['controllers']['c0'] |
| 986 | self.controllers[hostname]['hostname'] = hostname |
| 987 | #elf.addNode('Controller', 0, float(30), float(30), name=hostname) |
| 988 | icon = self.findWidgetByName(hostname) |
| 989 | #con.bind('<Button-3>', self.do_controllerPopup ) |
| 990 | else: |
| 991 | controllers = loadedTopology['controllers'] |
| 992 | for controller in controllers: |
| 993 | hostname = controller['opts']['hostname'] |
| 994 | x = controller['x'] |
| 995 | y = controller['y'] |
| 996 | self.addNode('Controller', 0, float(x), float(y), name=hostname) |
| 997 | self.controllers[hostname] = controller['opts'] |
| 998 | icon = self.findWidgetByName(hostname) |
| 999 | #con.bind('<Button-3>', self.do_controllerPopup ) |
| 1000 | |
| 1001 | |
| 1002 | # Load hosts |
| 1003 | hosts = loadedTopology['hosts'] |
| 1004 | for host in hosts: |
| 1005 | nodeNum = host['number'] |
| 1006 | hostname = 'h'+nodeNum |
| 1007 | if 'hostname' in host['opts']: |
| 1008 | hostname = host['opts']['hostname'] |
| 1009 | else: |
| 1010 | host['opts']['hostname'] = hostname |
| 1011 | if 'nodeNum' not in host['opts']: |
| 1012 | host['opts']['nodeNum'] = int(nodeNum) |
| 1013 | x = host['x'] |
| 1014 | y = host['y'] |
| 1015 | self.addNode('Host', nodeNum, float(x), float(y), name=hostname) |
| 1016 | |
| 1017 | # Fix JSON converting tuple to list when saving |
| 1018 | if 'privateDirectory' in host['opts']: |
| 1019 | newDirList = [] |
| 1020 | for privateDir in host['opts']['privateDirectory']: |
| 1021 | if isinstance( privateDir, list ): |
| 1022 | newDirList.append((privateDir[0],privateDir[1])) |
| 1023 | else: |
| 1024 | newDirList.append(privateDir) |
| 1025 | host['opts']['privateDirectory'] = newDirList |
| 1026 | self.hostOpts[hostname] = host['opts'] |
| 1027 | icon = self.findWidgetByName(hostname) |
| 1028 | icon.bind('<Button-3>', self.do_hostPopup ) |
| 1029 | |
| 1030 | # Load switches |
| 1031 | switches = loadedTopology['switches'] |
| 1032 | for switch in switches: |
| 1033 | nodeNum = switch['number'] |
| 1034 | hostname = 's'+nodeNum |
| 1035 | if 'controllers' not in switch['opts']: |
| 1036 | switch['opts']['controllers'] = [] |
| 1037 | if 'switchType' not in switch['opts']: |
| 1038 | switch['opts']['switchType'] = 'default' |
| 1039 | if 'hostname' in switch['opts']: |
| 1040 | hostname = switch['opts']['hostname'] |
| 1041 | else: |
| 1042 | switch['opts']['hostname'] = hostname |
| 1043 | if 'nodeNum' not in switch['opts']: |
| 1044 | switch['opts']['nodeNum'] = int(nodeNum) |
| 1045 | x = switch['x'] |
| 1046 | y = switch['y'] |
| 1047 | if switch['opts']['switchType'] == "legacyRouter": |
| 1048 | self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname) |
| 1049 | icon = self.findWidgetByName(hostname) |
| 1050 | icon.bind('<Button-3>', self.do_legacyRouterPopup ) |
| 1051 | |
| 1052 | # Load links |
| 1053 | links = loadedTopology['links'] |
| 1054 | for link in links: |
| 1055 | srcNode = link['src'] |
| 1056 | src = self.findWidgetByName(srcNode) |
| 1057 | sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) |
| 1058 | |
| 1059 | destNode = link['dest'] |
| 1060 | dest = self.findWidgetByName(destNode) |
| 1061 | dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) |
| 1062 | |
| 1063 | self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, |
| 1064 | fill='blue', tag='link' ) |
| 1065 | c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) |
| 1066 | self.addLink( src, dest, linkopts=link['opts'] ) |
| 1067 | self.createDataLinkBindings() |
| 1068 | self.link = self.linkWidget = None |
| 1069 | |
| 1070 | f.close |
| 1071 | |
| 1072 | def findWidgetByName( self, name ): |
| 1073 | for widget in self.widgetToItem: |
| 1074 | if name == widget[ 'text' ]: |
| 1075 | return widget |
| 1076 | |
| 1077 | def newTopology( self ): |
| 1078 | "New command." |
| 1079 | for widget in self.widgetToItem.keys(): |
| 1080 | self.deleteItem( self.widgetToItem[ widget ] ) |
| 1081 | self.hostCount = 0 |
| 1082 | self.switchCount = 0 |
| 1083 | self.links = {} |
| 1084 | self.hostOpts = {} |
| 1085 | self.switchOpts = {} |
| 1086 | self.appPrefs["ipBase"]= self.defaultIpBase |
| 1087 | |
| 1088 | def saveTopology( self ): |
| 1089 | "Save command." |
| 1090 | myFormats = [ |
| 1091 | ('Mininet Topology','*.mn'), |
| 1092 | ('All Files','*'), |
| 1093 | ] |
| 1094 | |
| 1095 | savingDictionary = {} |
| 1096 | fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...") |
| 1097 | if len(fileName ) > 0: |
| 1098 | # Save Application preferences |
| 1099 | savingDictionary['version'] = '2' |
| 1100 | |
| 1101 | # Save Switches and Hosts |
| 1102 | hostsToSave = [] |
| 1103 | switchesToSave = [] |
| 1104 | controllersToSave = [] |
| 1105 | for widget in self.widgetToItem: |
| 1106 | name = widget[ 'text' ] |
| 1107 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1108 | x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) |
| 1109 | if 'LegacyRouter' in tags: |
| 1110 | nodeNum = self.switchOpts[name]['nodeNum'] |
| 1111 | nodeToSave = {'number':str(nodeNum), |
| 1112 | 'x':str(x1), |
| 1113 | 'y':str(y1), |
| 1114 | 'opts':self.switchOpts[name] } |
| 1115 | switchesToSave.append(nodeToSave) |
| 1116 | elif 'Host' in tags: |
| 1117 | nodeNum = self.hostOpts[name]['nodeNum'] |
| 1118 | nodeToSave = {'number':str(nodeNum), |
| 1119 | 'x':str(x1), |
| 1120 | 'y':str(y1), |
| 1121 | 'opts':self.hostOpts[name] } |
| 1122 | hostsToSave.append(nodeToSave) |
| 1123 | else: |
| 1124 | raise Exception( "Cannot create mystery node: " + name ) |
| 1125 | savingDictionary['hosts'] = hostsToSave |
| 1126 | savingDictionary['switches'] = switchesToSave |
| 1127 | savingDictionary['controllers'] = controllersToSave |
| 1128 | |
| 1129 | # Save Links |
| 1130 | linksToSave = [] |
| 1131 | for link in self.links.values(): |
| 1132 | src = link['src'] |
| 1133 | dst = link['dest'] |
| 1134 | linkopts = link['linkOpts'] |
| 1135 | |
| 1136 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 1137 | linkToSave = {'src':srcName, |
| 1138 | 'dest':dstName, |
| 1139 | 'opts':linkopts} |
| 1140 | if link['type'] == 'data': |
| 1141 | linksToSave.append(linkToSave) |
| 1142 | savingDictionary['links'] = linksToSave |
| 1143 | |
| 1144 | # Save Application preferences |
| 1145 | savingDictionary['application'] = self.appPrefs |
| 1146 | |
| 1147 | try: |
| 1148 | f = open(fileName, 'wb') |
| 1149 | f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': '))) |
| 1150 | except Exception as er: |
| 1151 | print er |
| 1152 | finally: |
| 1153 | f.close() |
| 1154 | |
| 1155 | def exportScript( self ): |
| 1156 | "Export command." |
| 1157 | myFormats = [ |
| 1158 | ('Mininet Custom Topology','*.py'), |
| 1159 | ('All Files','*'), |
| 1160 | ] |
| 1161 | |
| 1162 | fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...") |
| 1163 | if len(fileName ) > 0: |
| 1164 | #print "Now saving under %s" % fileName |
| 1165 | f = open(fileName, 'wb') |
| 1166 | |
| 1167 | f.write("#!/usr/bin/python\n") |
| 1168 | f.write("\n") |
| 1169 | f.write("from mininet.net import Mininet\n") |
| 1170 | f.write("from mininet.node import Controller, RemoteController, OVSController\n") |
| 1171 | f.write("from mininet.node import CPULimitedHost, Host, Node\n") |
| 1172 | f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n") |
| 1173 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): |
| 1174 | f.write("from mininet.node import IVSSwitch\n") |
| 1175 | f.write("from mininet.cli import CLI\n") |
| 1176 | f.write("from mininet.log import setLogLevel, info\n") |
| 1177 | f.write("from mininet.link import TCLink, Intf\n") |
| 1178 | f.write("from subprocess import call\n") |
| 1179 | |
| 1180 | inBandCtrl = False |
| 1181 | hasLegacySwitch = False |
| 1182 | for widget in self.widgetToItem: |
| 1183 | name = widget[ 'text' ] |
| 1184 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1185 | |
| 1186 | if 'Controller' in tags: |
| 1187 | opts = self.controllers[name] |
| 1188 | controllerType = opts['controllerType'] |
| 1189 | if controllerType == 'inband': |
| 1190 | inBandCtrl = True |
| 1191 | |
| 1192 | if inBandCtrl == True: |
| 1193 | f.write("\n") |
| 1194 | f.write("class InbandController( RemoteController ):\n") |
| 1195 | f.write("\n") |
| 1196 | f.write(" def checkListening( self ):\n") |
| 1197 | f.write(" \"Overridden to do nothing.\"\n") |
| 1198 | f.write(" return\n") |
| 1199 | |
| 1200 | f.write("\n") |
| 1201 | f.write("def myNetwork():\n") |
| 1202 | f.write("\n") |
| 1203 | f.write(" net = Mininet( topo=None,\n") |
| 1204 | if len(self.appPrefs['dpctl']) > 0: |
| 1205 | f.write(" listenPort="+self.appPrefs['dpctl']+",\n") |
| 1206 | f.write(" build=False,\n") |
| 1207 | f.write(" ipBase='"+self.appPrefs['ipBase']+"')\n") |
| 1208 | f.write("\n") |
| 1209 | f.write(" info( '*** Adding controller\\n' )\n") |
| 1210 | for widget in self.widgetToItem: |
| 1211 | name = widget[ 'text' ] |
| 1212 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1213 | |
| 1214 | if 'Controller' in tags: |
| 1215 | opts = self.controllers[name] |
| 1216 | controllerType = opts['controllerType'] |
| 1217 | if 'controllerProtocol' in opts: |
| 1218 | controllerProtocol = opts['controllerProtocol'] |
| 1219 | else: |
| 1220 | controllerProtocol = 'tcp' |
| 1221 | controllerIP = opts['remoteIP'] |
| 1222 | controllerPort = opts['remotePort'] |
| 1223 | |
| 1224 | |
| 1225 | f.write(" "+name+"=net.addController(name='"+name+"',\n") |
| 1226 | |
| 1227 | if controllerType == 'remote': |
| 1228 | f.write(" controller=RemoteController,\n") |
| 1229 | f.write(" ip='"+controllerIP+"',\n") |
| 1230 | elif controllerType == 'inband': |
| 1231 | f.write(" controller=InbandController,\n") |
| 1232 | f.write(" ip='"+controllerIP+"',\n") |
| 1233 | elif controllerType == 'ovsc': |
| 1234 | f.write(" controller=OVSController,\n") |
| 1235 | else: |
| 1236 | f.write(" controller=Controller,\n") |
| 1237 | |
| 1238 | f.write(" protocol='"+controllerProtocol+"',\n") |
| 1239 | f.write(" port="+str(controllerPort)+")\n") |
| 1240 | f.write("\n") |
| 1241 | |
| 1242 | # Save Switches and Hosts |
| 1243 | f.write(" info( '*** Add switches\\n')\n") |
| 1244 | for widget in self.widgetToItem: |
| 1245 | name = widget[ 'text' ] |
| 1246 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1247 | if 'LegacyRouter' in tags: |
| 1248 | f.write(" "+name+" = net.addHost('"+name+"', cls=Node, ip='0.0.0.0')\n") |
| 1249 | f.write(" "+name+".cmd('sysctl -w net.ipv4.ip_forward=1')\n") |
| 1250 | if 'LegacySwitch' in tags: |
| 1251 | f.write(" "+name+" = net.addSwitch('"+name+"', cls=OVSKernelSwitch, failMode='standalone')\n") |
| 1252 | if 'Switch' in tags: |
| 1253 | opts = self.switchOpts[name] |
| 1254 | nodeNum = opts['nodeNum'] |
| 1255 | f.write(" "+name+" = net.addSwitch('"+name+"'") |
| 1256 | if opts['switchType'] == 'default': |
| 1257 | if self.appPrefs['switchType'] == 'ivs': |
| 1258 | f.write(", cls=IVSSwitch") |
| 1259 | elif self.appPrefs['switchType'] == 'user': |
| 1260 | f.write(", cls=UserSwitch") |
| 1261 | elif self.appPrefs['switchType'] == 'userns': |
| 1262 | f.write(", cls=UserSwitch, inNamespace=True") |
| 1263 | else: |
| 1264 | f.write(", cls=OVSKernelSwitch") |
| 1265 | elif opts['switchType'] == 'ivs': |
| 1266 | f.write(", cls=IVSSwitch") |
| 1267 | elif opts['switchType'] == 'user': |
| 1268 | f.write(", cls=UserSwitch") |
| 1269 | elif opts['switchType'] == 'userns': |
| 1270 | f.write(", cls=UserSwitch, inNamespace=True") |
| 1271 | else: |
| 1272 | f.write(", cls=OVSKernelSwitch") |
| 1273 | if 'dpctl' in opts: |
| 1274 | f.write(", listenPort="+opts['dpctl']) |
| 1275 | if 'dpid' in opts: |
| 1276 | f.write(", dpid='"+opts['dpid']+"'") |
| 1277 | f.write(")\n") |
| 1278 | if ('externalInterfaces' in opts): |
| 1279 | for extInterface in opts['externalInterfaces']: |
| 1280 | f.write(" Intf( '"+extInterface+"', node="+name+" )\n") |
| 1281 | |
| 1282 | f.write("\n") |
| 1283 | f.write(" info( '*** Add hosts\\n')\n") |
| 1284 | for widget in self.widgetToItem: |
| 1285 | name = widget[ 'text' ] |
| 1286 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1287 | if 'Host' in tags: |
| 1288 | opts = self.hostOpts[name] |
| 1289 | ip = None |
| 1290 | defaultRoute = None |
| 1291 | if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: |
| 1292 | defaultRoute = "'via "+opts['defaultRoute']+"'" |
| 1293 | else: |
| 1294 | defaultRoute = 'None' |
| 1295 | if 'ip' in opts and len(opts['ip']) > 0: |
| 1296 | ip = opts['ip'] |
| 1297 | else: |
| 1298 | nodeNum = self.hostOpts[name]['nodeNum'] |
| 1299 | ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) |
| 1300 | ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) |
| 1301 | |
| 1302 | if 'cores' in opts or 'cpu' in opts: |
| 1303 | f.write(" "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n") |
| 1304 | if 'cores' in opts: |
| 1305 | f.write(" "+name+".setCPUs(cores='"+opts['cores']+"')\n") |
| 1306 | if 'cpu' in opts: |
| 1307 | f.write(" "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n") |
| 1308 | else: |
| 1309 | f.write(" "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n") |
| 1310 | if ('externalInterfaces' in opts): |
| 1311 | for extInterface in opts['externalInterfaces']: |
| 1312 | f.write(" Intf( '"+extInterface+"', node="+name+" )\n") |
| 1313 | f.write("\n") |
| 1314 | |
| 1315 | # Save Links |
| 1316 | f.write(" info( '*** Add links\\n')\n") |
| 1317 | for key,linkDetail in self.links.iteritems(): |
| 1318 | tags = self.canvas.gettags(key) |
| 1319 | if 'data' in tags: |
| 1320 | optsExist = False |
| 1321 | src = linkDetail['src'] |
| 1322 | dst = linkDetail['dest'] |
| 1323 | linkopts = linkDetail['linkOpts'] |
| 1324 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 1325 | bw = '' |
| 1326 | delay = '' |
| 1327 | loss = '' |
| 1328 | max_queue_size = '' |
| 1329 | linkOpts = "{" |
| 1330 | if 'bw' in linkopts: |
| 1331 | bw = linkopts['bw'] |
| 1332 | linkOpts = linkOpts + "'bw':"+str(bw) |
| 1333 | optsExist = True |
| 1334 | if 'delay' in linkopts: |
| 1335 | delay = linkopts['delay'] |
| 1336 | if optsExist: |
| 1337 | linkOpts = linkOpts + "," |
| 1338 | linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'" |
| 1339 | optsExist = True |
| 1340 | if 'loss' in linkopts: |
| 1341 | if optsExist: |
| 1342 | linkOpts = linkOpts + "," |
| 1343 | linkOpts = linkOpts + "'loss':"+str(linkopts['loss']) |
| 1344 | optsExist = True |
| 1345 | if 'max_queue_size' in linkopts: |
| 1346 | if optsExist: |
| 1347 | linkOpts = linkOpts + "," |
| 1348 | linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size']) |
| 1349 | optsExist = True |
| 1350 | if 'jitter' in linkopts: |
| 1351 | if optsExist: |
| 1352 | linkOpts = linkOpts + "," |
| 1353 | linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'" |
| 1354 | optsExist = True |
| 1355 | if 'speedup' in linkopts: |
| 1356 | if optsExist: |
| 1357 | linkOpts = linkOpts + "," |
| 1358 | linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup']) |
| 1359 | optsExist = True |
| 1360 | |
| 1361 | linkOpts = linkOpts + "}" |
| 1362 | if optsExist: |
| 1363 | f.write(" "+srcName+dstName+" = "+linkOpts+"\n") |
| 1364 | f.write(" net.addLink("+srcName+", "+dstName) |
| 1365 | if optsExist: |
| 1366 | f.write(", cls=TCLink , **"+srcName+dstName) |
| 1367 | f.write(")\n") |
| 1368 | |
| 1369 | f.write("\n") |
| 1370 | f.write(" info( '*** Starting network\\n')\n") |
| 1371 | f.write(" net.build()\n") |
| 1372 | |
| 1373 | f.write(" info( '*** Starting controllers\\n')\n") |
| 1374 | f.write(" for controller in net.controllers:\n") |
| 1375 | f.write(" controller.start()\n") |
| 1376 | f.write("\n") |
| 1377 | |
| 1378 | f.write(" info( '*** Starting switches\\n')\n") |
| 1379 | for widget in self.widgetToItem: |
| 1380 | name = widget[ 'text' ] |
| 1381 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1382 | if 'Switch' in tags or 'LegacySwitch' in tags: |
| 1383 | opts = self.switchOpts[name] |
| 1384 | ctrlList = ",".join(opts['controllers']) |
| 1385 | f.write(" net.get('"+name+"').start(["+ctrlList+"])\n") |
| 1386 | |
| 1387 | f.write("\n") |
| 1388 | |
| 1389 | f.write(" info( '*** Post configure switches and hosts\\n')\n") |
| 1390 | for widget in self.widgetToItem: |
| 1391 | name = widget[ 'text' ] |
| 1392 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1393 | if 'Switch' in tags: |
| 1394 | opts = self.switchOpts[name] |
| 1395 | if opts['switchType'] == 'default': |
| 1396 | if self.appPrefs['switchType'] == 'user': |
| 1397 | if ('switchIP' in opts): |
| 1398 | if (len(opts['switchIP'])>0): |
| 1399 | f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") |
| 1400 | elif self.appPrefs['switchType'] == 'userns': |
| 1401 | if ('switchIP' in opts): |
| 1402 | if (len(opts['switchIP'])>0): |
| 1403 | f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n") |
| 1404 | elif self.appPrefs['switchType'] == 'ovs': |
| 1405 | if ('switchIP' in opts): |
| 1406 | if (len(opts['switchIP'])>0): |
| 1407 | f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") |
| 1408 | elif opts['switchType'] == 'user': |
| 1409 | if ('switchIP' in opts): |
| 1410 | if (len(opts['switchIP'])>0): |
| 1411 | f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") |
| 1412 | elif opts['switchType'] == 'userns': |
| 1413 | if ('switchIP' in opts): |
| 1414 | if (len(opts['switchIP'])>0): |
| 1415 | f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n") |
| 1416 | elif opts['switchType'] == 'ovs': |
| 1417 | if ('switchIP' in opts): |
| 1418 | if (len(opts['switchIP'])>0): |
| 1419 | f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") |
| 1420 | for widget in self.widgetToItem: |
| 1421 | name = widget[ 'text' ] |
| 1422 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1423 | if 'Host' in tags: |
| 1424 | opts = self.hostOpts[name] |
| 1425 | # Attach vlan interfaces |
| 1426 | if ('vlanInterfaces' in opts): |
| 1427 | for vlanInterface in opts['vlanInterfaces']: |
| 1428 | f.write(" "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n") |
| 1429 | f.write(" "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n") |
| 1430 | # Run User Defined Start Command |
| 1431 | if ('startCommand' in opts): |
| 1432 | f.write(" "+name+".cmdPrint('"+opts['startCommand']+"')\n") |
| 1433 | if 'Switch' in tags: |
| 1434 | opts = self.switchOpts[name] |
| 1435 | # Run User Defined Start Command |
| 1436 | if ('startCommand' in opts): |
| 1437 | f.write(" "+name+".cmdPrint('"+opts['startCommand']+"')\n") |
| 1438 | |
| 1439 | # Configure NetFlow |
| 1440 | nflowValues = self.appPrefs['netflow'] |
| 1441 | if len(nflowValues['nflowTarget']) > 0: |
| 1442 | nflowEnabled = False |
| 1443 | nflowSwitches = '' |
| 1444 | for widget in self.widgetToItem: |
| 1445 | name = widget[ 'text' ] |
| 1446 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1447 | |
| 1448 | if 'Switch' in tags: |
| 1449 | opts = self.switchOpts[name] |
| 1450 | if 'netflow' in opts: |
| 1451 | if opts['netflow'] == '1': |
| 1452 | nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF' |
| 1453 | nflowEnabled=True |
| 1454 | if nflowEnabled: |
| 1455 | nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout'] |
| 1456 | if nflowValues['nflowAddId'] == '1': |
| 1457 | nflowCmd = nflowCmd + ' add_id_to_interface=true' |
| 1458 | else: |
| 1459 | nflowCmd = nflowCmd + ' add_id_to_interface=false' |
| 1460 | f.write(" \n") |
| 1461 | f.write(" call('"+nflowCmd+nflowSwitches+"', shell=True)\n") |
| 1462 | |
| 1463 | # Configure sFlow |
| 1464 | sflowValues = self.appPrefs['sflow'] |
| 1465 | if len(sflowValues['sflowTarget']) > 0: |
| 1466 | sflowEnabled = False |
| 1467 | sflowSwitches = '' |
| 1468 | for widget in self.widgetToItem: |
| 1469 | name = widget[ 'text' ] |
| 1470 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1471 | |
| 1472 | if 'Switch' in tags: |
| 1473 | opts = self.switchOpts[name] |
| 1474 | if 'sflow' in opts: |
| 1475 | if opts['sflow'] == '1': |
| 1476 | sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF' |
| 1477 | sflowEnabled=True |
| 1478 | if sflowEnabled: |
| 1479 | sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling'] |
| 1480 | f.write(" \n") |
| 1481 | f.write(" call('"+sflowCmd+sflowSwitches+"', shell=True)\n") |
| 1482 | |
| 1483 | f.write("\n") |
| 1484 | f.write(" CLI(net)\n") |
| 1485 | for widget in self.widgetToItem: |
| 1486 | name = widget[ 'text' ] |
| 1487 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 1488 | if 'Host' in tags: |
| 1489 | opts = self.hostOpts[name] |
| 1490 | # Run User Defined Stop Command |
| 1491 | if ('stopCommand' in opts): |
| 1492 | f.write(" "+name+".cmdPrint('"+opts['stopCommand']+"')\n") |
| 1493 | if 'Switch' in tags: |
| 1494 | opts = self.switchOpts[name] |
| 1495 | # Run User Defined Stop Command |
| 1496 | if ('stopCommand' in opts): |
| 1497 | f.write(" "+name+".cmdPrint('"+opts['stopCommand']+"')\n") |
| 1498 | |
| 1499 | f.write(" net.stop()\n") |
| 1500 | f.write("\n") |
| 1501 | f.write("if __name__ == '__main__':\n") |
| 1502 | f.write(" setLogLevel( 'info' )\n") |
| 1503 | f.write(" myNetwork()\n") |
| 1504 | f.write("\n") |
| 1505 | |
| 1506 | |
| 1507 | f.close() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1508 | |
| 1509 | |
| 1510 | # Generic canvas handler |
| 1511 | # |
| 1512 | # We could have used bindtags, as in nodeIcon, but |
| 1513 | # the dynamic approach used here |
| 1514 | # may actually require less code. In any case, it's an |
| 1515 | # interesting introspection-based alternative to bindtags. |
| 1516 | |
| 1517 | def canvasHandle( self, eventName, event ): |
| 1518 | "Generic canvas event handler" |
| 1519 | if self.active is None: |
| 1520 | return |
| 1521 | toolName = self.active |
| 1522 | handler = getattr( self, eventName + toolName, None ) |
| 1523 | if handler is not None: |
| 1524 | handler( event ) |
| 1525 | |
| 1526 | def clickCanvas( self, event ): |
| 1527 | "Canvas click handler." |
| 1528 | self.canvasHandle( 'click', event ) |
| 1529 | |
| 1530 | def dragCanvas( self, event ): |
| 1531 | "Canvas drag handler." |
| 1532 | self.canvasHandle( 'drag', event ) |
| 1533 | |
| 1534 | def releaseCanvas( self, event ): |
| 1535 | "Canvas mouse up handler." |
| 1536 | self.canvasHandle( 'release', event ) |
| 1537 | |
| 1538 | # Currently the only items we can select directly are |
| 1539 | # links. Nodes are handled by bindings in the node icon. |
| 1540 | |
| 1541 | def findItem( self, x, y ): |
| 1542 | "Find items at a location in our canvas." |
| 1543 | items = self.canvas.find_overlapping( x, y, x, y ) |
| 1544 | if len( items ) == 0: |
| 1545 | return None |
| 1546 | else: |
| 1547 | return items[ 0 ] |
| 1548 | |
| 1549 | # Canvas bindings for Select, Host, Switch and Link tools |
| 1550 | |
| 1551 | def clickSelect( self, event ): |
| 1552 | "Select an item." |
| 1553 | self.selectItem( self.findItem( event.x, event.y ) ) |
| 1554 | |
| 1555 | def deleteItem( self, item ): |
| 1556 | "Delete an item." |
| 1557 | # Don't delete while network is running |
| 1558 | if self.buttons[ 'Select' ][ 'state' ] == 'disabled': |
| 1559 | return |
| 1560 | # Delete from model |
| 1561 | if item in self.links: |
| 1562 | self.deleteLink( item ) |
| 1563 | if item in self.itemToWidget: |
| 1564 | self.deleteNode( item ) |
| 1565 | # Delete from view |
| 1566 | self.canvas.delete( item ) |
| 1567 | |
| 1568 | def deleteSelection( self, _event ): |
| 1569 | "Delete the selected item." |
| 1570 | if self.selection is not None: |
| 1571 | self.deleteItem( self.selection ) |
| 1572 | self.selectItem( None ) |
| 1573 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1574 | def clearPopups(self): |
| 1575 | print 'Entrou funcao clear_popups' |
| 1576 | |
| 1577 | if isHostPopup == True: |
| 1578 | print 'Hostpopup = true' |
| 1579 | self.hostPopup.unpost |
| 1580 | isHostPopup = False |
| 1581 | #if isRouterPopup == True |
| 1582 | #if isLinkPopup == True |
| 1583 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1584 | def nodeIcon( self, node, name ): |
| 1585 | "Create a new node icon." |
| 1586 | icon = Button( self.canvas, image=self.images[ node ], |
| 1587 | text=name, compound='top' ) |
| 1588 | # Unfortunately bindtags wants a tuple |
| 1589 | bindtags = [ str( self.nodeBindings ) ] |
| 1590 | bindtags += list( icon.bindtags() ) |
| 1591 | icon.bindtags( tuple( bindtags ) ) |
| 1592 | return icon |
| 1593 | |
| 1594 | def newNode( self, node, event ): |
| 1595 | "Add a new node to our canvas." |
| 1596 | c = self.canvas |
| 1597 | x, y = c.canvasx( event.x ), c.canvasy( event.y ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1598 | name = self.nodePrefixes[ node ] |
| 1599 | if 'Switch' == node: |
| 1600 | self.switchCount += 1 |
| 1601 | name = self.nodePrefixes[ node ] + str( self.switchCount ) |
| 1602 | self.switchOpts[name] = {} |
| 1603 | self.switchOpts[name]['nodeNum']=self.switchCount |
| 1604 | self.switchOpts[name]['hostname']=name |
| 1605 | self.switchOpts[name]['switchType']='default' |
| 1606 | self.switchOpts[name]['controllers']=[] |
| 1607 | if 'LegacyRouter' == node: |
| 1608 | self.switchCount += 1 |
| 1609 | name = self.nodePrefixes[ node ] + str( self.switchCount ) |
| 1610 | self.switchOpts[name] = {} |
| 1611 | self.switchOpts[name]['nodeNum']=self.switchCount |
| 1612 | self.switchOpts[name]['hostname']=name |
| 1613 | self.switchOpts[name]['switchType']='legacyRouter' |
| 1614 | if 'LegacySwitch' == node: |
| 1615 | self.switchCount += 1 |
| 1616 | name = self.nodePrefixes[ node ] + str( self.switchCount ) |
| 1617 | self.switchOpts[name] = {} |
| 1618 | self.switchOpts[name]['nodeNum']=self.switchCount |
| 1619 | self.switchOpts[name]['hostname']=name |
| 1620 | self.switchOpts[name]['switchType']='legacySwitch' |
| 1621 | self.switchOpts[name]['controllers']=[] |
| 1622 | if 'Host' == node: |
| 1623 | self.hostCount += 1 |
| 1624 | name = self.nodePrefixes[ node ] + str( self.hostCount ) |
| 1625 | self.hostOpts[name] = {'sched':'host'} |
| 1626 | self.hostOpts[name]['nodeNum']=self.hostCount |
| 1627 | self.hostOpts[name]['hostname']=name |
| 1628 | if 'Controller' == node: |
| 1629 | name = self.nodePrefixes[ node ] + str( self.controllerCount ) |
| 1630 | ctrlr = { 'controllerType': 'ref', |
| 1631 | 'hostname': name, |
| 1632 | 'controllerProtocol': 'tcp', |
| 1633 | 'remoteIP': '127.0.0.1', |
| 1634 | 'remotePort': 6633} |
| 1635 | self.controllers[name] = ctrlr |
| 1636 | # We want to start controller count at 0 |
| 1637 | self.controllerCount += 1 |
| 1638 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1639 | icon = self.nodeIcon( node, name ) |
| 1640 | item = self.canvas.create_window( x, y, anchor='c', window=icon, |
| 1641 | tags=node ) |
| 1642 | self.widgetToItem[ icon ] = item |
| 1643 | self.itemToWidget[ item ] = icon |
| 1644 | self.selectItem( item ) |
| 1645 | icon.links = {} |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1646 | if 'LegacyRouter' == node: |
| 1647 | icon.bind('<Button-3>', self.do_legacyRouterPopup ) |
| 1648 | if 'Host' == node: |
| 1649 | icon.bind('<Button-3>', self.do_hostPopup ) |
| 1650 | |
| 1651 | def clickController( self, event ): |
| 1652 | "Add a new Controller to our canvas." |
| 1653 | self.newNode( 'Controller', event ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1654 | |
| 1655 | def clickHost( self, event ): |
| 1656 | "Add a new host to our canvas." |
| 1657 | self.newNode( 'Host', event ) |
| 1658 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1659 | def clickLegacyRouter( self, event ): |
| 1660 | "Add a new switch to our canvas." |
| 1661 | self.newNode( 'LegacyRouter', event ) |
| 1662 | |
| 1663 | def clickLegacySwitch( self, event ): |
| 1664 | "Add a new switch to our canvas." |
| 1665 | self.newNode( 'LegacySwitch', event ) |
| 1666 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1667 | def clickSwitch( self, event ): |
| 1668 | "Add a new switch to our canvas." |
| 1669 | self.newNode( 'Switch', event ) |
| 1670 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1671 | def dragNetLink( self, event ): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1672 | "Drag a link's endpoint to another node." |
| 1673 | if self.link is None: |
| 1674 | return |
| 1675 | # Since drag starts in widget, we use root coords |
| 1676 | x = self.canvasx( event.x_root ) |
| 1677 | y = self.canvasy( event.y_root ) |
| 1678 | c = self.canvas |
| 1679 | c.coords( self.link, self.linkx, self.linky, x, y ) |
| 1680 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1681 | def releaseNetLink( self, _event ): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1682 | "Give up on the current link." |
| 1683 | if self.link is not None: |
| 1684 | self.canvas.delete( self.link ) |
| 1685 | self.linkWidget = self.linkItem = self.link = None |
| 1686 | |
| 1687 | # Generic node handlers |
| 1688 | |
| 1689 | def createNodeBindings( self ): |
| 1690 | "Create a set of bindings for nodes." |
| 1691 | bindings = { |
| 1692 | '<ButtonPress-1>': self.clickNode, |
| 1693 | '<B1-Motion>': self.dragNode, |
| 1694 | '<ButtonRelease-1>': self.releaseNode, |
| 1695 | '<Enter>': self.enterNode, |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1696 | '<Leave>': self.leaveNode |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1697 | } |
| 1698 | l = Label() # lightweight-ish owner for bindings |
| 1699 | for event, binding in bindings.items(): |
| 1700 | l.bind( event, binding ) |
| 1701 | return l |
| 1702 | |
| 1703 | def selectItem( self, item ): |
| 1704 | "Select an item and remember old selection." |
| 1705 | self.lastSelection = self.selection |
| 1706 | self.selection = item |
| 1707 | |
| 1708 | def enterNode( self, event ): |
| 1709 | "Select node on entry." |
| 1710 | self.selectNode( event ) |
| 1711 | |
| 1712 | def leaveNode( self, _event ): |
| 1713 | "Restore old selection on exit." |
| 1714 | self.selectItem( self.lastSelection ) |
| 1715 | |
| 1716 | def clickNode( self, event ): |
| 1717 | "Node click handler." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1718 | if self.active is 'NetLink': |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1719 | self.startLink( event ) |
| 1720 | else: |
| 1721 | self.selectNode( event ) |
| 1722 | return 'break' |
| 1723 | |
| 1724 | def dragNode( self, event ): |
| 1725 | "Node drag handler." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1726 | if self.active is 'NetLink': |
| 1727 | self.dragNetLink( event ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1728 | else: |
| 1729 | self.dragNodeAround( event ) |
| 1730 | |
| 1731 | def releaseNode( self, event ): |
| 1732 | "Node release handler." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1733 | if self.active is 'NetLink': |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1734 | self.finishLink( event ) |
| 1735 | |
| 1736 | # Specific node handlers |
| 1737 | |
| 1738 | def selectNode( self, event ): |
| 1739 | "Select the node that was clicked on." |
| 1740 | item = self.widgetToItem.get( event.widget, None ) |
| 1741 | self.selectItem( item ) |
| 1742 | |
| 1743 | def dragNodeAround( self, event ): |
| 1744 | "Drag a node around on the canvas." |
| 1745 | c = self.canvas |
| 1746 | # Convert global to local coordinates; |
| 1747 | # Necessary since x, y are widget-relative |
| 1748 | x = self.canvasx( event.x_root ) |
| 1749 | y = self.canvasy( event.y_root ) |
| 1750 | w = event.widget |
| 1751 | # Adjust node position |
| 1752 | item = self.widgetToItem[ w ] |
| 1753 | c.coords( item, x, y ) |
| 1754 | # Adjust link positions |
| 1755 | for dest in w.links: |
| 1756 | link = w.links[ dest ] |
| 1757 | item = self.widgetToItem[ dest ] |
| 1758 | x1, y1 = c.coords( item ) |
| 1759 | c.coords( link, x, y, x1, y1 ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1760 | self.updateScrollRegion() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1761 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1762 | def createControlLinkBindings( self ): |
| 1763 | "Create a set of bindings for nodes." |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1764 | # Link bindings |
| 1765 | # Selection still needs a bit of work overall |
| 1766 | # Callbacks ignore event |
| 1767 | |
| 1768 | def select( _event, link=self.link ): |
| 1769 | "Select item on mouse entry." |
| 1770 | self.selectItem( link ) |
| 1771 | |
| 1772 | def highlight( _event, link=self.link ): |
| 1773 | "Highlight item on mouse entry." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1774 | self.selectItem( link ) |
| 1775 | self.canvas.itemconfig( link, fill='green' ) |
| 1776 | |
| 1777 | def unhighlight( _event, link=self.link ): |
| 1778 | "Unhighlight item on mouse exit." |
| 1779 | self.canvas.itemconfig( link, fill='red' ) |
| 1780 | #self.selectItem( None ) |
| 1781 | |
| 1782 | self.canvas.tag_bind( self.link, '<Enter>', highlight ) |
| 1783 | self.canvas.tag_bind( self.link, '<Leave>', unhighlight ) |
| 1784 | self.canvas.tag_bind( self.link, '<ButtonPress-1>', select ) |
| 1785 | |
| 1786 | def createDataLinkBindings( self ): |
| 1787 | "Create a set of bindings for nodes." |
| 1788 | # Link bindings |
| 1789 | # Selection still needs a bit of work overall |
| 1790 | # Callbacks ignore event |
| 1791 | |
| 1792 | def select( _event, link=self.link ): |
| 1793 | "Select item on mouse entry." |
| 1794 | self.selectItem( link ) |
| 1795 | |
| 1796 | def highlight( _event, link=self.link ): |
| 1797 | "Highlight item on mouse entry." |
| 1798 | self.selectItem( link ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1799 | self.canvas.itemconfig( link, fill='green' ) |
| 1800 | |
| 1801 | def unhighlight( _event, link=self.link ): |
| 1802 | "Unhighlight item on mouse exit." |
| 1803 | self.canvas.itemconfig( link, fill='blue' ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1804 | #self.selectItem( None ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1805 | |
| 1806 | self.canvas.tag_bind( self.link, '<Enter>', highlight ) |
| 1807 | self.canvas.tag_bind( self.link, '<Leave>', unhighlight ) |
| 1808 | self.canvas.tag_bind( self.link, '<ButtonPress-1>', select ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1809 | self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup ) |
| 1810 | |
| 1811 | |
| 1812 | def startLink( self, event ): |
| 1813 | "Start a new link." |
| 1814 | if event.widget not in self.widgetToItem: |
| 1815 | # Didn't click on a node |
| 1816 | return |
| 1817 | |
| 1818 | w = event.widget |
| 1819 | item = self.widgetToItem[ w ] |
| 1820 | x, y = self.canvas.coords( item ) |
| 1821 | self.link = self.canvas.create_line( x, y, x, y, width=4, |
| 1822 | fill='blue', tag='link' ) |
| 1823 | self.linkx, self.linky = x, y |
| 1824 | self.linkWidget = w |
| 1825 | self.linkItem = item |
| 1826 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1827 | |
| 1828 | def finishLink( self, event ): |
| 1829 | "Finish creating a link" |
| 1830 | if self.link is None: |
| 1831 | return |
| 1832 | source = self.linkWidget |
| 1833 | c = self.canvas |
| 1834 | # Since we dragged from the widget, use root coords |
| 1835 | x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root ) |
| 1836 | target = self.findItem( x, y ) |
| 1837 | dest = self.itemToWidget.get( target, None ) |
| 1838 | if ( source is None or dest is None or source == dest |
| 1839 | or dest in source.links or source in dest.links ): |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1840 | self.releaseNetLink( event ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1841 | return |
| 1842 | # For now, don't allow hosts to be directly linked |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1843 | stags = self.canvas.gettags( self.widgetToItem[ source ] ) |
| 1844 | dtags = self.canvas.gettags( target ) |
| 1845 | if (('Host' in stags and 'Host' in dtags) or |
| 1846 | ('Controller' in dtags and 'LegacyRouter' in stags) or |
| 1847 | ('Controller' in stags and 'LegacyRouter' in dtags) or |
| 1848 | ('Controller' in dtags and 'LegacySwitch' in stags) or |
| 1849 | ('Controller' in stags and 'LegacySwitch' in dtags) or |
| 1850 | ('Controller' in dtags and 'Host' in stags) or |
| 1851 | ('Controller' in stags and 'Host' in dtags) or |
| 1852 | ('Controller' in stags and 'Controller' in dtags)): |
| 1853 | self.releaseNetLink( event ) |
| 1854 | return |
| 1855 | |
| 1856 | # Set link type |
| 1857 | linkType='data' |
| 1858 | if 'Controller' in stags or 'Controller' in dtags: |
| 1859 | linkType='control' |
| 1860 | c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red') |
| 1861 | self.createControlLinkBindings() |
| 1862 | else: |
| 1863 | linkType='data' |
| 1864 | self.createDataLinkBindings() |
| 1865 | c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,)) |
| 1866 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1867 | x, y = c.coords( target ) |
| 1868 | c.coords( self.link, self.linkx, self.linky, x, y ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1869 | self.addLink( source, dest, linktype=linkType ) |
| 1870 | if linkType == 'control': |
| 1871 | controllerName = '' |
| 1872 | switchName = '' |
| 1873 | if 'Controller' in stags: |
| 1874 | controllerName = source[ 'text' ] |
| 1875 | switchName = dest[ 'text' ] |
| 1876 | else: |
| 1877 | controllerName = dest[ 'text' ] |
| 1878 | switchName = source[ 'text' ] |
| 1879 | |
| 1880 | self.switchOpts[switchName]['controllers'].append(controllerName) |
| 1881 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1882 | # We're done |
| 1883 | self.link = self.linkWidget = None |
| 1884 | |
| 1885 | # Menu handlers |
| 1886 | |
| 1887 | def about( self ): |
| 1888 | "Display about box." |
| 1889 | about = self.aboutBox |
| 1890 | if about is None: |
| 1891 | bg = 'white' |
| 1892 | about = Toplevel( bg='white' ) |
| 1893 | about.title( 'About' ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1894 | info = self.appName + ': a simple network editor for MiniNet' |
| 1895 | version = 'MiniEdit '+MINIEDIT_VERSION |
| 1896 | author = 'Originally by: Bob Lantz <rlantz@cs>, April 2010' |
| 1897 | enhancements = 'Enhancements by: Gregory Gee, Since July 2013' |
| 1898 | www = 'http://gregorygee.wordpress.com/category/miniedit/' |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1899 | line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1900 | line2 = Label( about, text=version, font='Helvetica 9', bg=bg ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1901 | line3 = Label( about, text=author, font='Helvetica 9', bg=bg ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1902 | line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg ) |
| 1903 | line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER ) |
| 1904 | line5.insert(0, www) |
| 1905 | line5.configure(state='readonly') |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1906 | line1.pack( padx=20, pady=10 ) |
| 1907 | line2.pack(pady=10 ) |
| 1908 | line3.pack(pady=10 ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1909 | line4.pack(pady=10 ) |
| 1910 | line5.pack(pady=10 ) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1911 | hide = ( lambda about=about: about.withdraw() ) |
| 1912 | self.aboutBox = about |
| 1913 | # Hide on close rather than destroying window |
| 1914 | Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide ) |
| 1915 | # Show (existing) window |
| 1916 | about.deiconify() |
| 1917 | |
| 1918 | def createToolImages( self ): |
| 1919 | "Create toolbar (and icon) images." |
| 1920 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 1921 | def checkIntf( self, intf ): |
| 1922 | "Make sure intf exists and is not configured." |
| 1923 | if ( ' %s:' % intf ) not in quietRun( 'ip link show' ): |
| 1924 | showerror(title="Error", |
| 1925 | message='External interface ' +intf + ' does not exist! Skipping.') |
| 1926 | return False |
| 1927 | ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) ) |
| 1928 | if ips: |
| 1929 | showerror(title="Error", |
| 1930 | message= intf + ' has an IP address and is probably in use! Skipping.' ) |
| 1931 | return False |
| 1932 | return True |
| 1933 | |
| 1934 | def hostDetails( self, _ignore=None ): |
| 1935 | if ( self.selection is None or |
| 1936 | self.net is not None or |
| 1937 | self.selection not in self.itemToWidget ): |
| 1938 | return |
| 1939 | widget = self.itemToWidget[ self.selection ] |
| 1940 | name = widget[ 'text' ] |
| 1941 | tags = self.canvas.gettags( self.selection ) |
| 1942 | if 'Host' not in tags: |
| 1943 | return |
| 1944 | |
| 1945 | prefDefaults = self.hostOpts[name] |
| 1946 | hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults) |
| 1947 | self.master.wait_window(hostBox.top) |
| 1948 | if hostBox.result: |
| 1949 | newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']} |
| 1950 | newHostOpts['sched'] = hostBox.result['sched'] |
| 1951 | if len(hostBox.result['startCommand']) > 0: |
| 1952 | newHostOpts['startCommand'] = hostBox.result['startCommand'] |
| 1953 | if len(hostBox.result['stopCommand']) > 0: |
| 1954 | newHostOpts['stopCommand'] = hostBox.result['stopCommand'] |
| 1955 | if len(hostBox.result['cpu']) > 0: |
| 1956 | newHostOpts['cpu'] = float(hostBox.result['cpu']) |
| 1957 | if len(hostBox.result['cores']) > 0: |
| 1958 | newHostOpts['cores'] = hostBox.result['cores'] |
| 1959 | if len(hostBox.result['hostname']) > 0: |
| 1960 | newHostOpts['hostname'] = hostBox.result['hostname'] |
| 1961 | name = hostBox.result['hostname'] |
| 1962 | widget[ 'text' ] = name |
| 1963 | if len(hostBox.result['defaultRoute']) > 0: |
| 1964 | newHostOpts['defaultRoute'] = hostBox.result['defaultRoute'] |
| 1965 | if len(hostBox.result['ip']) > 0: |
| 1966 | newHostOpts['ip'] = hostBox.result['ip'] |
| 1967 | if len(hostBox.result['externalInterfaces']) > 0: |
| 1968 | newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces'] |
| 1969 | if len(hostBox.result['vlanInterfaces']) > 0: |
| 1970 | newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces'] |
| 1971 | if len(hostBox.result['privateDirectory']) > 0: |
| 1972 | newHostOpts['privateDirectory'] = hostBox.result['privateDirectory'] |
| 1973 | self.hostOpts[name] = newHostOpts |
| 1974 | print 'New host details for ' + name + ' = ' + str(newHostOpts) |
| 1975 | |
| 1976 | def switchDetails( self, _ignore=None ): |
| 1977 | if ( self.selection is None or |
| 1978 | self.net is not None or |
| 1979 | self.selection not in self.itemToWidget ): |
| 1980 | return |
| 1981 | widget = self.itemToWidget[ self.selection ] |
| 1982 | name = widget[ 'text' ] |
| 1983 | tags = self.canvas.gettags( self.selection ) |
| 1984 | if 'Switch' not in tags: |
| 1985 | return |
| 1986 | |
| 1987 | prefDefaults = self.switchOpts[name] |
| 1988 | switchBox = SwitchDialog(self, title='Switch Details', prefDefaults=prefDefaults) |
| 1989 | self.master.wait_window(switchBox.top) |
| 1990 | if switchBox.result: |
| 1991 | newSwitchOpts = {'nodeNum':self.switchOpts[name]['nodeNum']} |
| 1992 | newSwitchOpts['switchType'] = switchBox.result['switchType'] |
| 1993 | newSwitchOpts['controllers'] = self.switchOpts[name]['controllers'] |
| 1994 | if len(switchBox.result['startCommand']) > 0: |
| 1995 | newSwitchOpts['startCommand'] = switchBox.result['startCommand'] |
| 1996 | if len(switchBox.result['stopCommand']) > 0: |
| 1997 | newSwitchOpts['stopCommand'] = switchBox.result['stopCommand'] |
| 1998 | if len(switchBox.result['dpctl']) > 0: |
| 1999 | newSwitchOpts['dpctl'] = switchBox.result['dpctl'] |
| 2000 | if len(switchBox.result['dpid']) > 0: |
| 2001 | newSwitchOpts['dpid'] = switchBox.result['dpid'] |
| 2002 | if len(switchBox.result['hostname']) > 0: |
| 2003 | newSwitchOpts['hostname'] = switchBox.result['hostname'] |
| 2004 | name = switchBox.result['hostname'] |
| 2005 | widget[ 'text' ] = name |
| 2006 | if len(switchBox.result['externalInterfaces']) > 0: |
| 2007 | newSwitchOpts['externalInterfaces'] = switchBox.result['externalInterfaces'] |
| 2008 | newSwitchOpts['switchIP'] = switchBox.result['switchIP'] |
| 2009 | newSwitchOpts['sflow'] = switchBox.result['sflow'] |
| 2010 | newSwitchOpts['netflow'] = switchBox.result['netflow'] |
| 2011 | self.switchOpts[name] = newSwitchOpts |
| 2012 | print 'New switch details for ' + name + ' = ' + str(newSwitchOpts) |
| 2013 | |
| 2014 | def linkUp( self ): |
| 2015 | if ( self.selection is None or |
| 2016 | self.net is None): |
| 2017 | return |
| 2018 | link = self.selection |
| 2019 | linkDetail = self.links[link] |
| 2020 | src = linkDetail['src'] |
| 2021 | dst = linkDetail['dest'] |
| 2022 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 2023 | self.net.configLinkStatus(srcName, dstName, 'up') |
| 2024 | self.canvas.itemconfig(link, dash=()) |
| 2025 | |
| 2026 | def linkDown( self ): |
| 2027 | if ( self.selection is None or |
| 2028 | self.net is None): |
| 2029 | return |
| 2030 | link = self.selection |
| 2031 | linkDetail = self.links[link] |
| 2032 | src = linkDetail['src'] |
| 2033 | dst = linkDetail['dest'] |
| 2034 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 2035 | self.net.configLinkStatus(srcName, dstName, 'down') |
| 2036 | self.canvas.itemconfig(link, dash=(4, 4)) |
| 2037 | |
| 2038 | def linkDetails( self, _ignore=None ): |
| 2039 | if ( self.selection is None or |
| 2040 | self.net is not None): |
| 2041 | return |
| 2042 | link = self.selection |
| 2043 | |
| 2044 | linkDetail = self.links[link] |
| 2045 | src = linkDetail['src'] |
| 2046 | dest = linkDetail['dest'] |
| 2047 | linkopts = linkDetail['linkOpts'] |
| 2048 | linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts) |
| 2049 | if linkBox.result is not None: |
| 2050 | linkDetail['linkOpts'] = linkBox.result |
| 2051 | print 'New link details = ' + str(linkBox.result) |
| 2052 | |
| 2053 | def prefDetails( self ): |
| 2054 | prefDefaults = self.appPrefs |
| 2055 | prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults) |
| 2056 | print 'New Prefs = ' + str(prefBox.result) |
| 2057 | if prefBox.result: |
| 2058 | self.appPrefs = prefBox.result |
| 2059 | |
| 2060 | |
| 2061 | def controllerDetails( self ): |
| 2062 | if ( self.selection is None or |
| 2063 | self.net is not None or |
| 2064 | self.selection not in self.itemToWidget ): |
| 2065 | return |
| 2066 | widget = self.itemToWidget[ self.selection ] |
| 2067 | name = widget[ 'text' ] |
| 2068 | tags = self.canvas.gettags( self.selection ) |
| 2069 | oldName = name |
| 2070 | if 'Controller' not in tags: |
| 2071 | return |
| 2072 | |
| 2073 | ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name]) |
| 2074 | if ctrlrBox.result: |
| 2075 | #print 'Controller is ' + ctrlrBox.result[0] |
| 2076 | if len(ctrlrBox.result['hostname']) > 0: |
| 2077 | name = ctrlrBox.result['hostname'] |
| 2078 | widget[ 'text' ] = name |
| 2079 | else: |
| 2080 | ctrlrBox.result['hostname'] = name |
| 2081 | self.controllers[name] = ctrlrBox.result |
| 2082 | print 'New controller details for ' + name + ' = ' + str(self.controllers[name]) |
| 2083 | # Find references to controller and change name |
| 2084 | if oldName != name: |
| 2085 | for widget in self.widgetToItem: |
| 2086 | switchName = widget[ 'text' ] |
| 2087 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 2088 | if 'Switch' in tags: |
| 2089 | switch = self.switchOpts[switchName] |
| 2090 | if oldName in switch['controllers']: |
| 2091 | switch['controllers'].remove(oldName) |
| 2092 | switch['controllers'].append(name) |
| 2093 | |
| 2094 | |
| 2095 | def listBridge( self, _ignore=None ): |
| 2096 | if ( self.selection is None or |
| 2097 | self.net is None or |
| 2098 | self.selection not in self.itemToWidget ): |
| 2099 | return |
| 2100 | name = self.itemToWidget[ self.selection ][ 'text' ] |
| 2101 | tags = self.canvas.gettags( self.selection ) |
| 2102 | |
| 2103 | if name not in self.net.nameToNode: |
| 2104 | return |
| 2105 | if 'Switch' in tags or 'LegacySwitch' in tags: |
| 2106 | call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True) |
| 2107 | |
| 2108 | def ovsShow( self, _ignore=None ): |
| 2109 | call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True) |
| 2110 | |
| 2111 | def rootTerminal( self, _ignore=None ): |
| 2112 | call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True) |
| 2113 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2114 | # Model interface |
| 2115 | # |
| 2116 | # Ultimately we will either want to use a topo or |
| 2117 | # mininet object here, probably. |
| 2118 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2119 | def addLink( self, source, dest, linktype='data', linkopts={} ): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2120 | "Add link to model." |
| 2121 | source.links[ dest ] = self.link |
| 2122 | dest.links[ source ] = self.link |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2123 | self.links[ self.link ] = {'type' :linktype, |
| 2124 | 'src':source, |
| 2125 | 'dest':dest, |
| 2126 | 'linkOpts':linkopts} |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2127 | |
| 2128 | def deleteLink( self, link ): |
| 2129 | "Delete link from model." |
| 2130 | pair = self.links.get( link, None ) |
| 2131 | if pair is not None: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2132 | source=pair['src'] |
| 2133 | dest=pair['dest'] |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2134 | del source.links[ dest ] |
| 2135 | del dest.links[ source ] |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2136 | stags = self.canvas.gettags( self.widgetToItem[ source ] ) |
| 2137 | dtags = self.canvas.gettags( self.widgetToItem[ dest ] ) |
| 2138 | ltags = self.canvas.gettags( link ) |
| 2139 | |
| 2140 | if 'control' in ltags: |
| 2141 | controllerName = '' |
| 2142 | switchName = '' |
| 2143 | if 'Controller' in stags: |
| 2144 | controllerName = source[ 'text' ] |
| 2145 | switchName = dest[ 'text' ] |
| 2146 | else: |
| 2147 | controllerName = dest[ 'text' ] |
| 2148 | switchName = source[ 'text' ] |
| 2149 | |
| 2150 | if controllerName in self.switchOpts[switchName]['controllers']: |
| 2151 | self.switchOpts[switchName]['controllers'].remove(controllerName) |
| 2152 | |
| 2153 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2154 | if link is not None: |
| 2155 | del self.links[ link ] |
| 2156 | |
| 2157 | def deleteNode( self, item ): |
| 2158 | "Delete node (and its links) from model." |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2159 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2160 | widget = self.itemToWidget[ item ] |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2161 | tags = self.canvas.gettags(item) |
| 2162 | if 'Controller' in tags: |
| 2163 | # remove from switch controller lists |
| 2164 | for serachwidget in self.widgetToItem: |
| 2165 | name = serachwidget[ 'text' ] |
| 2166 | tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] ) |
| 2167 | if 'Switch' in tags: |
| 2168 | if widget['text'] in self.switchOpts[name]['controllers']: |
| 2169 | self.switchOpts[name]['controllers'].remove(widget['text']) |
| 2170 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2171 | for link in widget.links.values(): |
| 2172 | # Delete from view and model |
| 2173 | self.deleteItem( link ) |
| 2174 | del self.itemToWidget[ item ] |
| 2175 | del self.widgetToItem[ widget ] |
| 2176 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2177 | def buildNodes( self, net): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2178 | # Make nodes |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2179 | print "Getting Hosts and Switches." |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2180 | for widget in self.widgetToItem: |
| 2181 | name = widget[ 'text' ] |
| 2182 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2183 | #print name+' has '+str(tags) |
| 2184 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2185 | if 'Switch' in tags: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2186 | opts = self.switchOpts[name] |
| 2187 | #print str(opts) |
| 2188 | |
| 2189 | # Create the correct switch class |
| 2190 | switchClass = customOvs |
| 2191 | switchParms={} |
| 2192 | if 'dpctl' in opts: |
| 2193 | switchParms['listenPort']=int(opts['dpctl']) |
| 2194 | if 'dpid' in opts: |
| 2195 | switchParms['dpid']=opts['dpid'] |
| 2196 | if opts['switchType'] == 'default': |
| 2197 | if self.appPrefs['switchType'] == 'ivs': |
| 2198 | switchClass = IVSSwitch |
| 2199 | elif self.appPrefs['switchType'] == 'user': |
| 2200 | switchClass = CustomUserSwitch |
| 2201 | elif self.appPrefs['switchType'] == 'userns': |
| 2202 | switchParms['inNamespace'] = True |
| 2203 | switchClass = CustomUserSwitch |
| 2204 | else: |
| 2205 | switchClass = customOvs |
| 2206 | elif opts['switchType'] == 'user': |
| 2207 | switchClass = CustomUserSwitch |
| 2208 | elif opts['switchType'] == 'userns': |
| 2209 | switchClass = CustomUserSwitch |
| 2210 | switchParms['inNamespace'] = True |
| 2211 | elif opts['switchType'] == 'ivs': |
| 2212 | switchClass = IVSSwitch |
| 2213 | else: |
| 2214 | switchClass = customOvs |
| 2215 | |
| 2216 | if switchClass == customOvs: |
| 2217 | # Set OpenFlow versions |
| 2218 | self.openFlowVersions = [] |
| 2219 | if self.appPrefs['openFlowVersions']['ovsOf10'] == '1': |
| 2220 | self.openFlowVersions.append('OpenFlow10') |
| 2221 | if self.appPrefs['openFlowVersions']['ovsOf11'] == '1': |
| 2222 | self.openFlowVersions.append('OpenFlow11') |
| 2223 | if self.appPrefs['openFlowVersions']['ovsOf12'] == '1': |
| 2224 | self.openFlowVersions.append('OpenFlow12') |
| 2225 | if self.appPrefs['openFlowVersions']['ovsOf13'] == '1': |
| 2226 | self.openFlowVersions.append('OpenFlow13') |
| 2227 | protoList = ",".join(self.openFlowVersions) |
| 2228 | switchParms['protocols'] = protoList |
| 2229 | newSwitch = net.addSwitch( name , cls=switchClass, **switchParms) |
| 2230 | |
| 2231 | # Some post startup config |
| 2232 | if switchClass == CustomUserSwitch: |
| 2233 | if ('switchIP' in opts): |
| 2234 | if (len(opts['switchIP']) > 0): |
| 2235 | newSwitch.setSwitchIP(opts['switchIP']) |
| 2236 | if switchClass == customOvs: |
| 2237 | if ('switchIP' in opts): |
| 2238 | if (len(opts['switchIP']) > 0): |
| 2239 | newSwitch.setSwitchIP(opts['switchIP']) |
| 2240 | |
| 2241 | # Attach external interfaces |
| 2242 | if ('externalInterfaces' in opts): |
| 2243 | for extInterface in opts['externalInterfaces']: |
| 2244 | if self.checkIntf(extInterface): |
| 2245 | Intf( extInterface, node=newSwitch ) |
| 2246 | |
| 2247 | elif 'LegacySwitch' in tags: |
| 2248 | newSwitch = net.addSwitch( name , cls=LegacySwitch) |
| 2249 | elif 'LegacyRouter' in tags: |
| 2250 | newSwitch = net.addHost( name , cls=LegacyRouter) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2251 | elif 'Host' in tags: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2252 | opts = self.hostOpts[name] |
| 2253 | #print str(opts) |
| 2254 | ip = None |
| 2255 | defaultRoute = None |
| 2256 | if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: |
| 2257 | defaultRoute = 'via '+opts['defaultRoute'] |
| 2258 | if 'ip' in opts and len(opts['ip']) > 0: |
| 2259 | ip = opts['ip'] |
| 2260 | else: |
| 2261 | nodeNum = self.hostOpts[name]['nodeNum'] |
| 2262 | ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) |
| 2263 | ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) |
| 2264 | |
| 2265 | # Create the correct host class |
| 2266 | if 'cores' in opts or 'cpu' in opts: |
| 2267 | if ('privateDirectory' in opts): |
| 2268 | hostCls = partial( CPULimitedHost, |
| 2269 | privateDirs=opts['privateDirectory'] ) |
| 2270 | else: |
| 2271 | hostCls=CPULimitedHost |
| 2272 | else: |
| 2273 | if ('privateDirectory' in opts): |
| 2274 | hostCls = partial( Host, |
| 2275 | privateDirs=opts['privateDirectory'] ) |
| 2276 | else: |
| 2277 | hostCls=Host |
| 2278 | print hostCls |
| 2279 | newHost = net.addHost( name, |
| 2280 | cls=hostCls, |
| 2281 | ip=ip, |
| 2282 | defaultRoute=defaultRoute |
| 2283 | ) |
| 2284 | |
| 2285 | # Set the CPULimitedHost specific options |
| 2286 | if 'cores' in opts: |
| 2287 | newHost.setCPUs(cores = opts['cores']) |
| 2288 | if 'cpu' in opts: |
| 2289 | newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched']) |
| 2290 | |
| 2291 | # Attach external interfaces |
| 2292 | if ('externalInterfaces' in opts): |
| 2293 | for extInterface in opts['externalInterfaces']: |
| 2294 | if self.checkIntf(extInterface): |
| 2295 | Intf( extInterface, node=newHost ) |
| 2296 | if ('vlanInterfaces' in opts): |
| 2297 | if len(opts['vlanInterfaces']) > 0: |
| 2298 | print 'Checking that OS is VLAN prepared' |
| 2299 | self.pathCheck('vconfig', moduleName='vlan package') |
| 2300 | moduleDeps( add='8021q' ) |
| 2301 | elif 'Controller' in tags: |
| 2302 | opts = self.controllers[name] |
| 2303 | |
| 2304 | # Get controller info from panel |
| 2305 | controllerType = opts['controllerType'] |
| 2306 | if 'controllerProtocol' in opts: |
| 2307 | controllerProtocol = opts['controllerProtocol'] |
| 2308 | else: |
| 2309 | controllerProtocol = 'tcp' |
| 2310 | opts['controllerProtocol'] = 'tcp' |
| 2311 | controllerIP = opts['remoteIP'] |
| 2312 | controllerPort = opts['remotePort'] |
| 2313 | |
| 2314 | # Make controller |
| 2315 | print 'Getting controller selection:'+controllerType |
| 2316 | if controllerType == 'remote': |
| 2317 | net.addController(name=name, |
| 2318 | controller=RemoteController, |
| 2319 | ip=controllerIP, |
| 2320 | protocol=controllerProtocol, |
| 2321 | port=controllerPort) |
| 2322 | elif controllerType == 'inband': |
| 2323 | net.addController(name=name, |
| 2324 | controller=InbandController, |
| 2325 | ip=controllerIP, |
| 2326 | protocol=controllerProtocol, |
| 2327 | port=controllerPort) |
| 2328 | elif controllerType == 'ovsc': |
| 2329 | net.addController(name=name, |
| 2330 | controller=OVSController, |
| 2331 | protocol=controllerProtocol, |
| 2332 | port=controllerPort) |
| 2333 | else: |
| 2334 | net.addController(name=name, |
| 2335 | controller=Controller, |
| 2336 | protocol=controllerProtocol, |
| 2337 | port=controllerPort) |
| 2338 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2339 | else: |
| 2340 | raise Exception( "Cannot create mystery node: " + name ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2341 | |
| 2342 | def pathCheck( self, *args, **kwargs ): |
| 2343 | "Make sure each program in *args can be found in $PATH." |
| 2344 | moduleName = kwargs.get( 'moduleName', 'it' ) |
| 2345 | for arg in args: |
| 2346 | if not quietRun( 'which ' + arg ): |
| 2347 | showerror(title="Error", |
| 2348 | message= 'Cannot find required executable %s.\n' % arg + |
| 2349 | 'Please make sure that %s is installed ' % moduleName + |
| 2350 | 'and available in your $PATH.' ) |
| 2351 | |
| 2352 | def buildLinks( self, net): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2353 | # Make links |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2354 | print "Getting Links." |
| 2355 | for key,link in self.links.iteritems(): |
| 2356 | tags = self.canvas.gettags(key) |
| 2357 | if 'data' in tags: |
| 2358 | src=link['src'] |
| 2359 | dst=link['dest'] |
| 2360 | linkopts=link['linkOpts'] |
| 2361 | srcName, dstName = src[ 'text' ], dst[ 'text' ] |
| 2362 | srcNode, dstNode = net.nameToNode[ srcName ], net.nameToNode[ dstName ] |
| 2363 | if linkopts: |
| 2364 | net.addLink(srcNode, dstNode, cls=TCLink, **linkopts) |
| 2365 | else: |
| 2366 | #print str(srcNode) |
| 2367 | #print str(dstNode) |
| 2368 | net.addLink(srcNode, dstNode) |
| 2369 | self.canvas.itemconfig(key, dash=()) |
| 2370 | |
| 2371 | |
| 2372 | def build( self ): |
| 2373 | print "Build network based on our topology." |
| 2374 | |
| 2375 | dpctl = None |
| 2376 | if len(self.appPrefs['dpctl']) > 0: |
| 2377 | dpctl = int(self.appPrefs['dpctl']) |
| 2378 | net = Mininet( topo=None, |
| 2379 | listenPort=dpctl, |
| 2380 | build=False, |
| 2381 | ipBase=self.appPrefs['ipBase'] ) |
| 2382 | |
| 2383 | self.buildNodes(net) |
| 2384 | self.buildLinks(net) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2385 | |
| 2386 | # Build network (we have to do this separately at the moment ) |
| 2387 | net.build() |
| 2388 | |
| 2389 | return net |
| 2390 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2391 | |
| 2392 | def postStartSetup( self ): |
| 2393 | |
| 2394 | # Setup host details |
| 2395 | for widget in self.widgetToItem: |
| 2396 | name = widget[ 'text' ] |
| 2397 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 2398 | if 'Host' in tags: |
| 2399 | newHost = self.net.get(name) |
| 2400 | opts = self.hostOpts[name] |
| 2401 | # Attach vlan interfaces |
| 2402 | if ('vlanInterfaces' in opts): |
| 2403 | for vlanInterface in opts['vlanInterfaces']: |
| 2404 | print 'adding vlan interface '+vlanInterface[1] |
| 2405 | newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0]) |
| 2406 | # Run User Defined Start Command |
| 2407 | if ('startCommand' in opts): |
| 2408 | newHost.cmdPrint(opts['startCommand']) |
| 2409 | if 'Switch' in tags: |
| 2410 | newNode = self.net.get(name) |
| 2411 | opts = self.switchOpts[name] |
| 2412 | # Run User Defined Start Command |
| 2413 | if ('startCommand' in opts): |
| 2414 | newNode.cmdPrint(opts['startCommand']) |
| 2415 | |
| 2416 | |
| 2417 | # Configure NetFlow |
| 2418 | nflowValues = self.appPrefs['netflow'] |
| 2419 | if len(nflowValues['nflowTarget']) > 0: |
| 2420 | nflowEnabled = False |
| 2421 | nflowSwitches = '' |
| 2422 | for widget in self.widgetToItem: |
| 2423 | name = widget[ 'text' ] |
| 2424 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 2425 | |
| 2426 | if 'Switch' in tags: |
| 2427 | opts = self.switchOpts[name] |
| 2428 | if 'netflow' in opts: |
| 2429 | if opts['netflow'] == '1': |
| 2430 | print name+' has Netflow enabled' |
| 2431 | nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF' |
| 2432 | nflowEnabled=True |
| 2433 | if nflowEnabled: |
| 2434 | nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout'] |
| 2435 | if nflowValues['nflowAddId'] == '1': |
| 2436 | nflowCmd = nflowCmd + ' add_id_to_interface=true' |
| 2437 | else: |
| 2438 | nflowCmd = nflowCmd + ' add_id_to_interface=false' |
| 2439 | print 'cmd = '+nflowCmd+nflowSwitches |
| 2440 | call(nflowCmd+nflowSwitches, shell=True) |
| 2441 | |
| 2442 | else: |
| 2443 | print 'No switches with Netflow' |
| 2444 | else: |
| 2445 | print 'No NetFlow targets specified.' |
| 2446 | |
| 2447 | # Configure sFlow |
| 2448 | sflowValues = self.appPrefs['sflow'] |
| 2449 | if len(sflowValues['sflowTarget']) > 0: |
| 2450 | sflowEnabled = False |
| 2451 | sflowSwitches = '' |
| 2452 | for widget in self.widgetToItem: |
| 2453 | name = widget[ 'text' ] |
| 2454 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 2455 | |
| 2456 | if 'Switch' in tags: |
| 2457 | opts = self.switchOpts[name] |
| 2458 | if 'sflow' in opts: |
| 2459 | if opts['sflow'] == '1': |
| 2460 | print name+' has sflow enabled' |
| 2461 | sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF' |
| 2462 | sflowEnabled=True |
| 2463 | if sflowEnabled: |
| 2464 | sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling'] |
| 2465 | print 'cmd = '+sflowCmd+sflowSwitches |
| 2466 | call(sflowCmd+sflowSwitches, shell=True) |
| 2467 | |
| 2468 | else: |
| 2469 | print 'No switches with sflow' |
| 2470 | else: |
| 2471 | print 'No sFlow targets specified.' |
| 2472 | |
| 2473 | ## NOTE: MAKE SURE THIS IS LAST THING CALLED |
| 2474 | # Start the CLI if enabled |
| 2475 | if self.appPrefs['startCLI'] == '1': |
| 2476 | 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") |
| 2477 | CLI(self.net) |
| 2478 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2479 | def start( self ): |
| 2480 | "Start network." |
| 2481 | if self.net is None: |
| 2482 | self.net = self.build() |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2483 | |
| 2484 | # Since I am going to inject per switch controllers. |
| 2485 | # I can't call net.start(). I have to replicate what it |
| 2486 | # does and add the controller options. |
| 2487 | #self.net.start() |
| 2488 | info( '**** Starting %s controllers\n' % len( self.net.controllers ) ) |
| 2489 | for controller in self.net.controllers: |
| 2490 | info( str(controller) + ' ') |
| 2491 | controller.start() |
| 2492 | info('\n') |
| 2493 | info( '**** Starting %s switches\n' % len( self.net.switches ) ) |
| 2494 | #for switch in self.net.switches: |
| 2495 | # info( switch.name + ' ') |
| 2496 | # switch.start( self.net.controllers ) |
| 2497 | for widget in self.widgetToItem: |
| 2498 | name = widget[ 'text' ] |
| 2499 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 2500 | if 'Switch' in tags: |
| 2501 | opts = self.switchOpts[name] |
| 2502 | switchControllers = [] |
| 2503 | for ctrl in opts['controllers']: |
| 2504 | switchControllers.append(self.net.get(ctrl)) |
| 2505 | info( name + ' ') |
| 2506 | # Figure out what controllers will manage this switch |
| 2507 | self.net.get(name).start( switchControllers ) |
| 2508 | if 'LegacySwitch' in tags: |
| 2509 | self.net.get(name).start( [] ) |
| 2510 | info( name + ' ') |
| 2511 | info('\n') |
| 2512 | |
| 2513 | self.postStartSetup() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2514 | |
| 2515 | def stop( self ): |
| 2516 | "Stop network." |
| 2517 | if self.net is not None: |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2518 | # Stop host details |
| 2519 | for widget in self.widgetToItem: |
| 2520 | name = widget[ 'text' ] |
| 2521 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) |
| 2522 | if 'Host' in tags: |
| 2523 | newHost = self.net.get(name) |
| 2524 | opts = self.hostOpts[name] |
| 2525 | # Run User Defined Stop Command |
| 2526 | if ('stopCommand' in opts): |
| 2527 | newHost.cmdPrint(opts['stopCommand']) |
| 2528 | if 'Switch' in tags: |
| 2529 | newNode = self.net.get(name) |
| 2530 | opts = self.switchOpts[name] |
| 2531 | # Run User Defined Stop Command |
| 2532 | if ('stopCommand' in opts): |
| 2533 | newNode.cmdPrint(opts['stopCommand']) |
| 2534 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2535 | self.net.stop() |
| 2536 | cleanUpScreens() |
| 2537 | self.net = None |
| 2538 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2539 | def do_linkPopup(self, event): |
| 2540 | # display the popup menu |
| 2541 | if ( self.net is None ): |
| 2542 | try: |
| 2543 | self.linkPopup.tk_popup(event.x_root, event.y_root) |
| 2544 | finally: |
| 2545 | # make sure to release the grab (Tk 8.0a1 only) |
| 2546 | self.linkPopup.grab_release() |
| 2547 | else: |
| 2548 | try: |
| 2549 | self.linkPopup.tk_popup(event.x_root, event.y_root) |
| 2550 | finally: |
| 2551 | # make sure to release the grab (Tk 8.0a1 only) |
| 2552 | self.linkPopup.grab_release() |
| 2553 | |
| 2554 | def do_legacyRouterPopup(self, event): |
| 2555 | # display the popup menu |
| 2556 | if ( self.net is None ): |
| 2557 | try: |
| 2558 | self.legacyRouterPopup.tk_popup(event.x_root, event.y_root) |
| 2559 | finally: |
| 2560 | # make sure to release the grab (Tk 8.0a1 only) |
| 2561 | self.legacyRouterPopup.grab_release() |
| 2562 | |
| 2563 | def do_hostPopup(self, event): |
| 2564 | # display the popup menu |
| 2565 | |
| 2566 | try: |
| 2567 | self.hostPopup.tk_popup(event.x_root, event.y_root) |
| 2568 | isHostPopup = True |
| 2569 | finally: |
| 2570 | # make sure to release the grab (Tk 8.0a1 only) |
| 2571 | self.hostPopup.grab_release() |
| 2572 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2573 | def xterm( self, _ignore=None ): |
| 2574 | "Make an xterm when a button is pressed." |
| 2575 | if ( self.selection is None or |
| 2576 | self.net is None or |
| 2577 | self.selection not in self.itemToWidget ): |
| 2578 | return |
| 2579 | name = self.itemToWidget[ self.selection ][ 'text' ] |
| 2580 | if name not in self.net.nameToNode: |
| 2581 | return |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2582 | term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] ) |
| 2583 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): |
| 2584 | self.net.terms += term |
| 2585 | else: |
| 2586 | self.net.terms.append(term) |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2587 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2588 | def iperf( self, _ignore=None ): |
| 2589 | "Make an xterm when a button is pressed." |
| 2590 | if ( self.selection is None or |
| 2591 | self.net is None or |
| 2592 | self.selection not in self.itemToWidget ): |
| 2593 | return |
| 2594 | name = self.itemToWidget[ self.selection ][ 'text' ] |
| 2595 | if name not in self.net.nameToNode: |
| 2596 | return |
| 2597 | self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' ) |
| 2598 | |
| 2599 | """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """ |
| 2600 | |
| 2601 | def parseArgs( self ): |
| 2602 | """Parse command-line args and return options object. |
| 2603 | returns: opts parse options dict""" |
| 2604 | |
| 2605 | if '--custom' in sys.argv: |
| 2606 | index = sys.argv.index( '--custom' ) |
| 2607 | if len( sys.argv ) > index + 1: |
| 2608 | filename = sys.argv[ index + 1 ] |
| 2609 | self.parseCustomFile( filename ) |
| 2610 | else: |
| 2611 | raise Exception( 'Custom file name not found' ) |
| 2612 | |
| 2613 | desc = ( "The %prog utility creates Mininet network from the\n" |
| 2614 | "command line. It can create parametrized topologies,\n" |
| 2615 | "invoke the Mininet CLI, and run tests." ) |
| 2616 | |
| 2617 | usage = ( '%prog [options]\n' |
| 2618 | '(type %prog -h for details)' ) |
| 2619 | |
| 2620 | opts = OptionParser( description=desc, usage=usage ) |
| 2621 | |
| 2622 | addDictOption( opts, TOPOS, TOPODEF, 'topo' ) |
| 2623 | addDictOption( opts, LINKS, LINKDEF, 'link' ) |
| 2624 | |
| 2625 | opts.add_option( '--custom', type='string', default=None, |
| 2626 | help='read custom topo and node params from .py' + |
| 2627 | 'file' ) |
| 2628 | |
| 2629 | self.options, self.args = opts.parse_args() |
| 2630 | # We don't accept extra arguments after the options |
| 2631 | if self.args: |
| 2632 | opts.print_help() |
| 2633 | exit() |
| 2634 | |
| 2635 | def setCustom( self, name, value ): |
| 2636 | "Set custom parameters for MininetRunner." |
| 2637 | if name in ( 'topos', 'switches', 'hosts', 'controllers' ): |
| 2638 | # Update dictionaries |
| 2639 | param = name.upper() |
| 2640 | globals()[ param ].update( value ) |
| 2641 | elif name == 'validate': |
| 2642 | # Add custom validate function |
| 2643 | self.validate = value |
| 2644 | else: |
| 2645 | # Add or modify global variable or class |
| 2646 | globals()[ name ] = value |
| 2647 | |
| 2648 | def parseCustomFile( self, fileName ): |
| 2649 | "Parse custom file and add params before parsing cmd-line options." |
| 2650 | customs = {} |
| 2651 | if os.path.isfile( fileName ): |
| 2652 | execfile( fileName, customs, customs ) |
| 2653 | for name, val in customs.iteritems(): |
| 2654 | self.setCustom( name, val ) |
| 2655 | else: |
| 2656 | raise Exception( 'could not find custom file: %s' % fileName ) |
| 2657 | |
| 2658 | def importTopo( self ): |
| 2659 | print 'topo='+self.options.topo |
| 2660 | if self.options.topo == 'none': |
| 2661 | return |
| 2662 | self.newTopology() |
| 2663 | topo = buildTopo( TOPOS, self.options.topo ) |
| 2664 | link = customConstructor( LINKS, self.options.link ) |
| 2665 | importNet = Mininet(topo=topo, build=False, link=link) |
| 2666 | importNet.build() |
| 2667 | |
| 2668 | c = self.canvas |
| 2669 | rowIncrement = 100 |
| 2670 | currentY = 100 |
| 2671 | |
| 2672 | # Add Controllers |
| 2673 | print 'controllers:'+str(len(importNet.controllers)) |
| 2674 | for controller in importNet.controllers: |
| 2675 | name = controller.name |
| 2676 | x = self.controllerCount*100+100 |
| 2677 | self.addNode('Controller', self.controllerCount, |
| 2678 | float(x), float(currentY), name=name) |
| 2679 | icon = self.findWidgetByName(name) |
| 2680 | #icon.bind('<Button-3>', self.do_controllerPopup ) |
| 2681 | ctrlr = { 'controllerType': 'ref', |
| 2682 | 'hostname': name, |
| 2683 | 'controllerProtocol': controller.protocol, |
| 2684 | 'remoteIP': controller.ip, |
| 2685 | 'remotePort': controller.port} |
| 2686 | self.controllers[name] = ctrlr |
| 2687 | |
| 2688 | |
| 2689 | |
| 2690 | currentY = currentY + rowIncrement |
| 2691 | |
| 2692 | # Add switches |
| 2693 | print 'switches:'+str(len(importNet.switches)) |
| 2694 | columnCount = 0 |
| 2695 | for switch in importNet.switches: |
| 2696 | name = switch.name |
| 2697 | self.switchOpts[name] = {} |
| 2698 | self.switchOpts[name]['nodeNum']=self.switchCount |
| 2699 | self.switchOpts[name]['hostname']=name |
| 2700 | self.switchOpts[name]['switchType']='default' |
| 2701 | self.switchOpts[name]['controllers']=[] |
| 2702 | |
| 2703 | x = columnCount*100+100 |
| 2704 | self.addNode('Switch', self.switchCount, |
| 2705 | float(x), float(currentY), name=name) |
| 2706 | icon = self.findWidgetByName(name) |
| 2707 | icon.bind('<Button-3>', self.do_switchPopup ) |
| 2708 | # Now link to controllers |
| 2709 | for controller in importNet.controllers: |
| 2710 | self.switchOpts[name]['controllers'].append(controller.name) |
| 2711 | dest = self.findWidgetByName(controller.name) |
| 2712 | dx, dy = c.coords( self.widgetToItem[ dest ] ) |
| 2713 | self.link = c.create_line(float(x), |
| 2714 | float(currentY), |
| 2715 | dx, |
| 2716 | dy, |
| 2717 | width=4, |
| 2718 | fill='red', |
| 2719 | dash=(6, 4, 2, 4), |
| 2720 | tag='link' ) |
| 2721 | c.itemconfig(self.link, tags=c.gettags(self.link)+('control',)) |
| 2722 | self.addLink( icon, dest, linktype='control' ) |
| 2723 | self.createControlLinkBindings() |
| 2724 | self.link = self.linkWidget = None |
| 2725 | if columnCount == 9: |
| 2726 | columnCount = 0 |
| 2727 | currentY = currentY + rowIncrement |
| 2728 | else: |
| 2729 | columnCount =columnCount+1 |
| 2730 | |
| 2731 | |
| 2732 | currentY = currentY + rowIncrement |
| 2733 | # Add hosts |
| 2734 | print 'hosts:'+str(len(importNet.hosts)) |
| 2735 | columnCount = 0 |
| 2736 | for host in importNet.hosts: |
| 2737 | name = host.name |
| 2738 | self.hostOpts[name] = {'sched':'host'} |
| 2739 | self.hostOpts[name]['nodeNum']=self.hostCount |
| 2740 | self.hostOpts[name]['hostname']=name |
| 2741 | self.hostOpts[name]['ip']=host.IP() |
| 2742 | |
| 2743 | x = columnCount*100+100 |
| 2744 | self.addNode('Host', self.hostCount, |
| 2745 | float(x), float(currentY), name=name) |
| 2746 | icon = self.findWidgetByName(name) |
| 2747 | icon.bind('<Button-3>', self.do_hostPopup ) |
| 2748 | if columnCount == 9: |
| 2749 | columnCount = 0 |
| 2750 | currentY = currentY + rowIncrement |
| 2751 | else: |
| 2752 | columnCount =columnCount+1 |
| 2753 | |
| 2754 | print 'links:'+str(len(topo.links())) |
| 2755 | #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')] |
| 2756 | for link in topo.links(): |
| 2757 | print str(link) |
| 2758 | srcNode = link[0] |
| 2759 | src = self.findWidgetByName(srcNode) |
| 2760 | sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) |
| 2761 | |
| 2762 | destNode = link[1] |
| 2763 | dest = self.findWidgetByName(destNode) |
| 2764 | dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) |
| 2765 | |
| 2766 | params = topo.linkInfo( srcNode, destNode ) |
| 2767 | print 'Link Parameters='+str(params) |
| 2768 | |
| 2769 | self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, |
| 2770 | fill='blue', tag='link' ) |
| 2771 | c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) |
| 2772 | self.addLink( src, dest, linkopts=params ) |
| 2773 | self.createDataLinkBindings() |
| 2774 | self.link = self.linkWidget = None |
| 2775 | |
| 2776 | importNet.stop() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2777 | |
| 2778 | def miniEditImages(): |
| 2779 | "Create and return images for MiniEdit." |
| 2780 | |
| 2781 | # Image data. Git will be unhappy. However, the alternative |
| 2782 | # is to keep track of separate binary files, which is also |
| 2783 | # unappealing. |
| 2784 | |
| 2785 | return { |
| 2786 | 'Select': BitmapImage( |
| 2787 | file='/usr/include/X11/bitmaps/left_ptr' ), |
| 2788 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2789 | 'LegacyRouter': PhotoImage( data=r""" |
| 2790 | R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+3QFq1DmL3wJMmAMzZZW11dnZ |
| 2791 | 2SFrtyNdmTSO6gIZMUKa8gJVqEOHzR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq |
| 2792 | 6ymF4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9vwNgvwJZsX+69gsXJQFH |
| 2793 | jTtjizF0tvHx8VOm9z2V736Dhz2N3QM2acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtg |
| 2794 | tktjfQFu3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh312Gt+VGm/AQIDTmB |
| 2795 | yAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i56gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia |
| 2796 | 7DpeggFt2QNPm97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er/yVVhwJJktPh |
| 2797 | 70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVVhQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18 |
| 2798 | zKfP9wwcLAMHCwFFiS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh7cve8pG/ |
| 2799 | 7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR1RMjNTF3vU2X4TZupwRSolNne4nB+T+L |
| 2800 | 2YGz4zJ/zYe99YGHjRdDcT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6WlpW2t |
| 2801 | 7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90uvPz8wIVKBp42SV5zbfT7wtXpStV |
| 2802 | fwFWrBVvyTt3swFz5kGBv2+1/QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t |
| 2803 | u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T5yH5BAEAAAAALAAAAAAyABgA |
| 2804 | Bwj/AAEIHEiQYJY7Qwg9UsTplRIbENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4c |
| 2805 | HeoIabJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPATqEBoRB9gVJsxRlhPwHI |
| 2806 | 0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkS |
| 2807 | rtwADuxCG/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmDjhTMmseoKQIFDx7R |
| 2808 | oxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq |
| 2809 | 9dGvv09RHFhcIUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p9HEUFhxgMSAv |
| 2810 | jbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CF |
| 2811 | oVggmEgCyRf01WcFCYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57AgyZckpKKP |
| 2812 | GFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemMIQggeaJywSQ/wgHOAmJskQEfWqBlFBEH |
| 2813 | 1P/QaGY3QOpDZXA2+A6m7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlBpZdi |
| 2814 | isd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtChRmVPNWgpr+Be+Nc9icARww9TkIEu |
| 2815 | DAsQ0O7DzGIQzD2QdDEJHTsIAROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB |
| 2816 | xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE42Q9jtFIp8z0Dy1jQMA1AGzi |
| 2817 | z9VoW7310V0znYDTGMQgwUDXLDBO2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOM |
| 2818 | LQkcjvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZMDHKCTwI8EcQFHBBAAFc |
| 2819 | gGPLHwLwcMIo12Qxu0ABAQA7 |
| 2820 | """), |
| 2821 | |
| 2822 | 'Host': PhotoImage( data=r""" |
| 2823 | R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M |
| 2824 | mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m |
| 2825 | Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A |
| 2826 | M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM |
| 2827 | AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz |
| 2828 | /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ |
| 2829 | zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ |
| 2830 | mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz |
| 2831 | ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ |
| 2832 | M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ |
| 2833 | AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA |
| 2834 | /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM |
| 2835 | zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm |
| 2836 | mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA |
| 2837 | ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM |
| 2838 | MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm |
| 2839 | AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A |
| 2840 | ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI |
| 2841 | AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA |
| 2842 | RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA |
| 2843 | ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw |
| 2844 | BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2 |
| 2845 | HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO |
| 2846 | p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/ |
| 2847 | C8cSBBAQADs= |
| 2848 | """ ), |
| 2849 | |
| 2850 | 'NetLink': PhotoImage( data=r""" |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2851 | R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M |
| 2852 | mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m |
| 2853 | Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A |
| 2854 | M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM |
| 2855 | AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz |
| 2856 | /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ |
| 2857 | zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ |
| 2858 | mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz |
| 2859 | ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ |
| 2860 | M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ |
| 2861 | AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA |
| 2862 | /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM |
| 2863 | zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm |
| 2864 | mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA |
| 2865 | ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM |
| 2866 | MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm |
| 2867 | AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A |
| 2868 | ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI |
| 2869 | AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA |
| 2870 | RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA |
| 2871 | ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG |
| 2872 | Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy |
| 2873 | lBmxI8mSNknm1Dnx5sCAADs= |
| 2874 | """ ) |
| 2875 | } |
| 2876 | |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2877 | def addDictOption( opts, choicesDict, default, name, helpStr=None ): |
| 2878 | """Convenience function to add choices dicts to OptionParser. |
| 2879 | opts: OptionParser instance |
| 2880 | choicesDict: dictionary of valid choices, must include default |
| 2881 | default: default choice key |
| 2882 | name: long option name |
| 2883 | help: string""" |
| 2884 | if default not in choicesDict: |
| 2885 | raise Exception( 'Invalid default %s for choices dict: %s' % |
| 2886 | ( default, name ) ) |
| 2887 | if not helpStr: |
| 2888 | helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + |
| 2889 | '[,param=value...]' ) |
| 2890 | opts.add_option( '--' + name, |
| 2891 | type='string', |
| 2892 | default = default, |
| 2893 | help = helpStr ) |
| 2894 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2895 | if __name__ == '__main__': |
| 2896 | setLogLevel( 'info' ) |
Caio Elias | 47b243c | 2014-11-23 19:25:34 -0200 | [diff] [blame^] | 2897 | app = MiniEdit() |
| 2898 | """ import topology if specified """ |
| 2899 | app.parseArgs() |
| 2900 | app.importTopo() |
| 2901 | |
| 2902 | global isHostPopup |
| 2903 | global isRouterPopup |
| 2904 | global isLinkPopup |
| 2905 | |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 2906 | app.mainloop() |