From 9aefda7c1a51e71ec06c3c4152f10fbecabfa657 Mon Sep 17 00:00:00 2001
From: Gregory Gee <gee.developer@gmail.com>
Date: Thu, 2 Jan 2014 21:40:46 -0500
Subject: [PATCH] Uploading MiniEdit 2.1.0.7.1

Many changes since master version.
- save/load topologies
- host, controller, switch and link properties
- link actions to set status up/down
---
 examples/miniedit.py | 2416 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 2342 insertions(+), 74 deletions(-)

diff --git a/examples/miniedit.py b/examples/miniedit.py
index 2883c1ce..8b348be9 100755
--- a/examples/miniedit.py
+++ b/examples/miniedit.py
@@ -6,30 +6,917 @@
 This is a simple demonstration of how one might build a
 GUI application using Mininet as the network model.
 
-Development version - not entirely functional!
-
 Bob Lantz, April 2010
+Gregory Gee, July 2013
+
+Controller icon from http://semlabs.co.uk/
 """
 
-from Tkinter import Frame, Button, Label, Scrollbar, Canvas
-from Tkinter import Menu, BitmapImage, PhotoImage, Wm, Toplevel
+MINIEDIT_VERSION = '2.1.0.7.1'
+
+from optparse import OptionParser
+from Tkinter import *
+from tkMessageBox import showinfo, showerror, showwarning
+from subprocess import call
+import tkFont
+import csv
+import tkFileDialog
+import tkSimpleDialog
+import re
+import json
+from distutils.version import StrictVersion
+import os
+import sys
+
+if 'PYTHONPATH' in os.environ:
+    sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
 
 # someday: from ttk import *
 
-from mininet.log import setLogLevel
-from mininet.net import Mininet
-from mininet.util import ipStr
+from mininet.log import info, error, debug, output, setLogLevel
+from mininet.net import Mininet, VERSION
+from mininet.util import ipStr, netParse, ipAdd, quietRun
+from mininet.util import buildTopo
 from mininet.term import makeTerm, cleanUpScreens
+from mininet.node import Controller, RemoteController, NOX, OVSController
+from mininet.node import CPULimitedHost, Host
+from mininet.node import OVSKernelSwitch, OVSSwitch
+from mininet.link import TCLink, Intf
+from mininet.cli import CLI
+from mininet.moduledeps import moduleDeps, pathCheck
+from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
+from mininet.topolib import TreeTopo
+
+print 'MiniEdit running against MiniNet '+VERSION
+if StrictVersion(VERSION) > StrictVersion('2.0'):
+    from mininet.node import IVSSwitch
+
+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 }
+
+class InbandController( RemoteController ):
+
+    def checkListening( self ):
+        "Overridden to do nothing."
+        return
+
+class customOvs(OVSSwitch):
+
+    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
+        OVSSwitch.__init__( self, name, **params )
+        self.switchIP = None
+
+    def getSwitchIP(self):
+        return self.switchIP
+
+    def setSwitchIP(self, ip):
+        self.switchIP = ip
+
+    def getOpenFlowVersion(self):
+        return self.openFlowVersions
+
+    def setOpenFlowVersion(self, versions):
+        self.openFlowVersions = []
+        if versions['ovsOf10'] == '1':
+            self.openFlowVersions.append('OpenFlow10')
+        if versions['ovsOf11'] == '1':
+            self.openFlowVersions.append('OpenFlow11')
+        if versions['ovsOf12'] == '1':
+            self.openFlowVersions.append('OpenFlow12')
+        if versions['ovsOf13'] == '1':
+            self.openFlowVersions.append('OpenFlow13')
+
+    def configureOpenFlowVersion(self):
+        if not ( 'OpenFlow11' in self.openFlowVersions or
+                 'OpenFlow12' in self.openFlowVersions or
+                 'OpenFlow13' in self.openFlowVersions ):
+            return
+
+        protoList = ",".join(self.openFlowVersions)
+        print 'Configuring OpenFlow to '+protoList
+        self.cmd( 'ovs-vsctl -- set bridge', self, 'protocols='+protoList)
+
+    def start( self, controllers ):
+        # Call superclass constructor
+        OVSSwitch.start( self, controllers )
+        # Set OpenFlow Versions
+        self.configureOpenFlowVersion()
+        # Set Switch IP address
+        if (self.switchIP is not None):
+            self.cmd( 'ifconfig', self, self.switchIP )
+
+class PrefsDialog(tkSimpleDialog.Dialog):
+
+        def __init__(self, parent, title, prefDefaults):
+
+            self.prefValues = prefDefaults
+
+            tkSimpleDialog.Dialog.__init__(self, parent, title)
+
+        def body(self, master):
+            self.rootFrame = master
+            self.leftfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
+            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2)
+            self.rightfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
+            self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2)
+
+
+            # Field for Base IP
+            Label(self.leftfieldFrame, text="IP Base:").grid(row=0, sticky=E)
+            self.ipEntry = Entry(self.leftfieldFrame)
+            self.ipEntry.grid(row=0, column=1)
+            ipBase =  self.prefValues['ipBase']
+            self.ipEntry.insert(0, ipBase)
+
+            # Selection of terminal type
+            Label(self.leftfieldFrame, text="Default Terminal:").grid(row=1, sticky=E)
+            self.terminalVar = StringVar(self.leftfieldFrame)
+            self.terminalOption = OptionMenu(self.leftfieldFrame, self.terminalVar, "xterm", "gterm")
+            self.terminalOption.grid(row=1, column=1, sticky=W)
+            terminalType = self.prefValues['terminalType']
+            self.terminalVar.set(terminalType)
+
+            # Field for CLI
+            Label(self.leftfieldFrame, text="Start CLI:").grid(row=2, sticky=E)
+            self.cliStart = IntVar()
+            self.cliButton = Checkbutton(self.leftfieldFrame, variable=self.cliStart)
+            self.cliButton.grid(row=2, column=1, sticky=W)
+            if self.prefValues['startCLI'] == '0':
+                self.cliButton.deselect()
+            else:
+                self.cliButton.select()
+
+            # Selection of switch type
+            Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E)
+            self.switchType = StringVar(self.leftfieldFrame)
+            self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch", "Indigo Virtual Switch")
+            self.switchTypeMenu.grid(row=3, column=1, sticky=W)
+            switchTypePref = self.prefValues['switchType']
+            if switchTypePref == 'ivs':
+                self.switchType.set("Indigo Virtual Switch")
+            else:
+                self.switchType.set("Open vSwitch")
+
+
+            # Fields for OVS OpenFlow version
+            ovsFrame= LabelFrame(self.leftfieldFrame, text='Open vSwitch', padx=5, pady=5)
+            ovsFrame.grid(row=4, column=0, columnspan=2, sticky=EW)
+            Label(ovsFrame, text="OpenFlow 1.0:").grid(row=0, sticky=E)
+            Label(ovsFrame, text="OpenFlow 1.1:").grid(row=1, sticky=E)
+            Label(ovsFrame, text="OpenFlow 1.2:").grid(row=2, sticky=E)
+            Label(ovsFrame, text="OpenFlow 1.3:").grid(row=3, sticky=E)
+
+            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()
+
+            # Field for DPCTL listen port
+            Label(self.leftfieldFrame, text="dpctl port:").grid(row=5, sticky=E)
+            self.dpctlEntry = Entry(self.leftfieldFrame)
+            self.dpctlEntry.grid(row=5, column=1)
+            if 'dpctl' in self.prefValues:
+                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
+
+            # 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)
+
+            Label(self.sflowFrame, text="Target:").grid(row=0, sticky=E)
+            self.sflowTarget = Entry(self.sflowFrame)
+            self.sflowTarget.grid(row=0, column=1)
+            self.sflowTarget.insert(0, sflowValues['sflowTarget'])
+
+            Label(self.sflowFrame, text="Sampling:").grid(row=1, sticky=E)
+            self.sflowSampling = Entry(self.sflowFrame)
+            self.sflowSampling.grid(row=1, column=1)
+            self.sflowSampling.insert(0, sflowValues['sflowSampling'])
+
+            Label(self.sflowFrame, text="Header:").grid(row=2, sticky=E)
+            self.sflowHeader = Entry(self.sflowFrame)
+            self.sflowHeader.grid(row=2, column=1)
+            self.sflowHeader.insert(0, sflowValues['sflowHeader'])
+
+            Label(self.sflowFrame, text="Polling:").grid(row=3, sticky=E)
+            self.sflowPolling = Entry(self.sflowFrame)
+            self.sflowPolling.grid(row=3, column=1)
+            self.sflowPolling.insert(0, sflowValues['sflowPolling'])
+
+            # 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)
+
+            Label(self.nFrame, text="Target:").grid(row=0, sticky=E)
+            self.nflowTarget = Entry(self.nFrame)
+            self.nflowTarget.grid(row=0, column=1)
+            self.nflowTarget.insert(0, nflowValues['nflowTarget'])
+
+            Label(self.nFrame, text="Active Timeout:").grid(row=1, sticky=E)
+            self.nflowTimeout = Entry(self.nFrame)
+            self.nflowTimeout.grid(row=1, column=1)
+            self.nflowTimeout.insert(0, nflowValues['nflowTimeout'])
+
+            Label(self.nFrame, text="Add ID to Interface:").grid(row=2, sticky=E)
+            self.nflowAddId = IntVar()
+            self.nflowAddIdButton = Checkbutton(self.nFrame, variable=self.nflowAddId)
+            self.nflowAddIdButton.grid(row=2, column=1, sticky=W)
+            if nflowValues['nflowAddId'] == '0':
+                self.nflowAddIdButton.deselect()
+            else:
+                self.nflowAddIdButton.select()
+
+            # 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(VERSION) < StrictVersion('2.1'):
+                    self.ovsOk = False
+                    showerror(title="Error",
+                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
+            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
+
+        def getOvsVersion(self):
+            outp = quietRun("ovs-vsctl show")
+            r = r'ovs_version: "(.*)"'
+            m = re.search(r, outp)
+            if m is None:
+                print 'Version check failed'
+                return None
+            else:
+                print 'Open vSwitch version is '+m.group(1)
+                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)
+
+            #return self.b # initial focus
+            buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey')
+            buttonFrame.grid(row=1 , column=0, sticky='nswe')
+
+            okButton = Button(buttonFrame, width=8, text='OK', relief='groove',
+                       bd=4, command=self.okAction)
+            okButton.grid(row=0, column=0, sticky=E)
+
+            canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove',
+                        bd=4, command=self.cancelAction)
+            canlceButton.grid(row=0, column=1, sticky=W)
+
+        def body(self, master):
+            self.rootFrame = master
+
+        def apply(self):
+            self.top.destroy()
+
+        def cancelAction(self):
+            self.top.destroy()
+
+        def okAction(self):
+            self.apply()
+            self.top.destroy()
+
+class HostDialog(CustomDialog):
+
+        def __init__(self, master, title, prefDefaults):
+
+            self.prefValues = prefDefaults
+            self.result = None
+
+            CustomDialog.__init__(self, master, title)
+
+        def body(self, master):
+            self.rootFrame = master
+            self.leftfieldFrame = Frame(self.rootFrame)
+            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2)
+            self.rightfieldFrame = Frame(self.rootFrame)
+            self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2)
+
+            # Field for Hostname
+            Label(self.leftfieldFrame, text="Hostname:").grid(row=0, sticky=E)
+            self.hostnameEntry = Entry(self.leftfieldFrame)
+            self.hostnameEntry.grid(row=0, column=1)
+            if 'hostname' in self.prefValues:
+                self.hostnameEntry.insert(0, self.prefValues['hostname'])
+
+            # Field for Switch IP
+            Label(self.leftfieldFrame, text="IP Address:").grid(row=1, sticky=E)
+            self.ipEntry = Entry(self.leftfieldFrame)
+            self.ipEntry.grid(row=1, column=1)
+            if 'ip' in self.prefValues:
+                self.ipEntry.insert(0, self.prefValues['ip'])
+
+            # Field for default route
+            Label(self.leftfieldFrame, text="Default Route:").grid(row=2, sticky=E)
+            self.routeEntry = Entry(self.leftfieldFrame)
+            self.routeEntry.grid(row=2, column=1)
+            if 'defaultRoute' in self.prefValues:
+                self.routeEntry.insert(0, self.prefValues['defaultRoute'])
+
+            # Field for CPU
+            Label(self.rightfieldFrame, text="Amount CPU:").grid(row=0, sticky=E)
+            self.cpuEntry = Entry(self.rightfieldFrame)
+            self.cpuEntry.grid(row=0, column=1)
+            if 'cpu' in self.prefValues:
+                self.cpuEntry.insert(0, str(self.prefValues['cpu']))
+            # Selection of Scheduler
+            if 'sched' in self.prefValues:
+                sched =  self.prefValues['sched']
+            else:
+                sched = 'host'
+            self.schedVar = StringVar(self.rightfieldFrame)
+            self.schedOption = OptionMenu(self.rightfieldFrame, self.schedVar, "host", "cfs", "rt")
+            self.schedOption.grid(row=0, column=2, sticky=W)
+            self.schedVar.set(sched)
+
+            # Selection of Cores
+            Label(self.rightfieldFrame, text="Cores:").grid(row=1, sticky=E)
+            self.coreEntry = Entry(self.rightfieldFrame)
+            self.coreEntry.grid(row=1, column=1)
+            if 'cores' in self.prefValues:
+                self.coreEntry.insert(1, self.prefValues['cores'])
+
+            # External Interfaces
+            self.externalInterfaces = 0
+            Label(self.rootFrame, text="External Interface:").grid(row=1, column=0, sticky=E)
+            self.b = Button( self.rootFrame, text='Add', command=self.addInterface)
+            self.b.grid(row=1, column=1)
+
+            self.interfaceFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=1, title='External Interfaces')
+            self.interfaceFrame.grid(row=2, column=0, sticky='nswe', columnspan=2)
+            self.tableFrame = self.interfaceFrame.interior
+            self.tableFrame.addRow(value=['Interface Name'], readonly=True)
+
+            # Add defined interfaces
+            externalInterfaces = []
+            if 'externalInterfaces' in self.prefValues:
+                externalInterfaces = self.prefValues['externalInterfaces']
+
+            for externalInterface in externalInterfaces:
+                self.tableFrame.addRow(value=[externalInterface])
+
+            # VLAN Interfaces
+            self.vlanInterfaces = 0
+            Label(self.rootFrame, text="VLAN Interface:").grid(row=1, column=2, sticky=E)
+            self.vlanButton = Button( self.rootFrame, text='Add', command=self.addVlanInterface)
+            self.vlanButton.grid(row=1, column=3)
+
+            self.vlanFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=2, title='VLAN Interfaces')
+            self.vlanFrame.grid(row=2, column=2, sticky='nswe', columnspan=2)
+            self.vlanTableFrame = self.vlanFrame.interior
+            self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True)
+
+            vlanInterfaces = []
+            if 'vlanInterfaces' in self.prefValues:
+                vlanInterfaces = self.prefValues['vlanInterfaces']
+            for vlanInterface in vlanInterfaces:
+                self.vlanTableFrame.addRow(value=vlanInterface)
+
+        def addVlanInterface( self ):
+            self.vlanTableFrame.addRow()
+
+        def addInterface( self ):
+            self.tableFrame.addRow()
+
+        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)])
+
+            results = {'cpu': self.cpuEntry.get(),
+                       'cores':self.coreEntry.get(),
+                       'sched':self.schedVar.get(),
+                       'hostname':self.hostnameEntry.get(),
+                       'ip':self.ipEntry.get(),
+                       'defaultRoute':self.routeEntry.get(),
+                       'externalInterfaces':externalInterfaces,
+                       'vlanInterfaces':vlanInterfaces}
+            self.result = results
+
+class SwitchDialog(CustomDialog):
+
+        def __init__(self, master, title, prefDefaults):
+
+            self.prefValues = prefDefaults
+            self.result = None
+            CustomDialog.__init__(self, master, title)
+
+        def body(self, master):
+            self.rootFrame = master
+
+            rowCount = 0
+            externalInterfaces = []
+            if 'externalInterfaces' in self.prefValues:
+                externalInterfaces = self.prefValues['externalInterfaces']
+
+            self.fieldFrame = Frame(self.rootFrame)
+            self.fieldFrame.grid(row=0, column=0, sticky='nswe')
+
+            # Field for Hostname
+            Label(self.fieldFrame, text="Hostname:").grid(row=rowCount, sticky=E)
+            self.hostnameEntry = Entry(self.fieldFrame)
+            self.hostnameEntry.grid(row=rowCount, column=1)
+            self.hostnameEntry.insert(0, self.prefValues['hostname'])
+            rowCount+=1
+
+            # Field for DPID
+            Label(self.fieldFrame, text="DPID:").grid(row=rowCount, sticky=E)
+            self.dpidEntry = Entry(self.fieldFrame)
+            self.dpidEntry.grid(row=rowCount, column=1)
+            if 'dpid' in self.prefValues:
+                self.dpidEntry.insert(0, self.prefValues['dpid'])
+            rowCount+=1
+
+            # Field for Netflow
+            Label(self.fieldFrame, text="Enable NetFlow:").grid(row=rowCount, sticky=E)
+            self.nflow = IntVar()
+            self.nflowButton = Checkbutton(self.fieldFrame, variable=self.nflow)
+            self.nflowButton.grid(row=rowCount, column=1, sticky=W)
+            if 'netflow' in self.prefValues:
+                if self.prefValues['netflow'] == '0':
+                    self.nflowButton.deselect()
+                else:
+                    self.nflowButton.select()
+            else:
+                self.nflowButton.deselect()
+            rowCount+=1
+
+            # Field for sflow
+            Label(self.fieldFrame, text="Enable sFlow:").grid(row=rowCount, sticky=E)
+            self.sflow = IntVar()
+            self.sflowButton = Checkbutton(self.fieldFrame, variable=self.sflow)
+            self.sflowButton.grid(row=rowCount, column=1, sticky=W)
+            if 'sflow' in self.prefValues:
+                if self.prefValues['sflow'] == '0':
+                    self.sflowButton.deselect()
+                else:
+                    self.sflowButton.select()
+            else:
+                self.sflowButton.deselect()
+            rowCount+=1
+
+            # Selection of switch type
+            Label(self.fieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E)
+            self.switchType = StringVar(self.fieldFrame)
+            self.switchTypeMenu = OptionMenu(self.fieldFrame, self.switchType, "Default", "Open vSwitch", "Indigo Virtual Switch")
+            self.switchTypeMenu.grid(row=rowCount, column=1, sticky=W)
+            if 'switchType' in self.prefValues:
+                switchTypePref = self.prefValues['switchType']
+                if switchTypePref == 'ivs':
+                    self.switchType.set("Indigo Virtual Switch")
+                elif switchTypePref == 'ovs':
+                    self.switchType.set("Open vSwitch")
+                else:
+                    self.switchType.set("Default")
+            else:
+                self.switchType.set("Default")
+            rowCount+=1
+
+            # Field for Switch IP
+            Label(self.fieldFrame, text="IP Address:").grid(row=rowCount, sticky=E)
+            self.ipEntry = Entry(self.fieldFrame)
+            self.ipEntry.grid(row=rowCount, column=1)
+            if 'switchIP' in self.prefValues:
+                self.ipEntry.insert(0, self.prefValues['switchIP'])
+            rowCount+=1
+
+            # Field for DPCTL port
+            Label(self.fieldFrame, text="DPCTL port:").grid(row=rowCount, sticky=E)
+            self.dpctlEntry = Entry(self.fieldFrame)
+            self.dpctlEntry.grid(row=rowCount, column=1)
+            if 'dpctl' in self.prefValues:
+                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
+            rowCount+=1
+
+            # External Interfaces
+            Label(self.fieldFrame, text="External Interface:").grid(row=rowCount, sticky=E)
+            self.b = Button( self.fieldFrame, text='Add', command=self.addInterface)
+            self.b.grid(row=rowCount, column=1)
+
+            self.interfaceFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=1, title='External Interfaces')
+            self.interfaceFrame.grid(row=2, column=0, sticky='nswe')
+            self.tableFrame = self.interfaceFrame.interior
+
+            # Add defined interfaces
+            for externalInterface in externalInterfaces:
+                self.tableFrame.addRow(value=[externalInterface])
+            rowCount+=1
+
+        def addInterface( self ):
+            self.tableFrame.addRow()
+
+        def defaultDpid( self ,name):
+            "Derive dpid from switch name, s1 -> 1"
+            try:
+                dpid = int( re.findall( r'\d+', name )[ 0 ] )
+                dpid = hex( dpid )[ 2: ]
+                return dpid
+            except IndexError:
+                return None
+                #raise Exception( 'Unable to derive default datapath ID - '
+                #                 'please either specify a dpid or use a '
+                #                 'canonical switch name such as s23.' )
+
+        def apply(self):
+            externalInterfaces = []
+            for row in range(self.tableFrame.rows):
+                #print 'Interface is ' + self.tableFrame.get(row, 0)
+                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,
+                       '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(VERSION) < StrictVersion('2.1'):
+                    self.ovsOk = False
+                    showerror(title="Error",
+                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
+            elif sw == 'Open vSwitch':
+                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)            
+
+        # create a canvas object and a vertical scrollbar for scrolling it
+        vscrollbar = Scrollbar(self, orient=VERTICAL)
+        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
+        canvas = Canvas(self, bd=0, highlightthickness=0,
+                        yscrollcommand=vscrollbar.set)
+        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
+        vscrollbar.config(command=canvas.yview)
+
+        # reset the view
+        canvas.xview_moveto(0)
+        canvas.yview_moveto(0)
+
+        # create a frame inside the canvas which will be scrolled with it
+        self.interior = interior = TableFrame(canvas, rows=rows, columns=columns)
+        interior_id = canvas.create_window(0, 0, window=interior,
+                                           anchor=NW)
+
+        # 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):
+
+        Frame.__init__(self, parent, background="black")
+        self._widgets = []
+        self.rows = rows
+        self.columns = columns
+        for row in range(rows):
+            current_row = []
+            for column in range(columns):
+                label = Entry(self, borderwidth=0)
+                label.grid(row=row, column=column, sticky="wens", padx=1, pady=1)
+                current_row.append(label)
+            self._widgets.append(current_row)
+
+    def set(self, row, column, value):
+        widget = self._widgets[row][column]
+        widget.insert(0, value)
+
+    def get(self, row, column):
+        widget = self._widgets[row][column]
+        return widget.get()
+
+    def addRow( self, value=None, readonly=False ):
+        #print "Adding row " + str(self.rows +1)
+        current_row = []
+        for column in range(self.columns):
+            label = Entry(self, borderwidth=0)
+            label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1)
+            if value is not None:
+                label.insert(0, value[column])
+            if (readonly == True):
+                label.configure(state='readonly')
+            current_row.append(label)
+        self._widgets.append(current_row)
+        self.update_idletasks()
+        self.rows += 1
+
+class LinkDialog(tkSimpleDialog.Dialog):
+
+        def __init__(self, parent, title, linkDefaults):
+
+            self.linkValues = linkDefaults
+
+            tkSimpleDialog.Dialog.__init__(self, parent, title)
+
+        def body(self, master):
+
+            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'])
+
+            Label(master, text="Loss:").grid(row=2, sticky=E)
+            self.e3 = Entry(master)
+            self.e3.grid(row=2, column=1)
+            Label(master, text="%").grid(row=2, column=2, sticky=W)
+            if 'loss' in self.linkValues:
+                self.e3.insert(0, str(self.linkValues['loss']))
+
+            Label(master, text="Max Queue size:").grid(row=3, sticky=E)
+            self.e4 = Entry(master)
+            self.e4.grid(row=3, column=1)
+            if 'max_queue_size' in self.linkValues:
+                self.e4.insert(0, str(self.linkValues['max_queue_size']))
+
+            Label(master, text="Jitter:").grid(row=4, sticky=E)
+            self.e5 = Entry(master)
+            self.e5.grid(row=4, column=1)
+            if 'jitter' in self.linkValues:
+                self.e5.insert(0, self.linkValues['jitter'])
+
+            Label(master, text="Speedup:").grid(row=5, sticky=E)
+            self.e6 = Entry(master)
+            self.e6.grid(row=5, column=1)
+            if 'speedup' in self.linkValues:
+                self.e6.insert(0, str(self.linkValues['speedup']))
+
+            return self.e1 # initial focus
+
+        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):
+
+        def __init__(self, parent, title, ctrlrDefaults=None):
+
+            if ctrlrDefaults:
+                self.ctrlrValues = ctrlrDefaults
+
+            tkSimpleDialog.Dialog.__init__(self, parent, title)
+
+        def body(self, master):
+
+            self.var = StringVar(master)
+
+            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
+
+            # Field for Remove Controller Port
+            Label(master, text="Controller Port:").grid(row=rowCount, sticky=E)
+            self.e2 = Entry(master)
+            self.e2.grid(row=rowCount, column=1)
+            self.e2.insert(0, self.ctrlrValues['remotePort'])
+            rowCount+=1
+
+            # Field for Controller Type
+            Label(master, text="Controller Type:").grid(row=rowCount, sticky=E)
+            controllerType = self.ctrlrValues['controllerType']
+            self.o1 = OptionMenu(master, self.var, "Remote Controller", "In-Band Controller", "OpenFlow Reference", "OVS Controller")
+            self.o1.grid(row=rowCount, column=1, sticky=W)
+            if controllerType == 'ref':
+                self.var.set("OpenFlow Reference")
+            elif controllerType == 'inband':
+                self.var.set("In-Band Controller")
+            elif controllerType == 'remote':
+                self.var.set("Remote Controller")
+            else:
+                self.var.set("OVS Controller")
+            rowCount+=1
+
+            # Field for Remove Controller IP
+            remoteFrame= LabelFrame(master, text='Remote/In-Band Controller', padx=5, pady=5)
+            remoteFrame.grid(row=rowCount, column=0, columnspan=2, sticky=W)
+
+            Label(remoteFrame, text="IP Address:").grid(row=0, sticky=E)
+            self.e1 = Entry(remoteFrame)
+            self.e1.grid(row=0, column=1)
+            self.e1.insert(0, self.ctrlrValues['remoteIP'])
+            rowCount+=1
+
+            return self.hostnameEntry # initial focus
+
+        def apply(self):
+            hostname = self.hostnameEntry.get()
+            controllerType = self.var.get()
+            remoteIP = self.e1.get()
+            controllerPort = int(self.e2.get())
+            self.result = { 'hostname': hostname,
+                            'remoteIP': remoteIP,
+                            'remotePort': controllerPort}
+
+            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'
 
 class MiniEdit( Frame ):
 
     "A simple network editor for Mininet."
 
-    def __init__( self, parent=None, cheight=200, cwidth=500 ):
+    def __init__( self, parent=None, cheight=400, cwidth=800 ):
+
+        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'}
+
+        }
+
 
         Frame.__init__( self, parent )
         self.action = None
         self.appName = 'MiniEdit'
+        self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" )
 
         # Style
         self.font = ( 'Geneva', 9 )
@@ -47,11 +934,14 @@ def __init__( self, parent=None, cheight=200, cwidth=500 ):
         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', 'Link' )
+        self.tools = ( 'Select', 'Host', 'Switch', 'Link', 'Controller' )
         self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
         self.toolbar = self.createToolbar()
 
@@ -67,7 +957,7 @@ def __init__( self, parent=None, cheight=200, cwidth=500 ):
 
         # Initialize node data
         self.nodeBindings = self.createNodeBindings()
-        self.nodePrefixes = { 'Switch': 's', 'Host': 'h' }
+        self.nodePrefixes = { 'Switch': 's', 'Host': 'h' , 'Controller': 'c'}
         self.widgetToItem = {}
         self.itemToWidget = {}
 
@@ -83,13 +973,54 @@ def __init__( self, parent=None, cheight=200, cwidth=500 ):
         self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
         self.focus()
 
+        self.hostPopup = Menu(self.top, tearoff=0)
+        self.hostPopup.add_command(label='Host Options', font=self.font)
+        self.hostPopup.add_separator()
+        self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails )
+
+        self.hostRunPopup = Menu(self.top, tearoff=0)
+        self.hostRunPopup.add_command(label='Host Options', font=self.font)
+        self.hostRunPopup.add_separator()
+        self.hostRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
+
+        self.switchPopup = Menu(self.top, tearoff=0)
+        self.switchPopup.add_command(label='Swtich Options', font=self.font)
+        self.switchPopup.add_separator()
+        self.switchPopup.add_command(label='Properties', font=self.font, command=self.switchDetails )
+
+        self.switchRunPopup = Menu(self.top, tearoff=0)
+        self.switchRunPopup.add_command(label='Swtich Options', font=self.font)
+        self.switchRunPopup.add_separator()
+        self.switchRunPopup.add_command(label='List bridge details', font=self.font, command=self.listBridge )
+
+        self.linkPopup = Menu(self.top, tearoff=0)
+        self.linkPopup.add_command(label='Link Options', font=self.font)
+        self.linkPopup.add_separator()
+        self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails )
+
+        self.linkRunPopup = Menu(self.top, tearoff=0)
+        self.linkRunPopup.add_command(label='Link Options', font=self.font)
+        self.linkRunPopup.add_separator()
+        self.linkRunPopup.add_command(label='Link Up', font=self.font, command=self.linkUp )
+        self.linkRunPopup.add_command(label='Link Down', font=self.font, command=self.linkDown )
+
+        self.controllerPopup = Menu(self.top, tearoff=0)
+        self.controllerPopup.add_command(label='Controller Options', font=self.font)
+        self.controllerPopup.add_separator()
+        self.controllerPopup.add_command(label='Properties', font=self.font, command=self.controllerDetails )
+
+
         # Event handling initalization
         self.linkx = self.linky = self.linkItem = None
         self.lastSelection = None
 
         # Model initialization
         self.links = {}
-        self.nodeCount = 0
+        self.hostOpts = {}
+        self.switchOpts = {}
+        self.hostCount = 0
+        self.switchCount = 0
+        self.controllerCount = 0
         self.net = None
 
         # Close window gracefully
@@ -108,34 +1039,35 @@ def createMenubar( self ):
         mbar = Menu( self.top, font=font )
         self.top.configure( menu=mbar )
 
-        # Application menu
-        appMenu = Menu( mbar, tearoff=False )
-        mbar.add_cascade( label=self.appName, font=font, menu=appMenu )
-        appMenu.add_command( label='About MiniEdit', command=self.about,
-                             font=font)
-        appMenu.add_separator()
-        appMenu.add_command( label='Quit', command=self.quit, font=font )
 
-        #fileMenu = Menu( mbar, tearoff=False )
-        #mbar.add_cascade( label="File", font=font, menu=fileMenu )
-        #fileMenu.add_command( label="Load...", font=font )
-        #fileMenu.add_separator()
-        #fileMenu.add_command( label="Save", font=font )
-        #fileMenu.add_separator()
-        #fileMenu.add_command( label="Print", font=font )
+        fileMenu = Menu( mbar, tearoff=False )
+        mbar.add_cascade( label="File", font=font, menu=fileMenu )
+        fileMenu.add_command( label="New", font=font, command=self.newTopology )
+        fileMenu.add_command( label="Open", font=font, command=self.loadTopology )
+        fileMenu.add_command( label="Save", font=font, command=self.saveTopology )
+        fileMenu.add_command( label="Export", font=font, command=self.exportTopology )
+        fileMenu.add_separator()
+        fileMenu.add_command( label='Quit', command=self.quit, font=font )
 
         editMenu = Menu( mbar, tearoff=False )
         mbar.add_cascade( label="Edit", font=font, menu=editMenu )
         editMenu.add_command( label="Cut", font=font,
                               command=lambda: self.deleteSelection( None ) )
+        editMenu.add_command( label="Preferences", font=font, command=self.prefDetails)
 
         runMenu = Menu( mbar, tearoff=False )
         mbar.add_cascade( label="Run", font=font, menu=runMenu )
         runMenu.add_command( label="Run", font=font, command=self.doRun )
         runMenu.add_command( label="Stop", font=font, command=self.doStop )
-        runMenu.add_separator()
-        runMenu.add_command( label='Xterm', font=font, command=self.xterm )
+        fileMenu.add_separator()
+        runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow )
+        runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal )
 
+        # 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
 
     def createCanvas( self ):
@@ -241,6 +1173,515 @@ def doStop( self ):
         for tool in self.tools:
             self.buttons[ tool ].config( state='normal' )
 
+    def addNode( self, node, nodeNum, x, y, name=None):
+        "Add a new node to our canvas."
+        if 'Switch' == node:
+            self.switchCount += 1
+        if 'Host' == node:
+            self.hostCount += 1
+        if 'Controller' == node:
+            self.controllerCount += 1
+        if name is None:
+            name = self.nodePrefixes[ node ] + nodeNum
+        self.addNamedNode(node, name, x, y)
+
+    def addNamedNode( self, node, name, x, y):
+        "Add a new node to our canvas."
+        c = self.canvas
+        icon = self.nodeIcon( node, name )
+        item = self.canvas.create_window( x, y, anchor='c', window=icon,
+                                          tags=node )
+        self.widgetToItem[ icon ] = item
+        self.itemToWidget[ item ] = icon
+        icon.links = {}
+
+    def loadTopology( self ):
+        "Load command."
+        c = self.canvas
+
+        myFormats = [
+            ('Mininet Topology','*.mn'),
+            ('All Files','*'),
+        ]
+        f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
+        if f == None:
+            return
+        self.newTopology()
+        loadedTopology = eval(f.read())
+
+        # Load application preferences
+        if 'application' in loadedTopology:
+            self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items())
+            if "ovsOf10" not in self.appPrefs["openFlowVersions"]:
+                self.appPrefs["openFlowVersions"]["ovsOf10"] = '0'
+            if "ovsOf11" not in self.appPrefs["openFlowVersions"]:
+                self.appPrefs["openFlowVersions"]["ovsOf11"] = '0'
+            if "ovsOf12" not in self.appPrefs["openFlowVersions"]:
+                self.appPrefs["openFlowVersions"]["ovsOf12"] = '0'
+            if "ovsOf13" not in self.appPrefs["openFlowVersions"]:
+                self.appPrefs["openFlowVersions"]["ovsOf13"] = '0'
+            if "sflow" not in self.appPrefs:
+                self.appPrefs["sflow"] = self.sflowDefaults
+            if "netflow" not in self.appPrefs:
+                self.appPrefs["netflow"] = self.nflowDefaults
+
+        # 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)
+            self.hostOpts[hostname] = host['opts']
+            icon = self.findWidgetByName(hostname)
+            icon.bind('<Button-3>', self.do_hostPopup )
+
+        # 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']
+            self.addNode('Switch', nodeNum, float(x), float(y), name=hostname)
+            self.switchOpts[hostname] = switch['opts']
+            icon = self.findWidgetByName(hostname)
+            icon.bind('<Button-3>', self.do_switchPopup )
+
+            # create links to controllers
+            if (int(loadedTopology['version']) > 1):
+                controllers = self.switchOpts[hostname]['controllers']
+                for controller in controllers:
+                    dest = self.findWidgetByName(controller)
+                    dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
+                    self.link = self.canvas.create_line(float(x),
+                                                        float(y),
+                                                        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
+            else:
+                dest = self.findWidgetByName('c0')
+                dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
+                self.link = self.canvas.create_line(float(x),
+                                                    float(y),
+                                                    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
+
+        # 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]  )
+
+            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
+                                             fill='blue', tag='link' )
+            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
+            self.addLink( src, dest, linkopts=link['opts'] )
+            self.createDataLinkBindings()
+            self.link = self.linkWidget = None
+
+        f.close
+
+    def findWidgetByName( self, name ):
+        for widget in self.widgetToItem:
+            if name ==  widget[ 'text' ]:
+                return widget
+
+    def newTopology( self ):
+        "New command."
+        for widget in self.widgetToItem.keys():
+            self.deleteItem( self.widgetToItem[ widget ] )
+        self.hostCount = 0
+        self.switchCount = 0
+        self.controllerCount = 0
+        self.links = {}
+        self.hostOpts = {}
+        self.switchOpts = {}
+        self.controllers = {}
+        self.appPrefs["ipBase"]= self.defaultIpBase
+
+    def saveTopology( self ):
+        "Save command."
+        myFormats = [
+            ('Mininet Topology','*.mn'),
+            ('All Files','*'),
+        ]
+
+        savingDictionary = {}
+        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...")
+        if len(fileName ) > 0:
+            # Save Application preferences
+            savingDictionary['version'] = '2'
+
+            # Save Switches and Hosts
+            hostsToSave = []
+            switchesToSave = []
+            controllersToSave = []
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] )
+                if 'Switch' in tags:
+                    nodeNum = self.switchOpts[name]['nodeNum']
+                    nodeToSave = {'number':str(nodeNum),
+                                  'x':str(x1),
+                                  'y':str(y1),
+                                  'opts':self.switchOpts[name] }
+                    switchesToSave.append(nodeToSave)
+                elif 'Host' in tags:
+                    nodeNum = self.hostOpts[name]['nodeNum']
+                    nodeToSave = {'number':str(nodeNum),
+                                  'x':str(x1),
+                                  'y':str(y1),
+                                  'opts':self.hostOpts[name] }
+                    hostsToSave.append(nodeToSave)
+                elif 'Controller' in tags:
+                    nodeToSave = {'x':str(x1),
+                                  'y':str(y1),
+                                  'opts':self.controllers[name] }
+                    controllersToSave.append(nodeToSave)
+                else:
+                    raise Exception( "Cannot create mystery node: " + name )
+            savingDictionary['hosts'] = hostsToSave
+            savingDictionary['switches'] = switchesToSave
+            savingDictionary['controllers'] = controllersToSave
+
+            # Save Links
+            linksToSave = []
+            for link in self.links.values():
+                src = link['src']
+                dst = link['dest']
+                linkopts = link['linkOpts']
+
+                srcName, dstName = src[ 'text' ], dst[ 'text' ]
+                linkToSave = {'src':srcName,
+                              'dest':dstName,
+                              'opts':linkopts}
+                if link['type'] == 'data':
+                    linksToSave.append(linkToSave)
+            savingDictionary['links'] = linksToSave
+
+            # Save Application preferences
+            savingDictionary['application'] = self.appPrefs
+
+            try:
+                f = open(fileName, 'wb')
+                #f.write(str(savingDictionary))
+                f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': ')))
+            except Exception as er:
+                print er
+            finally:
+                f.close()
+
+    def exportTopology( self ):
+        "Export command."
+        myFormats = [
+            ('Mininet Custom Topology','*.py'),
+            ('All Files','*'),
+        ]
+
+        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...")
+        if len(fileName ) > 0:
+            #print "Now saving under %s" % 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\n")
+            f.write("from mininet.node import OVSKernelSwitch\n")
+            if StrictVersion(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")
+
+            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("                   link=TCLink)\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']
+                    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("                      port="+str(controllerPort)+")\n")
+                    f.write("\n")
+
+            # Save Switches and Hosts
+            f.write("    info( '*** Add switches\\n')\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Switch' in tags:
+                    opts = self.switchOpts[name]
+                    nodeNum = opts['nodeNum']
+                    f.write("    "+name+" = net.addSwitch('"+name+"'")
+                    if opts['switchType'] == 'default':
+                        if self.appPrefs['switchType'] == 'ivs':
+                            f.write(", cls=IVSSwitch")
+                        else:
+                            f.write(", cls=OVSKernelSwitch")
+                    elif opts['switchType'] == 'ivs':
+                        f.write(", cls=IVSSwitch")
+                    else:
+                        f.write(", cls=OVSKernelSwitch")
+                    if 'dpctl' in opts:
+                        f.write(", listenPort="+opts['dpctl'])
+                    if 'dpid' in opts:
+                        f.write(", dpid='"+opts['dpid']+"'")
+                    f.write(")\n")
+                    if ('externalInterfaces' in opts):
+                        for extInterface in opts['externalInterfaces']:
+                            f.write("    Intf( '"+extInterface+"', node="+name+" )\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)
+
+                    if 'cores' in opts or 'cpu' in opts:
+                        f.write("    "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
+                        if 'cores' in opts:
+                            f.write("    "+name+".setCPUs(cores='"+opts['cores']+"')\n")
+                        if 'cpu' in opts:
+                            f.write("    "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n")
+                    else:
+                        f.write("    "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
+                    if ('externalInterfaces' in opts):
+                        for extInterface in opts['externalInterfaces']:
+                            f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
+            f.write("\n")
+
+            # Save Links
+            f.write("    info( '*** Add links\\n')\n")
+            for key,linkDetail in self.links.iteritems():
+              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
+
+                linkOpts = linkOpts + "}"
+                if optsExist:
+                    f.write("    "+srcName+dstName+" = "+linkOpts+"\n")
+                f.write("    net.addLink("+srcName+", "+dstName)
+                if optsExist:
+                    f.write(", **"+srcName+dstName)
+                f.write(")\n")
+
+            f.write("\n")
+            f.write("    info( '*** Starting network\\n')\n")
+            f.write("    net.build()\n")
+
+            f.write("    info( '*** Starting controllers\\n')\n")
+            f.write("    for controller in net.controllers:\n")
+            f.write("        controller.start()\n")
+            f.write("\n")
+
+            f.write("    info( '*** Starting switches\\n')\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Switch' in tags:
+                    opts = self.switchOpts[name]
+                    ctrlList = ",".join(opts['controllers'])
+                    f.write("    net.get('"+name+"').start(["+ctrlList+"])\n")
+
+            f.write("\n")
+
+            f.write("    info( '*** Configuring switches\\n')\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Switch' in tags:
+                    opts = self.switchOpts[name]
+                    if ('switchIP' in opts):
+                        if (len(opts['switchIP'])>0):
+                            f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Host' in tags:
+                    opts = self.hostOpts[name]
+                    # Attach vlan interfaces
+                    if ('vlanInterfaces' in opts):
+                        for vlanInterface in opts['vlanInterfaces']:
+                            f.write("    "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n")
+                            f.write("    "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n")
+
+
+            f.write("\n")
+            f.write("    CLI(net)\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()
+
+
     # Generic canvas handler
     #
     # We could have used bindtags, as in nodeIcon, but
@@ -319,8 +1760,31 @@ def newNode( self, node, event ):
         "Add a new node to our canvas."
         c = self.canvas
         x, y = c.canvasx( event.x ), c.canvasy( event.y )
-        self.nodeCount += 1
-        name = self.nodePrefixes[ node ] + str( self.nodeCount )
+        name = self.nodePrefixes[ node ]
+        if 'Switch' == node:
+            self.switchCount += 1
+            name = self.nodePrefixes[ node ] + str( self.switchCount )
+            self.switchOpts[name] = {}
+            self.switchOpts[name]['nodeNum']=self.switchCount
+            self.switchOpts[name]['hostname']=name
+            self.switchOpts[name]['switchType']='default'
+            self.switchOpts[name]['controllers']=[]
+        if 'Host' == node:
+            self.hostCount += 1
+            name = self.nodePrefixes[ node ] + str( self.hostCount )
+            self.hostOpts[name] = {'sched':'host'}
+            self.hostOpts[name]['nodeNum']=self.hostCount
+            self.hostOpts[name]['hostname']=name
+        if 'Controller' == node:
+            name = self.nodePrefixes[ node ] + str( self.controllerCount )
+            ctrlr = { 'controllerType': 'ref',
+                      'hostname': name,
+                      'remoteIP': '127.0.0.1',
+                      'remotePort': 6633}
+            self.controllers[name] = ctrlr
+            # We want to start controller count at 0
+            self.controllerCount += 1
+
         icon = self.nodeIcon( node, name )
         item = self.canvas.create_window( x, y, anchor='c', window=icon,
                                           tags=node )
@@ -328,6 +1792,16 @@ def newNode( self, node, event ):
         self.itemToWidget[ item ] = icon
         self.selectItem( item )
         icon.links = {}
+        if 'Switch' == node:
+            icon.bind('<Button-3>', self.do_switchPopup )
+        if 'Host' == node:
+            icon.bind('<Button-3>', self.do_hostPopup )
+        if 'Controller' == node:
+            icon.bind('<Button-3>', self.do_controllerPopup )
+
+    def clickController( self, event ):
+        "Add a new Controller to our canvas."
+        self.newNode( 'Controller', event )
 
     def clickHost( self, event ):
         "Add a new host to our canvas."
@@ -362,8 +1836,7 @@ def createNodeBindings( self ):
             '<B1-Motion>': self.dragNode,
             '<ButtonRelease-1>': self.releaseNode,
             '<Enter>': self.enterNode,
-            '<Leave>': self.leaveNode,
-            '<Double-ButtonPress-1>': self.xterm
+            '<Leave>': self.leaveNode
         }
         l = Label()  # lightweight-ish owner for bindings
         for event, binding in bindings.items():
@@ -427,21 +1900,34 @@ def dragNodeAround( self, event ):
             item = self.widgetToItem[ dest ]
             x1, y1 = c.coords( item )
             c.coords( link, x, y, x1, y1 )
+        self.updateScrollRegion()
 
-    def startLink( self, event ):
-        "Start a new link."
-        if event.widget not in self.widgetToItem:
-            # Didn't click on a node
-            return
-        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
+    def createControlLinkBindings( self ):
+        "Create a set of bindings for nodes."
+        # Link bindings
+        # Selection still needs a bit of work overall
+        # Callbacks ignore event
+
+        def select( _event, link=self.link ):
+            "Select item on mouse entry."
+            self.selectItem( link )
+
+        def highlight( _event, link=self.link ):
+            "Highlight item on mouse entry."
+            self.selectItem( link )
+            self.canvas.itemconfig( link, fill='green' )
+
+        def unhighlight( _event, link=self.link ):
+            "Unhighlight item on mouse exit."
+            self.canvas.itemconfig( link, fill='red' )
+            #self.selectItem( None )
 
+        self.canvas.tag_bind( self.link, '<Enter>', highlight )
+        self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
+        self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
+
+    def createDataLinkBindings( self ):
+        "Create a set of bindings for nodes."
         # Link bindings
         # Selection still needs a bit of work overall
         # Callbacks ignore event
@@ -452,17 +1938,35 @@ def select( _event, link=self.link ):
 
         def highlight( _event, link=self.link ):
             "Highlight item on mouse entry."
-            # self.selectItem( link )
+            self.selectItem( link )
             self.canvas.itemconfig( link, fill='green' )
 
         def unhighlight( _event, link=self.link ):
             "Unhighlight item on mouse exit."
             self.canvas.itemconfig( link, fill='blue' )
-            # self.selectItem( None )
+            #self.selectItem( None )
 
         self.canvas.tag_bind( self.link, '<Enter>', highlight )
         self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
         self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
+        self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup )
+
+
+    def startLink( self, event ):
+        "Start a new link."
+        if event.widget not in self.widgetToItem:
+            # Didn't click on a node
+            return
+
+        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
+
 
     def finishLink( self, event ):
         "Finish creating a link"
@@ -481,12 +1985,39 @@ def finishLink( self, event ):
         # For now, don't allow hosts to be directly linked
         stags = self.canvas.gettags( self.widgetToItem[ source ] )
         dtags = self.canvas.gettags( target )
-        if 'Host' in stags and 'Host' in dtags:
+        if (('Host' in stags and 'Host' in dtags) or
+           ('Controller' in stags and 'Host' in dtags) or
+           ('Controller' in dtags and 'Host' in stags) or
+           ('Controller' in stags and 'Controller' in dtags)):
             self.releaseLink( event )
             return
+
+        # Set link type
+        linkType='data'
+        if 'Controller' in stags or 'Controller' in dtags:
+            linkType='control'
+            c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red')
+            self.createControlLinkBindings()
+        else:
+            linkType='data'
+            self.createDataLinkBindings()
+        c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,))
+
         x, y = c.coords( target )
         c.coords( self.link, self.linkx, self.linky, x, y )
-        self.addLink( source, dest )
+        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
 
@@ -500,14 +2031,22 @@ def about( self ):
             about = Toplevel( bg='white' )
             about.title( 'About' )
             info = self.appName + ': a simple network editor for MiniNet'
-            warning = 'Development version - not entirely functional!'
-            author = 'Bob Lantz <rlantz@cs>, April 2010'
+            version = 'MiniEdit '+MINIEDIT_VERSION
+            author = 'Originally by: Bob Lantz <rlantz@cs>, April 2010'
+            enhancements = 'Enhancements by: Gregory Gee, Since July 2013'
+            www = 'http://gregorygee.wordpress.com/category/miniedit/'
             line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg )
-            line2 = Label( about, text=warning, font='Helvetica 9', bg=bg )
+            line2 = Label( about, text=version, font='Helvetica 9', bg=bg )
             line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
+            line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg )
+            line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER )
+            line5.insert(0, www)
+            line5.configure(state='readonly')
             line1.pack( padx=20, pady=10 )
             line2.pack(pady=10 )
             line3.pack(pady=10 )
+            line4.pack(pady=10 )
+            line5.pack(pady=10 )
             hide = ( lambda about=about: about.withdraw() )
             self.aboutBox = about
             # Hide on close rather than destroying window
@@ -518,73 +2057,516 @@ def about( self ):
     def createToolImages( self ):
         "Create toolbar (and icon) images."
 
+    def checkIntf( self, 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
+
+    def hostDetails( self, _ignore=None ):
+        if ( self.selection is None or
+             self.net is not None or
+             self.selection not in self.itemToWidget ):
+            return
+        widget = self.itemToWidget[ self.selection ]
+        name = widget[ 'text' ]
+        tags = self.canvas.gettags( self.selection )
+        if 'Host' not in tags:
+            return
+
+        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['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']
+            self.hostOpts[name] = newHostOpts
+            print 'New host details for ' + name + ' = ' + str(newHostOpts)
+
+    def switchDetails( self, _ignore=None ):
+        if ( self.selection is None or
+             self.net is not None or
+             self.selection not in self.itemToWidget ):
+            return
+        widget = self.itemToWidget[ self.selection ]
+        name = widget[ 'text' ]
+        tags = self.canvas.gettags( self.selection )
+        if 'Switch' not in tags:
+            return
+
+        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['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
+            print 'New switch details for ' + name + ' = ' + str(newSwitchOpts)
+
+    def linkUp( self ):
+        if ( self.selection is None or
+             self.net is None):
+            return
+        link = self.selection
+        linkDetail =  self.links[link]
+        src = linkDetail['src']
+        dst = linkDetail['dest']
+        srcName, dstName = src[ 'text' ], dst[ 'text' ]
+        self.net.configLinkStatus(srcName, dstName, 'up')
+        self.canvas.itemconfig(link, dash=())
+
+    def linkDown( self ):
+        if ( self.selection is None or
+             self.net is None):
+            return
+        link = self.selection
+        linkDetail =  self.links[link]
+        src = linkDetail['src']
+        dst = linkDetail['dest']
+        srcName, dstName = src[ 'text' ], dst[ 'text' ]
+        self.net.configLinkStatus(srcName, dstName, 'down')
+        self.canvas.itemconfig(link, dash=(4, 4))
+
+    def linkDetails( self, _ignore=None ):
+        if ( self.selection is None or
+             self.net is not None):
+            return
+        link = self.selection
+
+        linkDetail =  self.links[link]
+        src = linkDetail['src']
+        dest = linkDetail['dest']
+        linkopts = linkDetail['linkOpts']
+        linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
+        if linkBox.result:
+            linkDetail['linkOpts'] = linkBox.result
+            print 'New link details = ' + str(linkBox.result)
+
+    def prefDetails( self ):
+        prefDefaults = self.appPrefs
+        prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults)
+        print 'New Prefs = ' + str(prefBox.result)
+        if prefBox.result:
+            self.appPrefs = prefBox.result
+
+
+    def controllerDetails( self ):
+        if ( self.selection is None or
+             self.net is not None or
+             self.selection not in self.itemToWidget ):
+            return
+        widget = self.itemToWidget[ self.selection ]
+        name = widget[ 'text' ]
+        tags = self.canvas.gettags( self.selection )
+        oldName = name
+        if 'Controller' not in tags:
+            return
+
+        ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name])
+        if ctrlrBox.result:
+            #print 'Controller is ' + ctrlrBox.result[0]
+            if len(ctrlrBox.result['hostname']) > 0:
+                name = ctrlrBox.result['hostname']
+                widget[ 'text' ] = name
+            else:
+                ctrlrBox.result['hostname'] = name
+            self.controllers[name] = ctrlrBox.result
+            print 'New controller details for ' + name + ' = ' + str(self.controllers[name])
+            # Find references to controller and change name
+            if oldName != name:
+                for widget in self.widgetToItem:
+                    switchName = widget[ 'text' ]
+                    tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                    if 'Switch' in tags:
+                        switch = self.switchOpts[switchName]
+                        if oldName in switch['controllers']:
+                            switch['controllers'].remove(oldName)
+                            switch['controllers'].append(name)
+
+
+    def listBridge( self, _ignore=None ):
+        if ( self.selection is None or
+             self.net is None or
+             self.selection not in self.itemToWidget ):
+            return
+        name = self.itemToWidget[ self.selection ][ 'text' ]
+        tags = self.canvas.gettags( self.selection )
+
+        if name not in self.net.nameToNode:
+            return
+        if 'Switch' in tags:
+           call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True)
+
+    def ovsShow( self, _ignore=None ):
+        call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True)
+
+    def rootTerminal( self, _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.
 
-    def addLink( self, source, dest ):
+    def addLink( self, source, dest, linktype='data', linkopts={} ):
         "Add link to model."
         source.links[ dest ] = self.link
         dest.links[ source ] = self.link
-        self.links[ self.link ] = ( source, dest )
+        self.links[ self.link ] = {'type' :linktype,
+                                   'src':source,
+                                   'dest':dest,
+                                   'linkOpts':linkopts}
 
     def deleteLink( self, link ):
         "Delete link from model."
         pair = self.links.get( link, None )
         if pair is not None:
-            source, dest = pair
+            source=pair['src']
+            dest=pair['dest']
             del source.links[ dest ]
             del dest.links[ source ]
+            stags = self.canvas.gettags( self.widgetToItem[ source ] )
+            dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
+            ltags = self.canvas.gettags( link )
+
+            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 link is not None:
             del self.links[ link ]
 
     def deleteNode( self, item ):
         "Delete node (and its links) from model."
+
         widget = self.itemToWidget[ item ]
+        tags = self.canvas.gettags(item)
+        if 'Controller' in tags:
+            # remove from switch controller lists
+            for serachwidget in self.widgetToItem:
+                name = serachwidget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] )
+                if 'Switch' in tags:
+                    if widget['text'] in self.switchOpts[name]['controllers']:
+                        self.switchOpts[name]['controllers'].remove(widget['text'])
+            
         for link in widget.links.values():
             # Delete from view and model
             self.deleteItem( link )
         del self.itemToWidget[ item ]
         del self.widgetToItem[ widget ]
 
-    def build( self ):
-        "Build network based on our topology."
-
-        net = Mininet( topo=None )
-
-        # Make controller
-        net.addController( 'c0' )
+    def buildNodes( self, net):
         # Make nodes
+        print "Getting Hosts and Switches."
         for widget in self.widgetToItem:
             name = widget[ 'text' ]
             tags = self.canvas.gettags( self.widgetToItem[ widget ] )
-            nodeNum = int( name[ 1: ] )
+
             if 'Switch' in tags:
-                net.addSwitch( name )
+                opts = self.switchOpts[name]
+
+                # 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
+                    else:
+                        switchClass = customOvs
+                elif opts['switchType'] == 'ivs':
+                    switchClass = IVSSwitch
+                else:
+                    switchClass = customOvs
+                newSwitch = net.addSwitch( name , cls=switchClass, **switchParms)
+                #if self.appPrefs['switchType'] == 'ovs':
+                if switchClass == customOvs:
+                    newSwitch.setOpenFlowVersion(self.appPrefs['openFlowVersions'])
+                    if ('switchIP' in opts):
+                        if (len(opts['switchIP']) > 0):
+                            newSwitch.setSwitchIP(opts['switchIP'])
+
+                # Attach external interfaces
+                if ('externalInterfaces' in opts):
+                    for extInterface in opts['externalInterfaces']:
+                        if self.checkIntf(extInterface):
+                           Intf( extInterface, node=newSwitch )
+
             elif 'Host' in tags:
-                #Generate IP adddress in the 10.0/8 block
-                ipAddr = ( 10 << 24 ) + nodeNum
-                net.addHost( name, ip=ipStr( ipAddr ) )
+                opts = self.hostOpts[name]
+                ip = None
+                defaultRoute = None
+                if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
+                    defaultRoute = 'via '+opts['defaultRoute']
+                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)
+
+                # Create the correct host class
+                newHost = net.addHost( name,
+                                       cls=CPULimitedHost,
+                                       ip=ip,
+                                       defaultRoute=defaultRoute
+                                      )
+
+                # Set the CPULimitedHost specific options
+                if 'cores' in opts:
+                    newHost.setCPUs(cores = opts['cores'])
+                if 'cpu' in opts:
+                    newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched'])
+
+                # Attach external interfaces
+                if ('externalInterfaces' in opts):
+                    for extInterface in opts['externalInterfaces']:
+                        if self.checkIntf(extInterface):
+                           Intf( extInterface, node=newHost )
+                if ('vlanInterfaces' in opts):
+                    if len(opts['vlanInterfaces']) > 0:
+                        print 'Checking that OS is VLAN prepared'
+                        self.pathCheck('vconfig', moduleName='vlan package')
+                        moduleDeps( add='8021q' )
+            elif 'Controller' in tags:
+                opts = self.controllers[name]
+
+                # Get controller info from panel
+                controllerType = opts['controllerType']
+
+                # Make controller
+                print 'Getting controller selection:'+controllerType
+                controllerIP = opts['remoteIP']
+                controllerPort = opts['remotePort']
+                if controllerType == 'remote':
+                    net.addController(name=name,
+                                      controller=RemoteController,
+                                      ip=controllerIP,
+                                      port=controllerPort)
+                elif controllerType == 'inband':
+                    net.addController(name=name,
+                                      controller=InbandController,
+                                      ip=controllerIP,
+                                      port=controllerPort)
+                elif controllerType == 'ovsc':
+                    net.addController(name=name,
+                                      controller=OVSController,
+                                      port=controllerPort)
+                else:
+                    net.addController(name=name,
+                                      controller=Controller,
+                                      port=controllerPort)
+
             else:
                 raise Exception( "Cannot create mystery node: " + name )
+
+    def pathCheck( self, *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.' )
+
+    def buildLinks( self, net):
         # Make links
-        for link in self.links.values():
-            ( src, dst ) = link
-            srcName, dstName = src[ 'text' ], dst[ 'text' ]
-            src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
-            src.linkTo( dst )
+        print "Getting Links."
+        for key,link in self.links.iteritems():
+            tags = self.canvas.gettags(key)
+            if 'data' in tags:
+                src=link['src']
+                dst=link['dest']
+                linkopts=link['linkOpts']
+                srcName, dstName = src[ 'text' ], dst[ 'text' ]
+                src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
+                net.addLink(src, dst, **linkopts)
+                self.canvas.itemconfig(key, dash=())
+
+
+    def build( self ):
+        print "Build network based on our topology."
+
+        dpctl = None
+        if len(self.appPrefs['dpctl']) > 0:
+            dpctl = int(self.appPrefs['dpctl'])
+        net = Mininet( topo=None,
+                       listenPort=dpctl,
+                       build=False,
+                       link=TCLink,
+                       ipBase=self.appPrefs['ipBase'] )
+
+        self.buildNodes(net)
+        self.buildLinks(net)
 
         # Build network (we have to do this separately at the moment )
         net.build()
 
         return net
 
+
+    def postStartSetup( self ):
+
+        # Setup host VLAN subinterfaces
+        for widget in self.widgetToItem:
+            name = widget[ 'text' ]
+            tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+            if 'Host' in tags:
+                opts = self.hostOpts[name]
+                # Attach vlan interfaces
+                if ('vlanInterfaces' in opts):
+                    for vlanInterface in opts['vlanInterfaces']:
+                        print 'adding vlan interface '+vlanInterface[1]
+                        newHost = self.net.get(name)
+                        newHost.cmdPrint('vconfig add '+name+'-eth0 '+vlanInterface[1])
+                        newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0])
+
+        # 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':
+                            print name+' has Netflow enabled'
+                            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'
+                print 'cmd = '+nflowCmd+nflowSwitches
+                call(nflowCmd+nflowSwitches, shell=True)
+
+            else:
+                print 'No switches with Netflow'
+        else:
+            print 'No NetFlow targets specified.'
+
+        # 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':
+                            print name+' has sflow enabled'
+                            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']
+                print 'cmd = '+sflowCmd+sflowSwitches
+                call(sflowCmd+sflowSwitches, shell=True)
+
+            else:
+                print 'No switches with sflow'
+        else:
+            print 'No sFlow targets specified.'
+
+        ## NOTE: MAKE SURE THIS IS LAST THING CALLED
+        # Start the CLI if enabled
+        if self.appPrefs['startCLI'] == '1':
+            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")
+            CLI(self.net)
+
     def start( self ):
         "Start network."
         if self.net is None:
             self.net = self.build()
-            self.net.start()
+
+            # Since I am going to inject per switch controllers.
+            # I can't call net.start().  I have to replicate what it
+            # does and add the controller options.
+            #self.net.start()
+            info( '**** Starting %s controllers\n' % len( self.net.controllers ) )
+            for controller in self.net.controllers:
+                info( str(controller) + ' ')
+                controller.start()
+            info('\n')
+            info( '**** Starting %s switches\n' % len( self.net.switches ) )
+            #for switch in self.net.switches:
+            #    info( switch.name + ' ')
+            #    switch.start( self.net.controllers )
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Switch' in tags:
+                    opts = self.switchOpts[name]
+                    switchControllers = []
+                    for ctrl in opts['controllers']:
+                        switchControllers.append(self.net.get(ctrl))
+                    info( name + ' ')
+                    # Figure out what controllers will manage this switch
+                    self.net.get(name).start( switchControllers )
+            info('\n')
+
+            self.postStartSetup()
 
     def stop( self ):
         "Stop network."
@@ -593,6 +2575,60 @@ def stop( self ):
         cleanUpScreens()
         self.net = None
 
+    def do_linkPopup(self, event):
+        # display the popup menu
+        if ( self.net is None ):
+            try:
+                self.linkPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.linkPopup.grab_release()
+        else:
+            try:
+                self.linkRunPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.linkRunPopup.grab_release()
+
+    def do_controllerPopup(self, event):
+        # display the popup menu
+        if ( self.net is None ):
+            try:
+                self.controllerPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.controllerPopup.grab_release()
+
+    def do_hostPopup(self, event):
+        # display the popup menu
+        if ( self.net is None ):
+            try:
+                self.hostPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.hostPopup.grab_release()
+        else:
+            try:
+                self.hostRunPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.hostRunPopup.grab_release()
+
+    def do_switchPopup(self, event):
+        # display the popup menu
+        if ( self.net is None ):
+            try:
+                self.switchPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.switchPopup.grab_release()
+        else:
+            try:
+                self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.switchRunPopup.grab_release()
+
     def xterm( self, _ignore=None ):
         "Make an xterm when a button is pressed."
         if ( self.selection is None or
@@ -602,8 +2638,193 @@ def xterm( self, _ignore=None ):
         name = self.itemToWidget[ self.selection ][ 'text' ]
         if name not in self.net.nameToNode:
             return
-        term = makeTerm( self.net.nameToNode[ name ], 'Host' )
-        self.net.terms += term
+        term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] )
+        if StrictVersion(VERSION) > StrictVersion('2.0'):
+            self.net.terms += term
+        else:
+            self.net.terms.append(term)
+
+    def iperf( self, _ignore=None ):
+        "Make an xterm when a button is pressed."
+        if ( self.selection is None or
+             self.net is None or
+             self.selection not in self.itemToWidget ):
+            return
+        name = self.itemToWidget[ self.selection ][ 'text' ]
+        if name not in self.net.nameToNode:
+            return
+        self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' )
+
+    """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """
+
+    def parseArgs( self ):
+        """Parse command-line args and return options object.
+           returns: opts parse options dict"""
+
+        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' )
+
+        desc = ( "The %prog utility creates Mininet network from the\n"
+                 "command line. It can create parametrized topologies,\n"
+                 "invoke the Mininet CLI, and run tests." )
+
+        usage = ( '%prog [options]\n'
+                  '(type %prog -h for details)' )
+
+        opts = OptionParser( description=desc, usage=usage )
+
+        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
+        opts.add_option( '--custom', type='string', default=None,
+                         help='read custom topo and node params from .py' +
+                         'file' )
+
+        self.options, self.args = opts.parse_args()
+        # We don't accept extra arguments after the options
+        if self.args:
+            opts.print_help()
+            exit()
+
+    def setCustom( self, name, value ):
+        "Set custom parameters for MininetRunner."
+        if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
+            # Update dictionaries
+            param = name.upper()
+            globals()[ param ].update( value )
+        elif name == 'validate':
+            # Add custom validate function
+            self.validate = value
+        else:
+            # Add or modify global variable or class
+            globals()[ name ] = value
+
+    def parseCustomFile( self, fileName ):
+        "Parse custom file and add params before parsing cmd-line options."
+        customs = {}
+        if os.path.isfile( fileName ):
+            execfile( fileName, customs, customs )
+            for name, val in customs.iteritems():
+                self.setCustom( name, val )
+        else:
+            raise Exception( 'could not find custom file: %s' % fileName )
+
+    def importTopo( self ):
+        print 'topo='+self.options.topo
+        if self.options.topo == 'none':
+            return
+        self.newTopology()
+        topo = buildTopo( TOPOS, self.options.topo )
+        importNet = Mininet(topo=topo, build=False)
+        importNet.build()
+
+        c = self.canvas
+        rowIncrement = 100
+        currentY = 100
+
+        # Add Controllers
+        print 'controllers:'+str(len(importNet.controllers))
+        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,
+                      'remoteIP': controller.ip,
+                      'remotePort': controller.port}
+            self.controllers[name] = ctrlr
+
+
+
+        currentY = currentY + rowIncrement
+
+        # Add switches
+        print 'switches:'+str(len(importNet.switches))
+        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
+
+
+        currentY = currentY + rowIncrement
+        # Add hosts
+        print 'hosts:'+str(len(importNet.hosts))
+        columnCount = 0
+        for host in importNet.hosts:
+            name = host.name
+            self.hostOpts[name] = {'sched':'host'}
+            self.hostOpts[name]['nodeNum']=self.hostCount
+            self.hostOpts[name]['hostname']=name
+            self.hostOpts[name]['ip']=host.IP()
+
+            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
+
+        print 'links:'+str(len(topo.links()))
+        #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')]
+        for link in topo.links():
+            srcNode = link[0]
+            src = self.findWidgetByName(srcNode)
+            sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
+
+            destNode = link[1]
+            dest = self.findWidgetByName(destNode)
+            dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
+
+            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
+                                             fill='blue', tag='link' )
+            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
+            self.addLink( src, dest )
+            self.createDataLinkBindings()
+            self.link = self.linkWidget = None
+
+        importNet.stop()
 
 def miniEditImages():
     "Create and return images for MiniEdit."
@@ -616,6 +2837,31 @@ def miniEditImages():
         'Select': BitmapImage(
             file='/usr/include/X11/bitmaps/left_ptr' ),
 
+        'Controller': PhotoImage( data=r"""
+            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
+            """),
+
         'Host': PhotoImage( data=r"""
             R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
             mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
@@ -698,7 +2944,29 @@ def miniEditImages():
         """ )
     }
 
+def addDictOption( opts, choicesDict, default, name, helpStr=None ):
+    """Convenience function to add choices dicts to OptionParser.
+       opts: OptionParser instance
+       choicesDict: dictionary of valid choices, must include default
+       default: default choice key
+       name: long option name
+       help: string"""
+    if default not in choicesDict:
+        raise Exception( 'Invalid  default %s for choices dict: %s' %
+                         ( default, name ) )
+    if not helpStr:
+        helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
+                    '[,param=value...]' )
+    opts.add_option( '--' + name,
+                     type='string',
+                     default = default,
+                     help = helpStr )
+
 if __name__ == '__main__':
     setLogLevel( 'info' )
     app = MiniEdit()
+    """ import topology if specified """
+    app.parseArgs()
+    app.importTopo()
+
     app.mainloop()
-- 
GitLab