Академический Документы
Профессиональный Документы
Культура Документы
/usr/bin/python
"""
MiniEdit: a simple network editor for Mininet
This is a simple demonstration of how one might build a
GUI application using Mininet as the network model.
Bob Lantz, April 2010
Gregory Gee, July 2013
Controller icon from http://semlabs.co.uk/
OpenFlow icon from https://www.opennetworking.org/
"""
MINIEDIT_VERSION = '2.2.0.1'
import sys
from optparse import OptionParser
from subprocess import call
# pylint: disable=import-error
if sys.version_info[0] == 2:
from Tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu,
Checkbutton, Menu, Toplevel, Button, BitmapImage,
PhotoImage, Canvas, Scrollbar, Wm, TclError,
StringVar, IntVar, E, W, EW, NW, Y, VERTICAL, SOLID,
CENTER, RIGHT, LEFT, BOTH, TRUE, FALSE )
from ttk import Notebook
from tkMessageBox import showerror
import tkFont
import tkFileDialog
import tkSimpleDialog
else:
from tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu,
Checkbutton, Menu, Toplevel, Button, BitmapImage,
PhotoImage, Canvas, Scrollbar, Wm, TclError,
StringVar, IntVar, E, W, EW, NW, Y, VERTICAL, SOLID,
CENTER, RIGHT, LEFT, BOTH, TRUE, FALSE )
from tkinter.ttk import Notebook
from tkinter.messagebox import showerror
from tkinter import font as tkFont
from tkinter import simpledialog as tkSimpleDialog
from tkinter import filedialog as tkFileDialog
# pylint: enable=import-error
import re
import json
from distutils.version import StrictVersion
import os
from functools import partial
if 'PYTHONPATH' in os.environ:
sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
TOPODEF = 'none'
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
'linear': LinearTopo,
'reversed': SingleSwitchReversedTopo,
'single': SingleSwitchTopo,
'none': None,
'tree': TreeTopo }
CONTROLLERDEF = 'ref'
CONTROLLERS = { 'ref': Controller,
'ovsc': OVSController,
'nox': NOX,
'remote': RemoteController,
'none': lambda name: None }
LINKDEF = 'default'
LINKS = { 'default': Link,
'tc': TCLink }
HOSTDEF = 'proc'
HOSTS = { 'proc': Host,
'rt': custom( CPULimitedHost, sched='rt' ),
'cfs': custom( CPULimitedHost, sched='cfs' ) }
class CustomUserSwitch(UserSwitch):
"Customized UserSwitch"
def __init__( self, name, dpopts='--no-slicing', **kwargs ):
UserSwitch.__init__( self, name, **kwargs )
self.switchIP = None
def getSwitchIP(self):
"Return management IP address"
return self.switchIP
class LegacySwitch(OVSSwitch):
"OVS switch in standalone/bridge mode"
def __init__( self, name, **params ):
OVSSwitch.__init__( self, name, failMode='standalone', **params )
self.switchIP = None
class customOvs(OVSSwitch):
"Customized OVS switch"
def getSwitchIP(self):
"Return management IP address"
return self.switchIP
class PrefsDialog(tkSimpleDialog.Dialog):
"Preferences dialog"
def __init__(self, parent, title, prefDefaults):
self.prefValues = prefDefaults
self.ovsOf10 = IntVar()
self.covsOf10 = Checkbutton(ovsFrame, variable=self.ovsOf10)
self.covsOf10.grid(row=0, column=1, sticky=W)
if self.prefValues['openFlowVersions']['ovsOf10'] == '0':
self.covsOf10.deselect()
else:
self.covsOf10.select()
self.ovsOf11 = IntVar()
self.covsOf11 = Checkbutton(ovsFrame, variable=self.ovsOf11)
self.covsOf11.grid(row=1, column=1, sticky=W)
if self.prefValues['openFlowVersions']['ovsOf11'] == '0':
self.covsOf11.deselect()
else:
self.covsOf11.select()
self.ovsOf12 = IntVar()
self.covsOf12 = Checkbutton(ovsFrame, variable=self.ovsOf12)
self.covsOf12.grid(row=2, column=1, sticky=W)
if self.prefValues['openFlowVersions']['ovsOf12'] == '0':
self.covsOf12.deselect()
else:
self.covsOf12.select()
self.ovsOf13 = IntVar()
self.covsOf13 = Checkbutton(ovsFrame, variable=self.ovsOf13)
self.covsOf13.grid(row=3, column=1, sticky=W)
if self.prefValues['openFlowVersions']['ovsOf13'] == '0':
self.covsOf13.deselect()
else:
self.covsOf13.select()
# sFlow
sflowValues = self.prefValues['sflow']
self.sflowFrame= LabelFrame(self.rightfieldFrame, text='sFlow Profile for
Open vSwitch', padx=5, pady=5)
self.sflowFrame.grid(row=0, column=0, columnspan=2, sticky=EW)
# NetFlow
nflowValues = self.prefValues['netflow']
self.nFrame= LabelFrame(self.rightfieldFrame, text='NetFlow Profile for
Open vSwitch', padx=5, pady=5)
self.nFrame.grid(row=1, column=0, columnspan=2, sticky=EW)
# initial focus
return self.ipEntry
def apply(self):
ipBase = self.ipEntry.get()
terminalType = self.terminalVar.get()
startCLI = str(self.cliStart.get())
sw = self.switchType.get()
dpctl = self.dpctlEntry.get()
ovsOf10 = str(self.ovsOf10.get())
ovsOf11 = str(self.ovsOf11.get())
ovsOf12 = str(self.ovsOf12.get())
ovsOf13 = str(self.ovsOf13.get())
sflowValues = {'sflowTarget':self.sflowTarget.get(),
'sflowSampling':self.sflowSampling.get(),
'sflowHeader':self.sflowHeader.get(),
'sflowPolling':self.sflowPolling.get()}
nflowvalues = {'nflowTarget':self.nflowTarget.get(),
'nflowTimeout':self.nflowTimeout.get(),
'nflowAddId':str(self.nflowAddId.get())}
self.result = {'ipBase':ipBase,
'terminalType':terminalType,
'dpctl':dpctl,
'sflow':sflowValues,
'netflow':nflowvalues,
'startCLI':startCLI}
if sw == 'Indigo Virtual Switch':
self.result['switchType'] = 'ivs'
if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
self.ovsOk = False
showerror(title="Error",
message='MiniNet version 2.1+ required. You have
'+VERSION+'.')
elif sw == 'Userspace Switch':
self.result['switchType'] = 'user'
elif sw == 'Userspace Switch inNamespace':
self.result['switchType'] = 'userns'
else:
self.result['switchType'] = 'ovs'
self.ovsOk = True
if ovsOf11 == "1":
ovsVer = self.getOvsVersion()
if StrictVersion(ovsVer) < StrictVersion('2.0'):
self.ovsOk = False
showerror(title="Error",
message='Open vSwitch version 2.0+ required. You have
'+ovsVer+'.')
if ovsOf12 == "1" or ovsOf13 == "1":
ovsVer = self.getOvsVersion()
if StrictVersion(ovsVer) < StrictVersion('1.10'):
self.ovsOk = False
showerror(title="Error",
message='Open vSwitch version 1.10+ required. You have
'+ovsVer+'.')
if self.ovsOk:
self.result['openFlowVersions']={'ovsOf10':ovsOf10,
'ovsOf11':ovsOf11,
'ovsOf12':ovsOf12,
'ovsOf13':ovsOf13}
else:
self.result = None
@staticmethod
def getOvsVersion():
"Return OVS version"
outp = quietRun("ovs-vsctl --version")
r = r'ovs-vsctl \(Open vSwitch\) (.*)'
m = re.search(r, outp)
if m is None:
warn( 'Version check failed' )
return None
else:
info( 'Open vSwitch version is '+m.group(1), '\n' )
return m.group(1)
class CustomDialog(object):
# TODO: Fix button placement and Title and window focus lock
def __init__(self, master, _title):
self.top=Toplevel(master)
self.bodyFrame = Frame(self.top)
self.bodyFrame.grid(row=0, column=0, sticky='nswe')
self.body(self.bodyFrame)
def apply(self):
self.top.destroy()
def cancelAction(self):
self.top.destroy()
def okAction(self):
self.apply()
self.top.destroy()
class HostDialog(CustomDialog):
self.prefValues = prefDefaults
self.result = None
### TAB 1
# Field for Hostname
Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E)
self.hostnameEntry = Entry(self.propFrame)
self.hostnameEntry.grid(row=0, column=1)
if 'hostname' in self.prefValues:
self.hostnameEntry.insert(0, self.prefValues['hostname'])
# Selection of Cores
Label(self.propFrame, text="Cores:").grid(row=4, sticky=E)
self.coreEntry = Entry(self.propFrame)
self.coreEntry.grid(row=4, column=1)
if 'cores' in self.prefValues:
self.coreEntry.insert(1, self.prefValues['cores'])
# Start command
Label(self.propFrame, text="Start Command:").grid(row=5, sticky=E)
self.startEntry = Entry(self.propFrame)
self.startEntry.grid(row=5, column=1, sticky='nswe', columnspan=3)
if 'startCommand' in self.prefValues:
self.startEntry.insert(0, str(self.prefValues['startCommand']))
# Stop command
Label(self.propFrame, text="Stop Command:").grid(row=6, sticky=E)
self.stopEntry = Entry(self.propFrame)
self.stopEntry.grid(row=6, column=1, sticky='nswe', columnspan=3)
if 'stopCommand' in self.prefValues:
self.stopEntry.insert(0, str(self.prefValues['stopCommand']))
### TAB 2
# External Interfaces
self.externalInterfaces = 0
Label(self.interfaceFrame, text="External Interface:").grid(row=0,
column=0, sticky=E)
self.b = Button( self.interfaceFrame, text='Add',
command=self.addInterface)
self.b.grid(row=0, column=1)
### TAB 3
# VLAN Interfaces
self.vlanInterfaces = 0
Label(self.vlanFrame, text="VLAN Interface:").grid(row=0, column=0,
sticky=E)
self.vlanButton = Button( self.vlanFrame, text='Add',
command=self.addVlanInterface)
self.vlanButton.grid(row=0, column=1)
vlanInterfaces = []
if 'vlanInterfaces' in self.prefValues:
vlanInterfaces = self.prefValues['vlanInterfaces']
for vlanInterface in vlanInterfaces:
self.vlanTableFrame.addRow(value=vlanInterface)
### TAB 4
# Private Directories
self.privateDirectories = 0
Label(self.mountFrame, text="Private Directory:").grid(row=0, column=0,
sticky=E)
self.mountButton = Button( self.mountFrame, text='Add',
command=self.addDirectory)
self.mountButton.grid(row=0, column=1)
def apply(self):
externalInterfaces = []
for row in range(self.tableFrame.rows):
if (len(self.tableFrame.get(row, 0)) > 0 and
row > 0):
externalInterfaces.append(self.tableFrame.get(row, 0))
vlanInterfaces = []
for row in range(self.vlanTableFrame.rows):
if (len(self.vlanTableFrame.get(row, 0)) > 0 and
len(self.vlanTableFrame.get(row, 1)) > 0 and
row > 0):
vlanInterfaces.append([self.vlanTableFrame.get(row, 0),
self.vlanTableFrame.get(row, 1)])
privateDirectories = []
for row in range(self.mountTableFrame.rows):
if len(self.mountTableFrame.get(row, 0)) > 0 and row > 0:
if len(self.mountTableFrame.get(row, 1)) > 0:
privateDirectories.append((self.mountTableFrame.get(row, 0),
self.mountTableFrame.get(row, 1)))
else:
privateDirectories.append(self.mountTableFrame.get(row, 0))
class SwitchDialog(CustomDialog):
self.prefValues = prefDefaults
self.result = None
CustomDialog.__init__(self, master, title)
rowCount = 0
externalInterfaces = []
if 'externalInterfaces' in self.prefValues:
externalInterfaces = self.prefValues['externalInterfaces']
# External Interfaces
Label(self.rightfieldFrame, text="External Interface:").grid(row=0,
sticky=E)
self.b = Button( self.rightfieldFrame, text='Add',
command=self.addInterface)
self.b.grid(row=0, column=1)
self.commandFrame = Frame(self.rootFrame)
self.commandFrame.grid(row=1, column=0, sticky='nswe', columnspan=2)
self.commandFrame.columnconfigure(1, weight=1)
# Start command
Label(self.commandFrame, text="Start Command:").grid(row=0, column=0,
sticky=W)
self.startEntry = Entry(self.commandFrame)
self.startEntry.grid(row=0, column=1, sticky='nsew')
if 'startCommand' in self.prefValues:
self.startEntry.insert(0, str(self.prefValues['startCommand']))
# Stop command
Label(self.commandFrame, text="Stop Command:").grid(row=1, column=0,
sticky=W)
self.stopEntry = Entry(self.commandFrame)
self.stopEntry.grid(row=1, column=1, sticky='nsew')
if 'stopCommand' in self.prefValues:
self.stopEntry.insert(0, str(self.prefValues['stopCommand']))
def apply(self):
externalInterfaces = []
for row in range(self.tableFrame.rows):
# debug( 'Interface is ' + self.tableFrame.get(row, 0), '\n' )
if len(self.tableFrame.get(row, 0)) > 0:
externalInterfaces.append(self.tableFrame.get(row, 0))
dpid = self.dpidEntry.get()
if (self.defaultDpid(self.hostnameEntry.get()) is None
and len(dpid) == 0):
showerror(title="Error",
message= 'Unable to derive default datapath ID - '
'please either specify a DPID or use a '
'canonical switch name such as s23.' )
results = {'externalInterfaces':externalInterfaces,
'hostname':self.hostnameEntry.get(),
'dpid':dpid,
'startCommand':self.startEntry.get(),
'stopCommand':self.stopEntry.get(),
'sflow':str(self.sflow.get()),
'netflow':str(self.nflow.get()),
'dpctl':self.dpctlEntry.get(),
'switchIP':self.ipEntry.get()}
sw = self.switchType.get()
if sw == 'Indigo Virtual Switch':
results['switchType'] = 'ivs'
if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
self.ovsOk = False
showerror(title="Error",
message='MiniNet version 2.1+ required. You have
'+VERSION+'.')
elif sw == 'Userspace Switch inNamespace':
results['switchType'] = 'userns'
elif sw == 'Userspace Switch':
results['switchType'] = 'user'
elif sw == 'Open vSwitch Kernel Mode':
results['switchType'] = 'ovs'
else:
results['switchType'] = 'default'
self.result = results
class VerticalScrolledTable(LabelFrame):
"""A pure Tkinter scrollable frame that actually works!
* Use the 'interior' attribute to place widgets inside the scrollable frame
* Construct and pack/place/grid normally
* This frame only allows vertical scrolling
"""
def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw):
LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw)
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(_event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(_event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
return
class TableFrame(Frame):
def __init__(self, parent, rows=2, columns=2):
class LinkDialog(tkSimpleDialog.Dialog):
self.linkValues = linkDefaults
self.var = StringVar(master)
Label(master, text="Bandwidth:").grid(row=0, sticky=E)
self.e1 = Entry(master)
self.e1.grid(row=0, column=1)
Label(master, text="Mbit").grid(row=0, column=2, sticky=W)
if 'bw' in self.linkValues:
self.e1.insert(0,str(self.linkValues['bw']))
Label(master, text="Delay:").grid(row=1, sticky=E)
self.e2 = Entry(master)
self.e2.grid(row=1, column=1)
if 'delay' in self.linkValues:
self.e2.insert(0, self.linkValues['delay'])
def apply(self):
self.result = {}
if len(self.e1.get()) > 0:
self.result['bw'] = int(self.e1.get())
if len(self.e2.get()) > 0:
self.result['delay'] = self.e2.get()
if len(self.e3.get()) > 0:
self.result['loss'] = int(self.e3.get())
if len(self.e4.get()) > 0:
self.result['max_queue_size'] = int(self.e4.get())
if len(self.e5.get()) > 0:
self.result['jitter'] = self.e5.get()
if len(self.e6.get()) > 0:
self.result['speedup'] = int(self.e6.get())
class ControllerDialog(tkSimpleDialog.Dialog):
if ctrlrDefaults:
self.ctrlrValues = ctrlrDefaults
rowCount=0
# Field for Hostname
Label(master, text="Name:").grid(row=rowCount, sticky=E)
self.hostnameEntry = Entry(master)
self.hostnameEntry.grid(row=rowCount, column=1)
self.hostnameEntry.insert(0, self.ctrlrValues['hostname'])
rowCount+=1
def apply(self):
self.result = { 'hostname': self.hostnameEntry.get(),
'remoteIP': self.e1.get(),
'remotePort': int(self.e2.get())}
controllerType = self.var.get()
if controllerType == 'Remote Controller':
self.result['controllerType'] = 'remote'
elif controllerType == 'In-Band Controller':
self.result['controllerType'] = 'inband'
elif controllerType == 'OpenFlow Reference':
self.result['controllerType'] = 'ref'
else:
self.result['controllerType'] = 'ovsc'
controllerProtocol = self.protcolvar.get()
if controllerProtocol == 'SSL':
self.result['controllerProtocol'] = 'ssl'
else:
self.result['controllerProtocol'] = 'tcp'
class ToolTip(object):
def hidetip(self):
tw = self.tipwindow
self.tipwindow = None
if tw:
tw.destroy()
self.defaultIpBase='10.0.0.0/8'
self.nflowDefaults = {'nflowTarget':'',
'nflowTimeout':'600',
'nflowAddId':'0'}
self.sflowDefaults = {'sflowTarget':'',
'sflowSampling':'400',
'sflowHeader':'128',
'sflowPolling':'30'}
self.appPrefs={
"ipBase": self.defaultIpBase,
"startCLI": "0",
"terminalType": 'xterm',
"switchType": 'ovs',
"dpctl": '',
'sflow':self.sflowDefaults,
'netflow':self.nflowDefaults,
'openFlowVersions':{'ovsOf10':'1',
'ovsOf11':'0',
'ovsOf12':'0',
'ovsOf13':'0'}
# Style
self.font = ( 'Geneva', 9 )
self.smallFont = ( 'Geneva', 7 )
self.bg = 'white'
# Title
self.top = self.winfo_toplevel()
self.top.title( self.appName )
# Menu bar
self.createMenubar()
# Editing canvas
self.cheight, self.cwidth = cheight, cwidth
self.cframe, self.canvas = self.createCanvas()
# Toolbar
self.controllers = {}
# Toolbar
self.images = miniEditImages()
self.buttons = {}
self.active = None
self.tools = ( 'Select', 'Host', 'Switch', 'LegacySwitch', 'LegacyRouter',
'NetLink', 'Controller' )
self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
self.toolbar = self.createToolbar()
# Layout
self.toolbar.grid( column=0, row=0, sticky='nsew')
self.cframe.grid( column=1, row=0 )
self.columnconfigure( 1, weight=1 )
self.rowconfigure( 0, weight=1 )
self.pack( expand=True, fill='both' )
# About box
self.aboutBox = None
# Selection support
self.selection = None
# Keyboard bindings
self.bind( '<Control-q>', lambda event: self.quit() )
self.bind( '<KeyPress-Delete>', self.deleteSelection )
self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
self.focus()
# Model initialization
self.links = {}
self.hostOpts = {}
self.switchOpts = {}
self.hostCount = 0
self.switchCount = 0
self.controllerCount = 0
self.net = None
font = self.font
# Application menu
appMenu = Menu( mbar, tearoff=False )
mbar.add_cascade( label="Help", font=font, menu=appMenu )
appMenu.add_command( label='About MiniEdit', command=self.about,
font=font)
# Canvas
# Scroll bars
xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
# Resize box
resize = Label( f, bg='white' )
# Layout
canvas.grid( row=0, column=1, sticky='nsew')
ybar.grid( row=0, column=2, sticky='ns')
xbar.grid( row=1, column=1, sticky='ew' )
resize.grid( row=1, column=2, sticky='nsew' )
# Resize behavior
f.rowconfigure( 0, weight=1 )
f.columnconfigure( 1, weight=1 )
f.grid( row=0, column=0, sticky='nsew' )
f.bind( '<Configure>', lambda event: self.updateScrollRegion() )
# Mouse bindings
canvas.bind( '<ButtonPress-1>', self.clickCanvas )
canvas.bind( '<B1-Motion>', self.dragCanvas )
canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
return f, canvas
# Toolbar
@staticmethod
def createToolTip(widget, text):
toolTip = ToolTip(widget)
def enter(_event):
toolTip.showtip(text)
def leave(_event):
toolTip.hidetip()
widget.bind('<Enter>', enter)
widget.bind('<Leave>', leave)
# Tools
for tool in self.tools:
cmd = ( lambda t=tool: self.activate( t ) )
b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
if tool in self.images:
b.config( height=35, image=self.images[ tool ] )
self.createToolTip(b, str(tool))
# b.config( compound='top' )
b.pack( fill='x' )
self.buttons[ tool ] = b
self.activate( self.tools[ 0 ] )
# Spacer
Label( toolbar, text='' ).pack()
# Commands
for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]:
doCmd = getattr( self, 'do' + cmd )
b = Button( toolbar, text=cmd, font=self.smallFont,
fg=color, command=doCmd )
b.pack( fill='x', side='bottom' )
return toolbar
myFormats = [
('Mininet Topology','*.mn'),
('All Files','*'),
]
f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
if f == None:
return
self.newTopology()
loadedTopology = self.convertJsonUnicode(json.load(f))
# Load controllers
if 'controllers' in loadedTopology:
if loadedTopology['version'] == '1':
# This is old location of controller info
hostname = 'c0'
self.controllers = {}
self.controllers[hostname] = loadedTopology['controllers']['c0']
self.controllers[hostname]['hostname'] = hostname
self.addNode('Controller', 0, float(30), float(30), name=hostname)
icon = self.findWidgetByName(hostname)
icon.bind('<Button-3>', self.do_controllerPopup )
else:
controllers = loadedTopology['controllers']
for controller in controllers:
hostname = controller['opts']['hostname']
x = controller['x']
y = controller['y']
self.addNode('Controller', 0, float(x), float(y),
name=hostname)
self.controllers[hostname] = controller['opts']
icon = self.findWidgetByName(hostname)
icon.bind('<Button-3>', self.do_controllerPopup )
# Load hosts
hosts = loadedTopology['hosts']
for host in hosts:
nodeNum = host['number']
hostname = 'h'+nodeNum
if 'hostname' in host['opts']:
hostname = host['opts']['hostname']
else:
host['opts']['hostname'] = hostname
if 'nodeNum' not in host['opts']:
host['opts']['nodeNum'] = int(nodeNum)
x = host['x']
y = host['y']
self.addNode('Host', nodeNum, float(x), float(y), name=hostname)
# Load switches
switches = loadedTopology['switches']
for switch in switches:
nodeNum = switch['number']
hostname = 's'+nodeNum
if 'controllers' not in switch['opts']:
switch['opts']['controllers'] = []
if 'switchType' not in switch['opts']:
switch['opts']['switchType'] = 'default'
if 'hostname' in switch['opts']:
hostname = switch['opts']['hostname']
else:
switch['opts']['hostname'] = hostname
if 'nodeNum' not in switch['opts']:
switch['opts']['nodeNum'] = int(nodeNum)
x = switch['x']
y = switch['y']
if switch['opts']['switchType'] == "legacyRouter":
self.addNode('LegacyRouter', nodeNum, float(x), float(y),
name=hostname)
icon = self.findWidgetByName(hostname)
icon.bind('<Button-3>', self.do_legacyRouterPopup )
elif switch['opts']['switchType'] == "legacySwitch":
self.addNode('LegacySwitch', nodeNum, float(x), float(y),
name=hostname)
icon = self.findWidgetByName(hostname)
icon.bind('<Button-3>', self.do_legacySwitchPopup )
else:
self.addNode('Switch', nodeNum, float(x), float(y), name=hostname)
icon = self.findWidgetByName(hostname)
icon.bind('<Button-3>', self.do_switchPopup )
self.switchOpts[hostname] = switch['opts']
# Load links
links = loadedTopology['links']
for link in links:
srcNode = link['src']
src = self.findWidgetByName(srcNode)
sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
destNode = link['dest']
dest = self.findWidgetByName(destNode)
dx, dy = self.canvas.coords( self.widgetToItem[ dest] )
f.close()
def findWidgetByName( self, name ):
for widget in self.widgetToItem:
if name == widget[ 'text' ]:
return widget
savingDictionary = {}
fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save
the topology as...")
if len(fileName ) > 0:
# Save Application preferences
savingDictionary['version'] = '2'
# Save Links
linksToSave = []
for link in self.links.values():
src = link['src']
dst = link['dest']
linkopts = link['linkOpts']
try:
f = open(fileName, 'wb')
f.write(json.dumps(savingDictionary, sort_keys=True, indent=4,
separators=(',', ': ')))
# pylint: disable=broad-except
except Exception as er:
warn( er, '\n' )
# pylint: enable=broad-except
finally:
f.close()
fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats
,title="Export the topology as...")
if len(fileName ) > 0:
# debug( "Now saving under %s\n" % fileName )
f = open(fileName, 'wb')
f.write("#!/usr/bin/python\n")
f.write("\n")
f.write("from mininet.net import Mininet\n")
f.write("from mininet.node import Controller, RemoteController,
OVSController\n")
f.write("from mininet.node import CPULimitedHost, Host, Node\n")
f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n")
if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
f.write("from mininet.node import IVSSwitch\n")
f.write("from mininet.cli import CLI\n")
f.write("from mininet.log import setLogLevel, info\n")
f.write("from mininet.link import TCLink, Intf\n")
f.write("from subprocess import call\n")
inBandCtrl = False
for widget in self.widgetToItem:
name = widget[ 'text' ]
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
if 'Controller' in tags:
opts = self.controllers[name]
controllerType = opts['controllerType']
if controllerType == 'inband':
inBandCtrl = True
if inBandCtrl == True:
f.write("\n")
f.write("class InbandController( RemoteController ):\n")
f.write("\n")
f.write(" def checkListening( self ):\n")
f.write(" \"Overridden to do nothing.\"\n")
f.write(" return\n")
f.write("\n")
f.write("def myNetwork():\n")
f.write("\n")
f.write(" net = Mininet( topo=None,\n")
if len(self.appPrefs['dpctl']) > 0:
f.write(" listenPort="+self.appPrefs['dpctl']
+",\n")
f.write(" build=False,\n")
f.write(" ipBase='"+self.appPrefs['ipBase']+"')\n")
f.write("\n")
f.write(" info( '*** Adding controller\\n' )\n")
for widget in self.widgetToItem:
name = widget[ 'text' ]
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
if 'Controller' in tags:
opts = self.controllers[name]
controllerType = opts['controllerType']
if 'controllerProtocol' in opts:
controllerProtocol = opts['controllerProtocol']
else:
controllerProtocol = 'tcp'
controllerIP = opts['remoteIP']
controllerPort = opts['remotePort']
f.write(" "+name+"=net.addController(name='"+name+"',\n")
if controllerType == 'remote':
f.write("
controller=RemoteController,\n")
f.write(" ip='"+controllerIP+"',\n")
elif controllerType == 'inband':
f.write("
controller=InbandController,\n")
f.write(" ip='"+controllerIP+"',\n")
elif controllerType == 'ovsc':
f.write("
controller=OVSController,\n")
else:
f.write(" controller=Controller,\n")
f.write("
protocol='"+controllerProtocol+"',\n")
f.write(" port="+str(controllerPort)
+")\n")
f.write("\n")
f.write("\n")
f.write(" info( '*** Add hosts\\n')\n")
for widget in self.widgetToItem:
name = widget[ 'text' ]
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
if 'Host' in tags:
opts = self.hostOpts[name]
ip = None
defaultRoute = None
if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
defaultRoute = "'via "+opts['defaultRoute']+"'"
else:
defaultRoute = 'None'
if 'ip' in opts and len(opts['ip']) > 0:
ip = opts['ip']
else:
nodeNum = self.hostOpts[name]['nodeNum']
ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
ip = ipAdd(i=nodeNum, prefixLen=prefixLen,
ipBaseNum=ipBaseNum)
# Save Links
f.write(" info( '*** Add links\\n')\n")
for key,linkDetail in self.links.items():
tags = self.canvas.gettags(key)
if 'data' in tags:
optsExist = False
src = linkDetail['src']
dst = linkDetail['dest']
linkopts = linkDetail['linkOpts']
srcName, dstName = src[ 'text' ], dst[ 'text' ]
bw = ''
# delay = ''
# loss = ''
# max_queue_size = ''
linkOpts = "{"
if 'bw' in linkopts:
bw = linkopts['bw']
linkOpts = linkOpts + "'bw':"+str(bw)
optsExist = True
if 'delay' in linkopts:
# delay = linkopts['delay']
if optsExist:
linkOpts = linkOpts + ","
linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'"
optsExist = True
if 'loss' in linkopts:
if optsExist:
linkOpts = linkOpts + ","
linkOpts = linkOpts + "'loss':"+str(linkopts['loss'])
optsExist = True
if 'max_queue_size' in linkopts:
if optsExist:
linkOpts = linkOpts + ","
linkOpts = linkOpts +
"'max_queue_size':"+str(linkopts['max_queue_size'])
optsExist = True
if 'jitter' in linkopts:
if optsExist:
linkOpts = linkOpts + ","
linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'"
optsExist = True
if 'speedup' in linkopts:
if optsExist:
linkOpts = linkOpts + ","
linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup'])
optsExist = True
f.write("\n")
f.write(" info( '*** Starting network\\n')\n")
f.write(" net.build()\n")
f.write("\n")
# Configure NetFlow
nflowValues = self.appPrefs['netflow']
if len(nflowValues['nflowTarget']) > 0:
nflowEnabled = False
nflowSwitches = ''
for widget in self.widgetToItem:
name = widget[ 'text' ]
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
if 'Switch' in tags:
opts = self.switchOpts[name]
if 'netflow' in opts:
if opts['netflow'] == '1':
nflowSwitches = nflowSwitches+' -- set Bridge
'+name+' netflow=@MiniEditNF'
nflowEnabled=True
if nflowEnabled:
nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+
'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-
timeout='+nflowValues['nflowTimeout']
if nflowValues['nflowAddId'] == '1':
nflowCmd = nflowCmd + ' add_id_to_interface=true'
else:
nflowCmd = nflowCmd + ' add_id_to_interface=false'
f.write(" \n")
f.write(" call('"+nflowCmd+nflowSwitches+"', shell=True)\n")
# Configure sFlow
sflowValues = self.appPrefs['sflow']
if len(sflowValues['sflowTarget']) > 0:
sflowEnabled = False
sflowSwitches = ''
for widget in self.widgetToItem:
name = widget[ 'text' ]
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
if 'Switch' in tags:
opts = self.switchOpts[name]
if 'sflow' in opts:
if opts['sflow'] == '1':
sflowSwitches = sflowSwitches+' -- set Bridge
'+name+' sflow=@MiniEditSF'
sflowEnabled=True
if sflowEnabled:
sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+
'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+
'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']
+' '+ 'polling='+sflowValues['sflowPolling']
f.write(" \n")
f.write(" call('"+sflowCmd+sflowSwitches+"', shell=True)\n")
f.write("\n")
f.write(" CLI(net)\n")
for widget in self.widgetToItem:
name = widget[ 'text' ]
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
if 'Host' in tags:
opts = self.hostOpts[name]
# Run User Defined Stop Command
if 'stopCommand' in opts:
f.write(" "+name+".cmdPrint('"+opts['stopCommand']
+"')\n")
if 'Switch' in tags:
opts = self.switchOpts[name]
# Run User Defined Stop Command
if 'stopCommand' in opts:
f.write(" "+name+".cmdPrint('"+opts['stopCommand']
+"')\n")
f.write(" net.stop()\n")
f.write("\n")
f.write("if __name__ == '__main__':\n")
f.write(" setLogLevel( 'info' )\n")
f.write(" myNetwork()\n")
f.write("\n")
f.close()
w = event.widget
item = self.widgetToItem[ w ]
x, y = self.canvas.coords( item )
self.link = self.canvas.create_line( x, y, x, y, width=4,
fill='blue', tag='link' )
self.linkx, self.linky = x, y
self.linkWidget = w
self.linkItem = item
x, y = c.coords( target )
c.coords( self.link, self.linkx, self.linky, x, y )
self.addLink( source, dest, linktype=linkType )
if linkType == 'control':
controllerName = ''
switchName = ''
if 'Controller' in stags:
controllerName = source[ 'text' ]
switchName = dest[ 'text' ]
else:
controllerName = dest[ 'text' ]
switchName = source[ 'text' ]
self.switchOpts[switchName]['controllers'].append(controllerName)
# We're done
self.link = self.linkWidget = None
# Menu handlers
@staticmethod
def checkIntf( intf ):
"Make sure intf exists and is not configured."
if ( ' %s:' % intf ) not in quietRun( 'ip link show' ):
showerror(title="Error",
message='External interface ' +intf + ' does not exist!
Skipping.')
return False
ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) )
if ips:
showerror(title="Error",
message= intf + ' has an IP address and is probably in use!
Skipping.' )
return False
return True
prefDefaults = self.hostOpts[name]
hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults)
self.master.wait_window(hostBox.top)
if hostBox.result:
newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']}
newHostOpts['sched'] = hostBox.result['sched']
if len(hostBox.result['startCommand']) > 0:
newHostOpts['startCommand'] = hostBox.result['startCommand']
if len(hostBox.result['stopCommand']) > 0:
newHostOpts['stopCommand'] = hostBox.result['stopCommand']
if len(hostBox.result['cpu']) > 0:
newHostOpts['cpu'] = float(hostBox.result['cpu'])
if len(hostBox.result['cores']) > 0:
newHostOpts['cores'] = hostBox.result['cores']
if len(hostBox.result['hostname']) > 0:
newHostOpts['hostname'] = hostBox.result['hostname']
name = hostBox.result['hostname']
widget[ 'text' ] = name
if len(hostBox.result['defaultRoute']) > 0:
newHostOpts['defaultRoute'] = hostBox.result['defaultRoute']
if len(hostBox.result['ip']) > 0:
newHostOpts['ip'] = hostBox.result['ip']
if len(hostBox.result['externalInterfaces']) > 0:
newHostOpts['externalInterfaces'] =
hostBox.result['externalInterfaces']
if len(hostBox.result['vlanInterfaces']) > 0:
newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces']
if len(hostBox.result['privateDirectory']) > 0:
newHostOpts['privateDirectory'] =
hostBox.result['privateDirectory']
self.hostOpts[name] = newHostOpts
info( 'New host details for ' + name + ' = ' + str(newHostOpts), '\n' )
prefDefaults = self.switchOpts[name]
switchBox = SwitchDialog(self, title='Switch Details',
prefDefaults=prefDefaults)
self.master.wait_window(switchBox.top)
if switchBox.result:
newSwitchOpts = {'nodeNum':self.switchOpts[name]['nodeNum']}
newSwitchOpts['switchType'] = switchBox.result['switchType']
newSwitchOpts['controllers'] = self.switchOpts[name]['controllers']
if len(switchBox.result['startCommand']) > 0:
newSwitchOpts['startCommand'] = switchBox.result['startCommand']
if len(switchBox.result['stopCommand']) > 0:
newSwitchOpts['stopCommand'] = switchBox.result['stopCommand']
if len(switchBox.result['dpctl']) > 0:
newSwitchOpts['dpctl'] = switchBox.result['dpctl']
if len(switchBox.result['dpid']) > 0:
newSwitchOpts['dpid'] = switchBox.result['dpid']
if len(switchBox.result['hostname']) > 0:
newSwitchOpts['hostname'] = switchBox.result['hostname']
name = switchBox.result['hostname']
widget[ 'text' ] = name
if len(switchBox.result['externalInterfaces']) > 0:
newSwitchOpts['externalInterfaces'] =
switchBox.result['externalInterfaces']
newSwitchOpts['switchIP'] = switchBox.result['switchIP']
newSwitchOpts['sflow'] = switchBox.result['sflow']
newSwitchOpts['netflow'] = switchBox.result['netflow']
self.switchOpts[name] = newSwitchOpts
info( 'New switch details for ' + name + ' = ' + str(newSwitchOpts),
'\n' )
linkDetail = self.links[link]
# src = linkDetail['src']
# dest = linkDetail['dest']
linkopts = linkDetail['linkOpts']
linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
if linkBox.result is not None:
linkDetail['linkOpts'] = linkBox.result
info( 'New link details = ' + str(linkBox.result), '\n' )
@staticmethod
def ovsShow( _ignore=None ):
call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read
-p \"Press Enter to close\"' &"], shell=True)
@staticmethod
def rootTerminal( _ignore=None ):
call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True)
# Model interface
#
# Ultimately we will either want to use a topo or
# mininet object here, probably.
if 'control' in ltags:
controllerName = ''
switchName = ''
if 'Controller' in stags:
controllerName = source[ 'text' ]
switchName = dest[ 'text' ]
else:
controllerName = dest[ 'text' ]
switchName = source[ 'text' ]
if controllerName in self.switchOpts[switchName]['controllers']:
self.switchOpts[switchName]
['controllers'].remove(controllerName)
if 'Switch' in tags:
opts = self.switchOpts[name]
# debug( str(opts), '\n' )
# Create the correct switch class
switchClass = customOvs
switchParms={}
if 'dpctl' in opts:
switchParms['listenPort']=int(opts['dpctl'])
if 'dpid' in opts:
switchParms['dpid']=opts['dpid']
if opts['switchType'] == 'default':
if self.appPrefs['switchType'] == 'ivs':
switchClass = IVSSwitch
elif self.appPrefs['switchType'] == 'user':
switchClass = CustomUserSwitch
elif self.appPrefs['switchType'] == 'userns':
switchParms['inNamespace'] = True
switchClass = CustomUserSwitch
else:
switchClass = customOvs
elif opts['switchType'] == 'user':
switchClass = CustomUserSwitch
elif opts['switchType'] == 'userns':
switchClass = CustomUserSwitch
switchParms['inNamespace'] = True
elif opts['switchType'] == 'ivs':
switchClass = IVSSwitch
else:
switchClass = customOvs
if switchClass == customOvs:
# Set OpenFlow versions
self.openFlowVersions = []
if self.appPrefs['openFlowVersions']['ovsOf10'] == '1':
self.openFlowVersions.append('OpenFlow10')
if self.appPrefs['openFlowVersions']['ovsOf11'] == '1':
self.openFlowVersions.append('OpenFlow11')
if self.appPrefs['openFlowVersions']['ovsOf12'] == '1':
self.openFlowVersions.append('OpenFlow12')
if self.appPrefs['openFlowVersions']['ovsOf13'] == '1':
self.openFlowVersions.append('OpenFlow13')
protoList = ",".join(self.openFlowVersions)
switchParms['protocols'] = protoList
newSwitch = net.addSwitch( name , cls=switchClass, **switchParms)
# Make controller
info( 'Getting controller selection:'+controllerType, '\n' )
if controllerType == 'remote':
net.addController(name=name,
controller=RemoteController,
ip=controllerIP,
protocol=controllerProtocol,
port=controllerPort)
elif controllerType == 'inband':
net.addController(name=name,
controller=InbandController,
ip=controllerIP,
protocol=controllerProtocol,
port=controllerPort)
elif controllerType == 'ovsc':
net.addController(name=name,
controller=OVSController,
protocol=controllerProtocol,
port=controllerPort)
else:
net.addController(name=name,
controller=Controller,
protocol=controllerProtocol,
port=controllerPort)
else:
raise Exception( "Cannot create mystery node: " + name )
@staticmethod
def pathCheck( *args, **kwargs ):
"Make sure each program in *args can be found in $PATH."
moduleName = kwargs.get( 'moduleName', 'it' )
for arg in args:
if not quietRun( 'which ' + arg ):
showerror(title="Error",
message= 'Cannot find required executable %s.\n' % arg +
'Please make sure that %s is installed ' % moduleName +
'and available in your $PATH.' )
dpctl = None
if len(self.appPrefs['dpctl']) > 0:
dpctl = int(self.appPrefs['dpctl'])
net = Mininet( topo=None,
listenPort=dpctl,
build=False,
ipBase=self.appPrefs['ipBase'] )
self.buildNodes(net)
self.buildLinks(net)
return net
# Configure NetFlow
nflowValues = self.appPrefs['netflow']
if len(nflowValues['nflowTarget']) > 0:
nflowEnabled = False
nflowSwitches = ''
for widget in self.widgetToItem:
name = widget[ 'text' ]
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
if 'Switch' in tags:
opts = self.switchOpts[name]
if 'netflow' in opts:
if opts['netflow'] == '1':
info( name+' has Netflow enabled\n' )
nflowSwitches = nflowSwitches+' -- set Bridge '+name+'
netflow=@MiniEditNF'
nflowEnabled=True
if nflowEnabled:
nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+
'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-
timeout='+nflowValues['nflowTimeout']
if nflowValues['nflowAddId'] == '1':
nflowCmd = nflowCmd + ' add_id_to_interface=true'
else:
nflowCmd = nflowCmd + ' add_id_to_interface=false'
info( 'cmd = '+nflowCmd+nflowSwitches, '\n' )
call(nflowCmd+nflowSwitches, shell=True)
else:
info( 'No switches with Netflow\n' )
else:
info( 'No NetFlow targets specified.\n' )
# Configure sFlow
sflowValues = self.appPrefs['sflow']
if len(sflowValues['sflowTarget']) > 0:
sflowEnabled = False
sflowSwitches = ''
for widget in self.widgetToItem:
name = widget[ 'text' ]
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
if 'Switch' in tags:
opts = self.switchOpts[name]
if 'sflow' in opts:
if opts['sflow'] == '1':
info( name+' has sflow enabled\n' )
sflowSwitches = sflowSwitches+' -- set Bridge '+name+'
sflow=@MiniEditSF'
sflowEnabled=True
if sflowEnabled:
sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+
'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+
'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']
+' '+ 'polling='+sflowValues['sflowPolling']
info( 'cmd = '+sflowCmd+sflowSwitches, '\n' )
call(sflowCmd+sflowSwitches, shell=True)
else:
info( 'No switches with sflow\n' )
else:
info( 'No sFlow targets specified.\n' )
self.postStartSetup()
self.net.stop()
cleanUpScreens()
self.net = None
if '--custom' in sys.argv:
index = sys.argv.index( '--custom' )
if len( sys.argv ) > index + 1:
filename = sys.argv[ index + 1 ]
self.parseCustomFile( filename )
else:
raise Exception( 'Custom file name not found' )
c = self.canvas
rowIncrement = 100
currentY = 100
# Add Controllers
info( 'controllers:'+str(len(importNet.controllers)), '\n' )
for controller in importNet.controllers:
name = controller.name
x = self.controllerCount*100+100
self.addNode('Controller', self.controllerCount,
float(x), float(currentY), name=name)
icon = self.findWidgetByName(name)
icon.bind('<Button-3>', self.do_controllerPopup )
ctrlr = { 'controllerType': 'ref',
'hostname': name,
'controllerProtocol': controller.protocol,
'remoteIP': controller.ip,
'remotePort': controller.port}
self.controllers[name] = ctrlr
# Add switches
info( 'switches:'+str(len(importNet.switches)), '\n' )
columnCount = 0
for switch in importNet.switches:
name = switch.name
self.switchOpts[name] = {}
self.switchOpts[name]['nodeNum']=self.switchCount
self.switchOpts[name]['hostname']=name
self.switchOpts[name]['switchType']='default'
self.switchOpts[name]['controllers']=[]
x = columnCount*100+100
self.addNode('Switch', self.switchCount,
float(x), float(currentY), name=name)
icon = self.findWidgetByName(name)
icon.bind('<Button-3>', self.do_switchPopup )
# Now link to controllers
for controller in importNet.controllers:
self.switchOpts[name]['controllers'].append(controller.name)
dest = self.findWidgetByName(controller.name)
dx, dy = c.coords( self.widgetToItem[ dest ] )
self.link = c.create_line(float(x),
float(currentY),
dx,
dy,
width=4,
fill='red',
dash=(6, 4, 2, 4),
tag='link' )
c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
self.addLink( icon, dest, linktype='control' )
self.createControlLinkBindings()
self.link = self.linkWidget = None
if columnCount == 9:
columnCount = 0
currentY = currentY + rowIncrement
else:
columnCount =columnCount+1
x = columnCount*100+100
self.addNode('Host', self.hostCount,
float(x), float(currentY), name=name)
icon = self.findWidgetByName(name)
icon.bind('<Button-3>', self.do_hostPopup )
if columnCount == 9:
columnCount = 0
currentY = currentY + rowIncrement
else:
columnCount =columnCount+1
destNode = link[1]
dest = self.findWidgetByName(destNode)
dx, dy = self.canvas.coords( self.widgetToItem[ dest] )
importNet.stop()
def miniEditImages():
"Create and return images for MiniEdit."
return {
'Select': BitmapImage(
file='/usr/include/X11/bitmaps/left_ptr' ),
R0lGODlhMAAwAPcAAAEBAWfNAYWFhcfHx+3t6/f390lJUaWlpfPz8/Hx72lpaZGRke/v77m5uc0B
AeHh4e/v7WNjY3t7e5eXlyMjI4mJidPT0+3t7f///09PT7Ozs/X19fHx8ZWTk8HBwX9/fwAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAwADAA
Bwj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAEIeMCxo8ePHwVkBGABg8mTKFOmtDByAIYN
MGPCRCCzQIENNzEMGOkBAwIKQIMKpYCgKAIHCDB4GNkAA4OnUJ9+
+CDhQ1QGFzA0GKkBA4GvYMOK
BYtBA1cNaNOqXcuWq8q3b81m7Cqzbk2bMMu6/Tl0qFEEAZLKxdj1KlSqVA3rnet1rOOwiwmznUzZ
LdzLJgdfpIv3pmebN2Pm1GyRbocNp1PLNMDaAM3Im1/alQk4gO28pCt2RdCBt+/eRg8IP1AUdmmf
f5MrL56bYlcOvaP7Xo6Ag3HdGDho3869u/YE1507t+3AgLz58ujPMwg/sTBUCAzgy49PH0LW5u0x
XFiwvz////5dcJ9bjxVIAHsSdUXAAgs2yOCDDn6FYEQaFGDgYxNCpEFfHHKIX4IDhCjiiCSS+CGF
FlCmogYpcnVABTDGKGOMAlRQYwUHnKjhAjX2aOOPN8LImgAL6PiQBhLMqCSNAThQgQRGOqRBBD1W
aaOVAggnQARRNqRBBxmEKeaYZIrZQZcMKbDiigqM5OabcMYp55x01ilnQAA7
"""),
if __name__ == '__main__':
setLogLevel( 'info' )
app = MiniEdit()
### import topology if specified ###
app.parseArgs()
app.importTopo()
app.mainloop()