diff --git a/INSTALL b/INSTALL index 494638c..6d6337f 100644 --- a/INSTALL +++ b/INSTALL @@ -2,7 +2,7 @@ Mininet Installation/Configuration Notes ---------------------------------------- -Mininet 2.1.0 +Mininet 2.1.0+ --- The supported installation methods for Mininet are 1) using a @@ -51,6 +51,13 @@ like to contribute an installation script, we would welcome it!) git clone git://github.com/mininet/mininet.git + Note that the above git command will check out the latest and greatest + Mininet (which we recommend!) If you want to run the last tagged/released + version of Mininet, use: + + git clone git://github.com/mininet/mininet + git checkout -b 2.1.0 2.1.0 + If you are running Ubuntu, you may be able to use our handy `install.sh` script, which is in `mininet/util`. diff --git a/LICENSE b/LICENSE index de9b391..0cb01dd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Mininet 2.1.0 License +Mininet 2.1.0+ License Copyright (c) 2013 Open Networking Laboratory Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of diff --git a/Makefile b/Makefile index c989e61..74da5ac 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,8 @@ MANDIR = /usr/share/man/man1 DOCDIRS = doc/html doc/latex PDF = doc/latex/refman.pdf +CFLAGS += -Wall -Wextra + all: codecheck test clean: diff --git a/README.md b/README.md index 785dd15..4c668ae 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Mininet: Rapid Prototyping for Software Defined Networks *The best way to emulate almost any network on your laptop!* -Version 2.1.0 +Version 2.1.0+ ### What is Mininet? @@ -66,9 +66,9 @@ Mininet includes: `mn -c` -### New features in 2.1.0 +### New features in 2.1.0+ -Mininet 2.1.0 provides a number of bug fixes as well as +Mininet 2.1.0+ provides a number of bug fixes as well as several new features, including: * Convenient access to `Mininet()` as a dict of nodes @@ -127,7 +127,7 @@ Mininet to change the networking world! ### Credits -The Mininet 2.1.0 Team: +The Mininet 2.1.0+ Team: * Bob Lantz * Brian O'Connor diff --git a/bin/mn b/bin/mn index 5bab076..f4b16ef 100755 --- a/bin/mn +++ b/bin/mn @@ -25,11 +25,13 @@ from mininet.cli import CLI from mininet.log import lg, LEVELS, info, debug, error from mininet.net import Mininet, MininetWithControlNet, VERSION from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, - NOX, RemoteController, UserSwitch, OVSKernelSwitch, + NOX, RemoteController, DefaultController, + UserSwitch, OVSSwitch, OVSLegacyKernelSwitch, IVSSwitch ) +from mininet.nodelib import LinuxBridge from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo -from mininet.topolib import TreeTopo +from mininet.topolib import TreeTopo, TorusTopo from mininet.util import custom, customConstructor from mininet.util import buildTopo @@ -40,24 +42,29 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), 'linear': LinearTopo, 'reversed': SingleSwitchReversedTopo, 'single': SingleSwitchTopo, - 'tree': TreeTopo } + 'tree': TreeTopo, + 'torus': TorusTopo } SWITCHDEF = 'ovsk' SWITCHES = { 'user': UserSwitch, - 'ovsk': OVSKernelSwitch, + 'ovs': OVSSwitch, + # Keep ovsk for compatibility with 2.0 + 'ovsk': OVSSwitch, 'ovsl': OVSLegacyKernelSwitch, - 'ivs': IVSSwitch } + 'ivs': IVSSwitch, + 'lxbr': LinuxBridge } HOSTDEF = 'proc' HOSTS = { 'proc': Host, 'rt': custom( CPULimitedHost, sched='rt' ), 'cfs': custom( CPULimitedHost, sched='cfs' ) } -CONTROLLERDEF = 'ovsc' +CONTROLLERDEF = 'default' CONTROLLERS = { 'ref': Controller, 'ovsc': OVSController, 'nox': NOX, 'remote': RemoteController, + 'default': DefaultController, 'none': lambda name: None } LINKDEF = 'default' @@ -268,12 +275,14 @@ class MininetRunner( object ): if test == 'none': pass elif test == 'all': + mn.waitConnected() mn.start() mn.ping() mn.iperf() elif test == 'cli': CLI( mn ) elif test != 'build': + mn.waitConnected() getattr( mn, test )() if self.options.post: diff --git a/examples/README.md b/examples/README.md index 4be5564..96e30d7 100644 --- a/examples/README.md +++ b/examples/README.md @@ -117,3 +117,8 @@ memory and `sysctl` configuration (see `INSTALL`.) This example creates a 64-host tree network, and attempts to check full connectivity using `ping`, for different switch/datapath types. + +#### numberedports.py + +This example verifies the mininet ofport numbers match up to the ovs port numbers. +It also verifies that the port numbers match up to the interface numbers diff --git a/examples/bind.py b/examples/bind.py index 2c317cf..287c97a 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -1,197 +1,72 @@ #!/usr/bin/python """ -bind.py: Bind mount prototype +bind.py: Bind mount example -This creates hosts with private directories as desired. +This creates hosts with private directories that the user specifies. +These hosts may have persistent directories that will be available +across multiple mininet session, or temporary directories that will +only last for one mininet session. To specify a persistent +directory, add a tuple to a list of private directories: + + [ ( 'directory to be mounted on', 'directory to be mounted' ) ] + +String expansion may be used to create a directory template for +each host. To do this, add a %(name)s in place of the host name +when creating your list of directories: + + [ ( '/var/run', '/tmp/%(name)s/var/run' ) ] + +If no persistent directory is specified, the directories will default +to temporary private directories. To do this, simply create a list of +directories to be made private. A tmpfs will then be mounted on them. + +You may use both temporary and persistent directories at the same +time. In the following privateDirs string, each host will have a +persistent directory in the root filesystem at +"/tmp/(hostname)/var/run" mounted on "/var/run". Each host will also +have a temporary private directory mounted on "/var/log". + + [ ( '/var/run', '/tmp/%(name)s/var/run' ), '/var/log' ] + +This example has both persistent directories mounted on '/var/log' +and '/var/run'. It also has a temporary private directory mounted +on '/var/mn' """ from mininet.net import Mininet -from mininet.node import Host +from mininet.node import Host, HostWithPrivateDirs from mininet.cli import CLI -from mininet.util import errFail, quietRun, errRun from mininet.topo import SingleSwitchTopo from mininet.log import setLogLevel, info, debug -from os.path import realpath from functools import partial -# Utility functions for unmounting a tree - -MNRUNDIR = realpath( '/var/run/mn' ) - -def mountPoints(): - "Return list of mounted file systems" - mtab, _err, _ret = errFail( 'cat /proc/mounts' ) - lines = mtab.split( '\n' ) - mounts = [] - for line in lines: - if not line: - continue - fields = line.split( ' ') - mount = fields[ 1 ] - mounts.append( mount ) - return mounts - -def unmountAll( rootdir=MNRUNDIR ): - "Unmount all mounts under a directory tree" - rootdir = realpath( rootdir ) - # Find all mounts below rootdir - # This is subtle because /foo is not - # a parent of /foot - dirslash = rootdir + '/' - mounts = [ m for m in mountPoints() - if m == dir or m.find( dirslash ) == 0 ] - # Unmount them from bottom to top - mounts.sort( reverse=True ) - for mount in mounts: - debug( 'Unmounting', mount, '\n' ) - _out, err, code = errRun( 'umount', mount ) - if code != 0: - info( '*** Warning: failed to umount', mount, '\n' ) - info( err ) - - -class HostWithPrivateDirs( Host ): - "Host with private directories" - - mnRunDir = MNRUNDIR - - def __init__(self, name, *args, **kwargs ): - """privateDirs: list of private directories - remounts: dirs to remount - unmount: unmount dirs in cleanup? (True) - Note: if unmount is False, you must call unmountAll() - manually.""" - self.privateDirs = kwargs.pop( 'privateDirs', [] ) - self.remounts = kwargs.pop( 'remounts', [] ) - self.unmount = kwargs.pop( 'unmount', True ) - Host.__init__( self, name, *args, **kwargs ) - self.rundir = '%s/%s' % ( self.mnRunDir, name ) - self.root, self.private = None, None # set in createBindMounts - if self.privateDirs: - self.privateDirs = [ realpath( d ) for d in self.privateDirs ] - self.createBindMounts() - # These should run in the namespace before we chroot, - # in order to put the right entries in /etc/mtab - # Eventually this will allow a local pid space - # Now we chroot and cd to wherever we were before. - pwd = self.cmd( 'pwd' ).strip() - self.sendCmd( 'exec chroot', self.root, 'bash -ms mininet:' - + self.name ) - self.waiting = False - self.cmd( 'cd', pwd ) - # In order for many utilities to work, - # we need to remount /proc and /sys - self.cmd( 'mount /proc' ) - self.cmd( 'mount /sys' ) - - def mountPrivateDirs( self ): - "Create and bind mount private dirs" - for dir_ in self.privateDirs: - privateDir = self.private + dir_ - errFail( 'mkdir -p ' + privateDir ) - mountPoint = self.root + dir_ - errFail( 'mount -B %s %s' % - ( privateDir, mountPoint) ) - - def mountDirs( self, dirs ): - "Mount a list of directories" - for dir_ in dirs: - mountpoint = self.root + dir_ - errFail( 'mount -B %s %s' % - ( dir_, mountpoint ) ) - - @classmethod - def findRemounts( cls, fstypes=None ): - """Identify mount points in /proc/mounts to remount - fstypes: file system types to match""" - if fstypes is None: - fstypes = [ 'nfs' ] - dirs = quietRun( 'cat /proc/mounts' ).strip().split( '\n' ) - remounts = [] - for dir_ in dirs: - line = dir_.split() - mountpoint, fstype = line[ 1 ], line[ 2 ] - # Don't re-remount directories!!! - if mountpoint.find( cls.mnRunDir ) == 0: - continue - if fstype in fstypes: - remounts.append( mountpoint ) - return remounts - - def createBindMounts( self ): - """Create a chroot directory structure, - with self.privateDirs as private dirs""" - errFail( 'mkdir -p '+ self.rundir ) - unmountAll( self.rundir ) - # Create /root and /private directories - self.root = self.rundir + '/root' - self.private = self.rundir + '/private' - errFail( 'mkdir -p ' + self.root ) - errFail( 'mkdir -p ' + self.private ) - # Recursively mount / in private doort - # note we'll remount /sys and /proc later - errFail( 'mount -B / ' + self.root ) - self.mountDirs( self.remounts ) - self.mountPrivateDirs() - - def unmountBindMounts( self ): - "Unmount all of our bind mounts" - unmountAll( self.rundir ) - - def popen( self, *args, **kwargs ): - "Popen with chroot support" - chroot = kwargs.pop( 'chroot', True ) - mncmd = kwargs.get( 'mncmd', - [ 'mnexec', '-a', str( self.pid ) ] ) - if chroot: - mncmd = [ 'chroot', self.root ] + mncmd - kwargs[ 'mncmd' ] = mncmd - return Host.popen( self, *args, **kwargs ) - - def cleanup( self ): - """Clean up, then unmount bind mounts - unmount: actually unmount bind mounts?""" - # Wait for process to actually terminate - self.shell.wait() - Host.cleanup( self ) - if self.unmount: - self.unmountBindMounts() - errFail( 'rmdir ' + self.root ) - - -# Convenience aliases - -findRemounts = HostWithPrivateDirs.findRemounts - - # Sample usage def testHostWithPrivateDirs(): "Test bind mounts" topo = SingleSwitchTopo( 10 ) - remounts = findRemounts( fstypes=[ 'nfs' ] ) - privateDirs = [ '/var/log', '/var/run' ] - host = partial( HostWithPrivateDirs, remounts=remounts, - privateDirs=privateDirs, unmount=False ) + privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ), + ( '/var/run', '/tmp/%(name)s/var/run' ), + '/var/mn' ] + host = partial( HostWithPrivateDirs, + privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) net.start() - info( 'Private Directories:', privateDirs, '\n' ) + directories = [] + for directory in privateDirs: + directories.append( directory[ 0 ] + if isinstance( directory, tuple ) + else directory ) + info( 'Private Directories:', directories, '\n' ) CLI( net ) net.stop() - # We do this all at once to save a bit of time - info( 'Unmounting host bind mounts...\n' ) - unmountAll() - if __name__ == '__main__': - unmountAll() setLogLevel( 'info' ) testHostWithPrivateDirs() info( 'Done.\n') - - diff --git a/examples/controllers2.py b/examples/controllers2.py index 0836533..628a2f1 100755 --- a/examples/controllers2.py +++ b/examples/controllers2.py @@ -19,7 +19,7 @@ from mininet.log import setLogLevel def multiControllerNet(): "Create a network from semi-scratch with multiple controllers." - net = Mininet( controller=Controller, switch=OVSSwitch, build=False ) + net = Mininet( controller=Controller, switch=OVSSwitch ) print "*** Creating (reference) controllers" c1 = net.addController( 'c1', port=6633 ) diff --git a/examples/linearbandwidth.py b/examples/linearbandwidth.py index 3fd06c7..dee5490 100755 --- a/examples/linearbandwidth.py +++ b/examples/linearbandwidth.py @@ -24,7 +24,7 @@ of switches, this example demonstrates: """ from mininet.net import Mininet -from mininet.node import UserSwitch, OVSKernelSwitch +from mininet.node import UserSwitch, OVSKernelSwitch, Controller from mininet.topo import Topo from mininet.log import lg from mininet.util import irange @@ -76,7 +76,7 @@ def linearBandwidthTest( lengths ): print "*** testing", datapath, "datapath" Switch = switches[ datapath ] results[ datapath ] = [] - net = Mininet( topo=topo, switch=Switch ) + net = Mininet( topo=topo, switch=Switch, controller=Controller, waitConnected=True ) net.start() print "*** testing basic connectivity" for n in lengths: diff --git a/examples/miniedit.py b/examples/miniedit.py index 2883c1c..472c0bc 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -6,30 +6,1011 @@ MiniEdit: a simple network editor for Mininet This is a simple demonstration of how one might build a GUI application using Mininet as the network model. -Development version - not entirely functional! - Bob Lantz, April 2010 +Gregory Gee, July 2013 + +Controller icon from http://semlabs.co.uk/ +OpenFlow icon from https://www.opennetworking.org/ """ -from Tkinter import Frame, Button, Label, Scrollbar, Canvas -from Tkinter import Menu, BitmapImage, PhotoImage, Wm, Toplevel +MINIEDIT_VERSION = '2.1.0.8.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, Node +from mininet.node import OVSKernelSwitch, OVSSwitch, UserSwitch +from mininet.link import TCLink, Intf, Link +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 +MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION) +if StrictVersion(MININET_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 CustomUserSwitch(UserSwitch): + def __init__( self, name, dpopts='--no-slicing', **kwargs ): + UserSwitch.__init__( self, name, **kwargs ) + self.switchIP = None + + def getSwitchIP(self): + return self.switchIP + + def setSwitchIP(self, ip): + self.switchIP = ip + + def start( self, controllers ): + # Call superclass constructor + UserSwitch.start( self, controllers ) + # Set Switch IP address + if (self.switchIP is not None): + if not self.inNamespace: + self.cmd( 'ifconfig', self, self.switchIP ) + else: + self.cmd( 'ifconfig lo', self.switchIP ) + +class LegacyRouter( Node ): + + def __init__( self, name, inNamespace=True, **params ): + Node.__init__( self, name, inNamespace, **params ) + + def config( self, **_params ): + if self.intfs: + self.setParam( _params, 'setIP', ip='0.0.0.0' ) + r = Node.config( self, **_params ) + self.cmd('sysctl -w net.ipv4.ip_forward=1') + return r + +class LegacySwitch(OVSSwitch): + + def __init__( self, name, **params ): + OVSSwitch.__init__( self, name, failMode='standalone', **params ) + self.switchIP = None + +class customOvs(OVSSwitch): + + def __init__( self, name, failMode='secure', datapath='kernel', **params ): + OVSSwitch.__init__( self, name, failMode=failMode, datapath=datapath, **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", "Userspace Switch", "Userspace Switch inNamespace") + self.switchTypeMenu.grid(row=3, column=1, sticky=W) + switchTypePref = self.prefValues['switchType'] + if switchTypePref == 'ivs': + self.switchType.set("Indigo Virtual Switch") + elif switchTypePref == 'userns': + self.switchType.set("Userspace Switch inNamespace") + elif switchTypePref == 'user': + self.switchType.set("Userspace 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(MININET_VERSION) < StrictVersion('2.1'): + self.ovsOk = False + showerror(title="Error", + message='MiniNet version 2.1+ required. You have '+VERSION+'.') + elif sw == 'Userspace Switch': + self.result['switchType'] = 'user' + elif sw == 'Userspace Switch inNamespace': + self.result['switchType'] = 'userns' + else: + self.result['switchType'] = 'ovs' + + self.ovsOk = True + if ovsOf11 == "1": + ovsVer = self.getOvsVersion() + if StrictVersion(ovsVer) < StrictVersion('2.0'): + self.ovsOk = False + showerror(title="Error", + message='Open vSwitch version 2.0+ required. You have '+ovsVer+'.') + if ovsOf12 == "1" or ovsOf13 == "1": + ovsVer = self.getOvsVersion() + if StrictVersion(ovsVer) < StrictVersion('1.10'): + self.ovsOk = False + showerror(title="Error", + message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.') + + if self.ovsOk: + self.result['openFlowVersions']={'ovsOf10':ovsOf10, + 'ovsOf11':ovsOf11, + 'ovsOf12':ovsOf12, + 'ovsOf13':ovsOf13} + else: + self.result = None + + 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", "Userspace Switch", "Userspace Switch inNamespace") + 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 == 'userns': + self.switchType.set("Userspace Switch inNamespace") + elif switchTypePref == 'user': + self.switchType.set("Userspace 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(MININET_VERSION) < StrictVersion('2.1'): + self.ovsOk = False + showerror(title="Error", + message='MiniNet version 2.1+ required. You have '+VERSION+'.') + elif sw == 'Userspace Switch inNamespace': + results['switchType'] = 'userns' + elif sw == 'Userspace Switch': + results['switchType'] = 'user' + elif sw == 'Open vSwitch': + 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_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_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 ToolTip(object): + + def __init__(self, widget): + self.widget = widget + self.tipwindow = None + self.id = None + self.x = self.y = 0 + + def showtip(self, text): + "Display text in tooltip window" + self.text = text + if self.tipwindow or not self.text: + return + x, y, cx, cy = self.widget.bbox("insert") + x = x + self.widget.winfo_rootx() + 27 + y = y + cy + self.widget.winfo_rooty() +27 + self.tipwindow = tw = Toplevel(self.widget) + tw.wm_overrideredirect(1) + tw.wm_geometry("+%d+%d" % (x, y)) + try: + # For Mac OS + tw.tk.call("::tk::unsupported::MacWindowStyle", + "style", tw._w, + "help", "noActivates") + except TclError: + pass + label = Label(tw, text=self.text, justify=LEFT, + background="#ffffe0", relief=SOLID, borderwidth=1, + font=("tahoma", "8", "normal")) + label.pack(ipadx=1) + + def hidetip(self): + tw = self.tipwindow + self.tipwindow = None + if tw: + tw.destroy() class MiniEdit( Frame ): "A simple network editor for Mininet." - def __init__( self, parent=None, cheight=200, cwidth=500 ): + def __init__( self, parent=None, cheight=600, cwidth=1000 ): + + 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 +1028,14 @@ class MiniEdit( Frame ): 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', 'LegacySwitch', 'LegacyRouter', 'NetLink', 'Controller' ) self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' } self.toolbar = self.createToolbar() @@ -67,7 +1051,7 @@ class MiniEdit( Frame ): # Initialize node data self.nodeBindings = self.createNodeBindings() - self.nodePrefixes = { 'Switch': 's', 'Host': 'h' } + self.nodePrefixes = { 'LegacyRouter': 'r', 'LegacySwitch': 's', 'Switch': 's', 'Host': 'h' , 'Controller': 'c'} self.widgetToItem = {} self.itemToWidget = {} @@ -83,13 +1067,59 @@ class MiniEdit( Frame ): self.bind( '', 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.legacyRouterRunPopup = Menu(self.top, tearoff=0) + self.legacyRouterRunPopup.add_command(label='Router Options', font=self.font) + self.legacyRouterRunPopup.add_separator() + self.legacyRouterRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm ) + + self.switchPopup = Menu(self.top, tearoff=0) + self.switchPopup.add_command(label='Switch 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='Switch 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 +1138,35 @@ class MiniEdit( Frame ): 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 ): @@ -200,6 +1231,16 @@ class MiniEdit( Frame ): # Activate dynamic bindings self.active = toolName + + def createToolTip(self, widget, text): + toolTip = ToolTip(widget) + def enter(event): + toolTip.showtip(text) + def leave(event): + toolTip.hidetip() + widget.bind('', enter) + widget.bind('', leave) + def createToolbar( self ): "Create and return our toolbar frame." @@ -211,6 +1252,7 @@ class MiniEdit( Frame ): b = Button( toolbar, text=tool, font=self.smallFont, command=cmd) if tool in self.images: b.config( height=35, image=self.images[ tool ] ) + self.createToolTip(b, str(tool)) # b.config( compound='top' ) b.pack( fill='x' ) self.buttons[ tool ] = b @@ -241,6 +1283,559 @@ class MiniEdit( Frame ): 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('', 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('', 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('', 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'] + if switch['opts']['switchType'] == "legacyRouter": + self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname) + icon = self.findWidgetByName(hostname) + icon.bind('', self.do_legacyRouterPopup ) + elif switch['opts']['switchType'] == "legacySwitch": + self.addNode('LegacySwitch', nodeNum, float(x), float(y), name=hostname) + icon = self.findWidgetByName(hostname) + icon.bind('', self.do_legacySwitchPopup ) + else: + self.addNode('Switch', nodeNum, float(x), float(y), name=hostname) + icon = self.findWidgetByName(hostname) + icon.bind('', self.do_switchPopup ) + self.switchOpts[hostname] = switch['opts'] + + # 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 or 'LegacySwitch' in tags or 'LegacyRouter' 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, Node\n") + f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n") + if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): + f.write("from mininet.node import IVSSwitch\n") + f.write("from mininet.cli import CLI\n") + f.write("from mininet.log import setLogLevel, info\n") + f.write("from mininet.link import TCLink, Intf\n") + + inBandCtrl = False + hasLegacySwitch = False + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + + if 'Controller' in tags: + opts = self.controllers[name] + controllerType = opts['controllerType'] + if controllerType == 'inband': + inBandCtrl = True + + if inBandCtrl == True: + f.write("\n") + f.write("class InbandController( RemoteController ):\n") + f.write("\n") + f.write(" def checkListening( self ):\n") + f.write(" \"Overridden to do nothing.\"\n") + f.write(" return\n") + + f.write("\n") + f.write("def myNetwork():\n") + f.write("\n") + f.write(" net = Mininet( topo=None,\n") + if len(self.appPrefs['dpctl']) > 0: + f.write(" listenPort="+self.appPrefs['dpctl']+",\n") + f.write(" build=False,\n") + f.write(" ipBase='"+self.appPrefs['ipBase']+"')\n") + f.write("\n") + f.write(" info( '*** Adding controller\\n' )\n") + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + + if 'Controller' in tags: + opts = self.controllers[name] + controllerType = opts['controllerType'] + 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 'LegacyRouter' in tags: + f.write(" "+name+" = net.addHost('"+name+"', cls=Node, ip='0.0.0.0')\n") + f.write(" "+name+".cmd('sysctl -w net.ipv4.ip_forward=1')\n") + if 'LegacySwitch' in tags: + f.write(" "+name+" = net.addSwitch('"+name+"', cls=OVSKernelSwitch, failMode='standalone')\n") + 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") + elif self.appPrefs['switchType'] == 'user': + f.write(", cls=UserSwitch") + elif self.appPrefs['switchType'] == 'userns': + f.write(", cls=UserSwitch, inNamespace=True") + else: + f.write(", cls=OVSKernelSwitch") + elif opts['switchType'] == 'ivs': + f.write(", cls=IVSSwitch") + elif opts['switchType'] == 'user': + f.write(", cls=UserSwitch") + elif opts['switchType'] == 'userns': + f.write(", cls=UserSwitch, inNamespace=True") + 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(", link=TCLink , **"+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 or 'LegacySwitch' 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 opts['switchType'] == 'default': + if self.appPrefs['switchType'] == 'user': + if ('switchIP' in opts): + if (len(opts['switchIP'])>0): + f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") + elif self.appPrefs['switchType'] == 'userns': + if ('switchIP' in opts): + if (len(opts['switchIP'])>0): + f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n") + elif self.appPrefs['switchType'] == 'ovs': + if ('switchIP' in opts): + if (len(opts['switchIP'])>0): + f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") + elif opts['switchType'] == 'user': + if ('switchIP' in opts): + if (len(opts['switchIP'])>0): + f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") + elif opts['switchType'] == 'userns': + if ('switchIP' in opts): + if (len(opts['switchIP'])>0): + f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n") + elif opts['switchType'] == 'ovs': + 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 +1914,46 @@ class MiniEdit( Frame ): "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 'LegacyRouter' == 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']='legacyRouter' + if 'LegacySwitch' == 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']='legacySwitch' + 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,16 +1961,38 @@ class MiniEdit( Frame ): self.itemToWidget[ item ] = icon self.selectItem( item ) icon.links = {} + if 'Switch' == node: + icon.bind('', self.do_switchPopup ) + if 'LegacyRouter' == node: + icon.bind('', self.do_legacyRouterPopup ) + if 'LegacySwitch' == node: + icon.bind('', self.do_legacySwitchPopup ) + if 'Host' == node: + icon.bind('', self.do_hostPopup ) + if 'Controller' == node: + icon.bind('', 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." self.newNode( 'Host', event ) + def clickLegacyRouter( self, event ): + "Add a new switch to our canvas." + self.newNode( 'LegacyRouter', event ) + + def clickLegacySwitch( self, event ): + "Add a new switch to our canvas." + self.newNode( 'LegacySwitch', event ) + def clickSwitch( self, event ): "Add a new switch to our canvas." self.newNode( 'Switch', event ) - def dragLink( self, event ): + def dragNetLink( self, event ): "Drag a link's endpoint to another node." if self.link is None: return @@ -347,7 +2002,7 @@ class MiniEdit( Frame ): c = self.canvas c.coords( self.link, self.linkx, self.linky, x, y ) - def releaseLink( self, _event ): + def releaseNetLink( self, _event ): "Give up on the current link." if self.link is not None: self.canvas.delete( self.link ) @@ -362,8 +2017,7 @@ class MiniEdit( Frame ): '': self.dragNode, '': self.releaseNode, '': self.enterNode, - '': self.leaveNode, - '': self.xterm + '': self.leaveNode } l = Label() # lightweight-ish owner for bindings for event, binding in bindings.items(): @@ -385,7 +2039,7 @@ class MiniEdit( Frame ): def clickNode( self, event ): "Node click handler." - if self.active is 'Link': + if self.active is 'NetLink': self.startLink( event ) else: self.selectNode( event ) @@ -393,14 +2047,14 @@ class MiniEdit( Frame ): def dragNode( self, event ): "Node drag handler." - if self.active is 'Link': - self.dragLink( event ) + if self.active is 'NetLink': + self.dragNetLink( event ) else: self.dragNodeAround( event ) def releaseNode( self, event ): "Node release handler." - if self.active is 'Link': + if self.active is 'NetLink': self.finishLink( event ) # Specific node handlers @@ -427,21 +2081,10 @@ class MiniEdit( Frame ): 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 @@ -452,17 +2095,59 @@ class MiniEdit( Frame ): 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='red' ) + #self.selectItem( None ) + + self.canvas.tag_bind( self.link, '', highlight ) + self.canvas.tag_bind( self.link, '', unhighlight ) + self.canvas.tag_bind( self.link, '', select ) + + def createDataLinkBindings( 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='blue' ) - # self.selectItem( None ) + #self.selectItem( None ) self.canvas.tag_bind( self.link, '', highlight ) self.canvas.tag_bind( self.link, '', unhighlight ) self.canvas.tag_bind( self.link, '', select ) + self.canvas.tag_bind( self.link, '', 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" @@ -476,17 +2161,48 @@ class MiniEdit( Frame ): dest = self.itemToWidget.get( target, None ) if ( source is None or dest is None or source == dest or dest in source.links or source in dest.links ): - self.releaseLink( event ) + self.releaseNetLink( event ) return # 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: - self.releaseLink( event ) + if (('Host' in stags and 'Host' in dtags) or + ('Controller' in dtags and 'LegacyRouter' in stags) or + ('Controller' in stags and 'LegacyRouter' in dtags) or + ('Controller' in dtags and 'LegacySwitch' in stags) or + ('Controller' in stags and 'LegacySwitch' in dtags) or + ('Controller' in dtags and 'Host' in stags) or + ('Controller' in stags and 'Host' in dtags) or + ('Controller' in stags and 'Controller' in dtags)): + self.releaseNetLink( 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 +2216,22 @@ class MiniEdit( Frame ): 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 , April 2010' + version = 'MiniEdit '+MINIEDIT_VERSION + author = 'Originally by: Bob Lantz , 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 +2242,542 @@ class MiniEdit( Frame ): 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 is not None: + 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 or 'LegacySwitch' 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: ] ) + #print name+' has '+str(tags) + 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 + elif self.appPrefs['switchType'] == 'user': + switchClass = CustomUserSwitch + elif self.appPrefs['switchType'] == 'userns': + switchParms['inNamespace'] = True + switchClass = CustomUserSwitch + else: + switchClass = customOvs + elif opts['switchType'] == 'user': + switchClass = CustomUserSwitch + elif opts['switchType'] == 'userns': + switchClass = CustomUserSwitch + switchParms['inNamespace'] = True + elif opts['switchType'] == 'ivs': + switchClass = IVSSwitch + else: + switchClass = customOvs + newSwitch = net.addSwitch( name , cls=switchClass, **switchParms) + if switchClass == CustomUserSwitch: + if ('switchIP' in opts): + if (len(opts['switchIP']) > 0): + newSwitch.setSwitchIP(opts['switchIP']) + 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 'LegacySwitch' in tags: + newSwitch = net.addSwitch( name , cls=LegacySwitch) + elif 'LegacyRouter' in tags: + newSwitch = net.addHost( name , cls=LegacyRouter) 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 + hostCls = Host + if 'cores' in opts or 'cpu' in opts: + hostCls=CPULimitedHost + newHost = net.addHost( name, + cls=hostCls, + 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 ] + if linkopts: + net.addLink(src, dst, cls=TCLink, **linkopts) + else: + net.addLink(src, dst) + 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, + 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 ) + if 'LegacySwitch' in tags: + self.net.get(name).start( [] ) + info( name + ' ') + info('\n') + + self.postStartSetup() def stop( self ): "Stop network." @@ -593,6 +2786,78 @@ class MiniEdit( Frame ): 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_legacyRouterPopup(self, event): + # display the popup menu + if ( self.net is not None ): + try: + self.legacyRouterRunPopup.tk_popup(event.x_root, event.y_root, 0) + finally: + # make sure to release the grab (Tk 8.0a1 only) + self.legacyRouterRunPopup.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_legacySwitchPopup(self, event): + # display the popup menu + if ( self.net is not None ): + 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 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 +2867,193 @@ class MiniEdit( Frame ): 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(MININET_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('', 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('', 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('', 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 +3066,133 @@ def miniEditImages(): 'Select': BitmapImage( file='/usr/include/X11/bitmaps/left_ptr' ), + 'Switch': PhotoImage( data=r""" +R0lGODlhLgAgAPcAAB2ZxGq61imex4zH3RWWwmK41tzd3vn9/jCiyfX7/Q6SwFay0gBlmtnZ2snJ +yr+2tAuMu6rY6D6kyfHx8XO/2Uqszjmly6DU5uXz+JLN4uz3+kSrzlKx0ZeZm2K21BuYw67a6QB9 +r+Xl5rW2uHW61On1+UGpzbrf6xiXwny9166vsMLCwgBdlAmHt8TFxgBwpNTs9C2hyO7t7ZnR5L/B +w0yv0NXV1gBimKGjpABtoQBuoqKkpiaUvqWmqHbB2/j4+Pf39729vgB/sN7w9obH3hSMugCAsonJ +4M/q8wBglgB6rCCaxLO0tX7C2wBqniGMuABzpuPl5f3+/v39/fr6+r7i7vP6/ABonV621LLc6zWk +yrq6uq6wskGlyUaszp6gohmYw8HDxKaoqn3E3LGztWGuzcnLzKmrrOnp6gB1qCaex1q001ewz+Dg +4QB3qrCxstHS09LR0dHR0s7Oz8zNzsfIyQaJuQB0pozL4YzI3re4uAGFtYDG3hOUwb+/wQB5rOvr +6wB2qdju9TWfxgBpniOcxeLj48vn8dvc3VKuzwB2qp6fos/Q0aXV6D+jxwB7rsXHyLu8vb27vCSc +xSGZwxyZxH3A2RuUv0+uzz+ozCedxgCDtABnnABroKutr/7+/n2/2LTd6wBvo9bX2OLo6lGv0C6d +xS6avjmmzLTR2uzr6m651RuXw4jF3CqfxySaxSadyAuRv9bd4cPExRiMuDKjyUWevNPS0sXl8BeY +xKytr8G/wABypXvC23vD3O73+3vE3cvU2PH5+7S1t7q7vCGVwO/v8JfM3zymyyyZwrWys+Hy90Ki +xK6qqg+TwBKXxMvMzaWtsK7U4jemzLXEygBxpW++2aCho97Z18bP0/T09fX29vb19ViuzdDR0crf +51qz01y00ujo6Onq6hCDs2Gpw3i71CqWv3S71nO92M/h52m207bJ0AN6rPPz9Nrh5Nvo7K/b6oTI +37Td7ABqneHi4yScxo/M4RiWwRqVwcro8n3B2lGoylStzszMzAAAACH5BAEAAP8ALAAAAAAuACAA +Bwj/AP8JHEjw3wEkEY74WOjrQhUNBSNKnCjRSoYKCOwJcKWpEAACBFBRGEKxZMkDjRAg2OBlQyYL +WhDEcOWxDwofv0zqHIhhDYIFC2p4MYFMS62ZaiYVWlJJAYIqO00KMlEjABYOQokaRbp0CYBKffpE +iDpxSKYC1gqswToUmYVaCFyp6QrgwwcCscaSJZhgQYBeAdRyqFBhgwWkGyct8WoXRZ8Ph/YOxMOB +CIUAHsBxwGQBAII1YwpMI5Brcd0PKFA4Q2ZFMgYteZqkwxyu1KQNJzQc+CdFCrxypyqdRoEPX6x7 +ki/n2TfbAxtNRHYTVCWpWTRbuRoX7yMgZ9QSFQa0/7LU/BXygjIWXVOBTR2sxp7BxGpENgKbY+PR +reqyIOKnOh0M445AjTjDCgrPSBNFKt9w8wMVU5g0Bg8kDAAKOutQAkNEQNBwDRAEeVEcAV6w84Ay +KowQSRhmzNGAASIAYow2IP6DySPk8ANKCv1wINE2cpjxCUEgOIOPAKicQMMbKnhyhhg97HDNF4vs +IEYkNkzwjwSP/PHIE2VIgIdEnxjAiBwNGIKGDKS8I0sw2VAzApNOQimGLlyMAIkDw2yhZTF/KKGE +lxCEMtEPBtDhACQurLDCLkFIsoUeZLyRpx8OmEGHN3AEcU0HkFAhUDFulDroJvOU5M44iDjgDTQO +1P/hzRw2IFJPGw3AAY0LI/SAwxc7jEKQI2mkEUipRoxp0g821AMIGlG0McockMzihx5c1LkDDmSg +UVAiafACRbGPVKDTFG3MYUYdLoThRxDE6DEMGUww8eQONGwTER9piFINFOPasaFJVIjTwC1xzOGP +A3HUKoIMDTwJR4QRgdBOJzq8UM0Lj5QihU5ZdGMOCSSYUwYzAwwkDhNtUKTBOZ10koMOoohihDwm +HZKPEDwb4fMe9An0g5Yl+SDKFTHnkMMLLQAjXUTxUCLEIyH0bIQAwuxVQhEMcEIIIUmHUEsWGCQg +xQEaIFGAHV0+QnUIIWwyg2T/3MPLDQwwcAUhTjiswYsQl1SAxQKmbBJCIMe6ISjVmXwsWQKJEJJE +3l1/TY8O4wZyh8ZQ3IF4qX9cggTdAmEwCAMs3IB311fsDfbMGv97BxSBQBAP6QMN0QUhLCSRhOp5 +e923zDpk/EIaRdyO+0C/eHBHEiz0vjrrfMfciSKD4LJ8RBEk88IN0ff+O/CEVEPLGK1tH1ECM7Dx +RDWdcMLJFTpUQ44jfCyjvlShZNDE/0QAgT6ypr6AAAA7 + """), + + 'LegacySwitch': PhotoImage( data=r""" +R0lGODlhMgAYAPcAAAEBAXmDjbe4uAE5cjF7xwFWq2Sa0S9biSlrrdTW1k2Ly02a5xUvSQFHjmep +6bfI2Q5SlQIYLwFfvj6M3Jaan8fHyDuFzwFp0Vah60uU3AEiRhFgrgFRogFr10N9uTFrpytHYQFM +mGWt9wIwX+bm5kaT4gtFgR1cnJPF9yt80CF0yAIMGHmp2c/P0AEoUb/P4Fei7qK4zgpLjgFkyQlf +t1mf5jKD1WWJrQ86ZwFAgBhYmVOa4MPV52uv8y+A0iR3ywFbtUyX5ECI0Q1UmwIcOUGQ3RBXoQI0 +aRJbpr3BxVeJvQUJDafH5wIlS2aq7xBmv52lr7fH12el5Wml3097ph1ru7vM3HCz91Ke6lid40KQ +4GSQvgQGClFnfwVJjszMzVCX3hljrdPT1AFLlBRnutPf6yd5zjeI2QE9eRBdrBNVl+3v70mV4ydf +lwMVKwErVlul8AFChTGB1QE3bsTFxQImTVmAp0FjiUSM1k+b6QQvWQ1SlxMgLgFixEqU3xJhsgFT +pn2Xs5OluZ+1yz1Xb6HN+Td9wy1zuYClykV5r0x2oeDh4qmvt8LDwxhuxRlLfyRioo2124mft9bi +71mDr7fT79nl8Z2hpQs9b7vN4QMQIOPj5XOPrU2Jx32z6xtvwzeBywFFikFnjwcPFa29yxJjuFmP +xQFv3qGxwRc/Z8vb6wsRGBNqwqmpqTdvqQIbNQFPngMzZAEfP0mQ13mHlQFYsAFnznOXu2mPtQxj +vQ1Vn4Ot1+/x8my0/CJgnxNNh8DT5CdJaWyx+AELFWmt8QxPkxBZpwMFB015pgFduGCNuyx7zdnZ +2WKm6h1xyOPp8aW70QtPkUmM0LrCyr/FyztljwFPm0OJzwFny7/L1xFjswE/e12i50iR2VR8o2Gf +3xszS2eTvz2BxSlloQdJiwMHDzF3u7bJ3T2I1WCp8+Xt80FokQFJklef6mORw2ap7SJ1y77Q47nN +3wFfu1Kb5cXJyxdhrdDR0wlNkTSF11Oa4yp4yQEuW0WQ3QIDBQI7dSH5BAEAAAAALAAAAAAyABgA +Bwj/AAEIHDjKF6SDvhImPMHwhA6HOiLqUENRDYSLEIplxBcNHz4Z5GTI8BLKS5OBA1Ply2fDhxwf +PlLITGFmmRkzP+DlVKHCmU9nnz45csSqKKsn9gileZKrVC4aRFACOGZu5UobNuRohRkzhc2b+36o +qCaqrFmzZEV1ERBg3BOmMl5JZTBhwhm7ZyycYZnvJdeuNl21qkCHTiPDhxspTtKoQgUKCJ6wehMV +5QctWupeo6TkjOd8e1lmdQkTGbTTMaDFiDGINeskX6YhEicUiQa5A/kUKaFFwQ0oXzjZ8Tbcm3Hj +irwpMtTSgg9QMJf5WEZ9375AiED19ImpSQSUB4Kw/8HFSMyiRWJaqG/xhf2X91+oCbmq1e/MFD/2 +EcApVkWVJhp8J9AqsywQxDfAbLJJPAy+kMkL8shjxTkUnhOJZ5+JVp8cKfhwxwdf4fQLgG4MFAwW +KOZRAxM81EAPPQvoE0QQfrDhx4399OMBMjz2yCMVivCoCAWXKLKMTPvoUYcsKwi0RCcwYCAlFjU0 +A6OBM4pXAhsl8FYELYWFWZhiZCbRQgIC2AGTLy408coxAoEDx5wwtGPALTVg0E4NKC7gp4FsBKoA +Ki8U+oIVmVih6DnZPMBMAlGwIARWOLiggSYC+ZNIOulwY4AkSZCyxaikbqHMqaeaIp4+rAaxQxBg +2P+IozuRzvLZIS4syYVAfMAhwhSC1EPCGoskIIYY9yS7Hny75OFnEIAGyiVvWkjjRxF11fXIG3WU +KNA6wghDTCW88PKMJZOkm24Z7LarSjPtoIjFn1lKyyVmmBVhwRtvaDDMgFL0Eu4VhaiDwhXCXNFD +D8QQw7ATEDsBw8RSxotFHs7CKJ60XWrRBj91EOGPQCA48c7J7zTjSTPctOzynjVkkYU+O9S8Axg4 +Z6BzBt30003Ps+AhNB5C4PCGC5gKJMMTZJBRytOl/CH1HxvQkMbVVxujtdZGGKGL17rsEfYQe+xR +zNnFcGQCv7LsKlAtp8R9Sgd0032BLXjPoPcMffTd3YcEgAMOxOBA1GJ4AYgXAMjiHDTgggveCgRI +3RfcnffefgcOeDKEG3444osDwgEspMNiTQhx5FoOShxcrrfff0uQjOycD+554qFzMHrpp4cwBju/ +5+CmVNbArnntndeCO+O689777+w0IH0o1P/TRJMohRA4EJwn47nyiocOSOmkn/57COxE3wD11Mfh +fg45zCGyVF4Ufvvyze8ewv5jQK9++6FwXxzglwM0GPAfR8AeSo4gwAHCbxsQNCAa/kHBAVhwAHPI +4BE2eIRYeHAEIBwBP0Y4Qn41YWRSCQgAOw== + """), + + 'LegacyRouter': PhotoImage( data=r""" +R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+3QFq1DmL3wJMmAMzZZW11dnZ +2SFrtyNdmTSO6gIZMUKa8gJVqEOHzR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq +6ymF4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9vwNgvwJZsX+69gsXJQFH +jTtjizF0tvHx8VOm9z2V736Dhz2N3QM2acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtg +tktjfQFu3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh312Gt+VGm/AQIDTmB +yAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i56gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia +7DpeggFt2QNPm97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er/yVVhwJJktPh +70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVVhQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18 +zKfP9wwcLAMHCwFFiS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh7cve8pG/ +7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR1RMjNTF3vU2X4TZupwRSolNne4nB+T+L +2YGz4zJ/zYe99YGHjRdDcT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6WlpW2t +7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90uvPz8wIVKBp42SV5zbfT7wtXpStV +fwFWrBVvyTt3swFz5kGBv2+1/QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t +u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T5yH5BAEAAAAALAAAAAAyABgA +Bwj/AAEIHEiQYJY7Qwg9UsTplRIbENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4c +HeoIabJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPATqEBoRB9gVJsxRlhPwHI +0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkS +rtwADuxCG/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmDjhTMmseoKQIFDx7R +oxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq +9dGvv09RHFhcIUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p9HEUFhxgMSAv +jbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CF +oVggmEgCyRf01WcFCYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57AgyZckpKKP +GFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemMIQggeaJywSQ/wgHOAmJskQEfWqBlFBEH +1P/QaGY3QOpDZXA2+A6m7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlBpZdi +isd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtChRmVPNWgpr+Be+Nc9icARww9TkIEu +DAsQ0O7DzGIQzD2QdDEJHTsIAROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB +xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE42Q9jtFIp8z0Dy1jQMA1AGzi +z9VoW7310V0znYDTGMQgwUDXLDBO2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOM +LQkcjvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZMDHKCTwI8EcQFHBBAAFc +gGPLHwLwcMIo12Qxu0ABAQA7 + """), + + '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 @@ -644,7 +3221,7 @@ def miniEditImages(): C8cSBBAQADs= """ ), - 'Switch': PhotoImage( data=r""" + 'OldSwitch': PhotoImage( data=r""" R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A @@ -671,7 +3248,7 @@ def miniEditImages(): 6saLWLNq3cq1q9evYB0GBAA7 """ ), - 'Link': PhotoImage( data=r""" + 'NetLink': PhotoImage( data=r""" R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A @@ -698,7 +3275,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() diff --git a/examples/nat.py b/examples/nat.py index fcad85c..dada363 100755 --- a/examples/nat.py +++ b/examples/nat.py @@ -59,13 +59,13 @@ def fixNetworkManager( root, intf ): cfile = '/etc/network/interfaces' line = '\niface %s inet manual\n' % intf config = open( cfile ).read() - if ( line ) not in config: + if line not in config: print '*** Adding', line.strip(), 'to', cfile with open( cfile, 'a' ) as f: f.write( line ) - # Probably need to restart network-manager to be safe - - # hopefully this won't disconnect you - root.cmd( 'service network-manager restart' ) + # Probably need to restart network-manager to be safe - + # hopefully this won't disconnect you + root.cmd( 'service network-manager restart' ) def connectToInternet( network, switch='s1', rootip='10.254', subnet='10.0/8'): """Connect the network to the internet diff --git a/examples/numberedports.py b/examples/numberedports.py new file mode 100755 index 0000000..8220550 --- /dev/null +++ b/examples/numberedports.py @@ -0,0 +1,76 @@ +#!/usr/bin/python + +""" +Create a network with 5 hosts, numbered 1-4 and 9. +Validate that the port numbers match to the interface name, +and that the ovs ports match the mininet ports. +""" + +from mininet.net import Mininet +from mininet.node import Controller +from mininet.log import setLogLevel, info, warn +from mininet.node import Node + +def validatePort( switch, intf ): + "Validate intf's OF port number" + ofport = int( switch.cmd( 'ovs-vsctl get Interface', intf, + 'ofport' ) ) + if ofport != switch.ports[ intf ]: + warn( 'WARNING: ofport for', intf, 'is actually', ofport, '\n' ) + return 0 + else: + return 1 + +def net(): + + "Create a network with 5 hosts." + + net = Mininet( controller=Controller ) + + info( '*** Adding controller\n' ) + net.addController( 'c0' ) + + info( '*** Adding hosts\n' ) + h1 = net.addHost( 'h1', ip='10.0.0.1' ) + h2 = net.addHost( 'h2', ip='10.0.0.2' ) + h3 = net.addHost( 'h3', ip='10.0.0.3' ) + h4 = net.addHost( 'h4', ip='10.0.0.4' ) + h5 = net.addHost( 'h5', ip='10.0.0.5' ) + + info( '*** Adding switch\n' ) + s1 = net.addSwitch( 's1' ) + + info( '*** Creating links\n' ) + # host 1-4 connect to ports 1-4 on the switch + net.addLink( h1, s1 ) + net.addLink( h2, s1 ) + net.addLink( h3, s1 ) + net.addLink( h4, s1 ) + net.addLink( h5, s1, port1 = 1, port2 = 9 ) # specify a different port to connect host 5 to on the switch. + + root = Node( 'root', inNamespace=False ) + info( '*** Starting network\n' ) + net.start() + + # print the interfaces and their port numbers + info( '\n*** printing and validating the ports running on each interface\n' ) + for intfs in s1.intfList(): + if not intfs.name == "lo": + info( intfs, ': ', s1.ports[intfs], + '\n' ) + info ( 'Validating that', intfs, 'is actually on port', s1.ports[intfs], '... ' ) + if validatePort( s1, intfs ): + info( 'Validated.\n' ) + print '\n' + + # test the network with pingall + net.pingAll() + print '\n' + + info( '*** Stopping network' ) + net.stop() + +if __name__ == '__main__': + setLogLevel( 'info' ) + net() + diff --git a/examples/sshd.py b/examples/sshd.py index cef1d87..27166cb 100755 --- a/examples/sshd.py +++ b/examples/sshd.py @@ -55,7 +55,7 @@ def sshd( network, cmd='/usr/sbin/sshd', opts='-D', if not switch: switch = network[ 's1' ] # switch to use if not routes: - routes = [ '10.0.0.0/24' ] + routes = [ '10.0.0.0/24' ] connectToRootNS( network, switch, ip, routes ) for host in network.hosts: host.cmd( cmd + ' ' + opts + '&' ) diff --git a/examples/test/test_hwintf.py b/examples/test/test_hwintf.py index 20d08d3..aafd643 100755 --- a/examples/test/test_hwintf.py +++ b/examples/test/test_hwintf.py @@ -5,12 +5,14 @@ Test for hwintf.py """ import unittest -import pexpect import re + +import pexpect + from mininet.log import setLogLevel -from mininet.net import Mininet from mininet.node import Node -from mininet.link import Link, Intf +from mininet.link import Link + class testHwintf( unittest.TestCase ): diff --git a/examples/test/test_numberedports.py b/examples/test/test_numberedports.py new file mode 100755 index 0000000..b565d3e --- /dev/null +++ b/examples/test/test_numberedports.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +""" +Test for numberedports.py +""" + +import unittest +import pexpect +from collections import defaultdict +from mininet.node import OVSSwitch + +class testNumberedports( unittest.TestCase ): + + @unittest.skipIf( OVSSwitch.setup() or OVSSwitch.isOldOVS(), "old version of OVS" ) + def testConsistency( self ): + """verify consistency between mininet and ovs ports""" + p = pexpect.spawn( 'python -m mininet.examples.numberedports' ) + opts = [ 'Validating that s1-eth\d is actually on port \d ... Validated.', + 'Validating that s1-eth\d is actually on port \d ... WARNING', + pexpect.EOF ] + correct_ports = True + count = 0 + while True: + index = p.expect( opts ) + if index == 0: + count += 1 + elif index == 1: + correct_ports = False + elif index == 2: + self.assertNotEqual( 0, count ) + break + self.assertTrue( correct_ports ) + + def testNumbering( self ): + """verify that all of the port numbers are printed correctly and consistent with their interface""" + p = pexpect.spawn( 'python -m mininet.examples.numberedports' ) + opts = [ 's1-eth(\d+) : (\d+)', + pexpect.EOF ] + count_intfs = 0 + while True: + index = p.expect( opts ) + if index == 0: + count_intfs += 1 + intfport = p.match.group( 1 ) + ofport = p.match.group( 2 ) + self.assertEqual( intfport, ofport ) + elif index == 1: + break + self.assertNotEqual( 0, count_intfs ) + +if __name__ == '__main__': + unittest.main() diff --git a/examples/test/test_simpleperf.py b/examples/test/test_simpleperf.py index 7d44c9c..d88da67 100755 --- a/examples/test/test_simpleperf.py +++ b/examples/test/test_simpleperf.py @@ -24,7 +24,7 @@ class testSimplePerf( unittest.TestCase ): # check ping results p.expect( "Results: (\d+)% dropped", timeout=120 ) loss = int( p.match.group( 1 ) ) - self.assertTrue( loss > 0 and loss < 100 ) + self.assertTrue( 0 < loss < 100 ) # check iperf results p.expect( "Results: \['([\d\.]+) .bits/sec", timeout=480 ) bw = float( p.match.group( 1 ) ) @@ -46,7 +46,7 @@ class testSimplePerf( unittest.TestCase ): m = re.search( expectStr, output ) loss = int( m.group( 3 ) ) net.stop() - self.assertTrue( loss > 0 and loss < 100 ) + self.assertTrue( 0 < loss < 100 ) if __name__ == '__main__': setLogLevel( 'warning' ) diff --git a/examples/test/test_sshd.py b/examples/test/test_sshd.py index eae65e8..9cfad6d 100755 --- a/examples/test/test_sshd.py +++ b/examples/test/test_sshd.py @@ -6,7 +6,6 @@ Test for sshd.py import unittest import pexpect -from time import sleep from mininet.clean import sh class testSSHD( unittest.TestCase ): diff --git a/mininet/clean.py b/mininet/clean.py index 5162bf3..49e32ea 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -10,7 +10,7 @@ It may also get rid of 'false positives', but hopefully nothing irreplaceable! """ -from subprocess import Popen, PIPE +from subprocess import Popen, PIPE, check_output as co import time from mininet.log import info @@ -47,21 +47,40 @@ def cleanup(): cleanUpScreens() info( "*** Removing excess kernel datapaths\n" ) - dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).split( '\n' ) + dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).splitlines() for dp in dps: - if dp != '': + if dp: sh( 'dpctl deldp ' + dp ) info( "*** Removing OVS datapaths" ) - dps = sh("ovs-vsctl --timeout=1 list-br").split( '\n' ) + dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines() + if dps: + sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp + for dp in dps if dp ) ) + # And in case the above didn't work... + dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines() for dp in dps: - if dp: - sh( 'ovs-vsctl del-br ' + dp ) + sh( 'ovs-vsctl del-br ' + dp ) info( "*** Removing all links of the pattern foo-ethX\n" ) - links = sh( r"ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' ) + links = sh( "ip link show | " + "egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).splitlines() for link in links: - if link != '': + if link: sh( "ip link del " + link ) + info( "*** Killing stale mininet node processes\n" ) + sh( 'pkill -9 -f mininet:' ) + # Make sure they are gone + while True: + try: + pids = co( 'pgrep -f mininet:'.split() ) + except: + pids = '' + if pids: + sh( 'pkill -f 9 mininet:' ) + sleep( .5 ) + else: + break + info( "*** Cleanup complete.\n" ) diff --git a/mininet/cli.py b/mininet/cli.py index 3ca190f..42a4d3d 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -31,6 +31,8 @@ from os import isatty from select import poll, POLLIN import sys import time +import os +import atexit from mininet.log import info, output, error from mininet.term import makeTerms, runX11 @@ -52,6 +54,18 @@ class CLI( Cmd ): self.inputFile = script Cmd.__init__( self ) info( '*** Starting CLI:\n' ) + + # Set up history if readline is available + try: + import readline + except ImportError: + pass + else: + history_path = os.path.expanduser('~/.mininet_history') + if os.path.isfile(history_path): + readline.read_history_file(history_path) + atexit.register(lambda: readline.write_history_file(history_path)) + if self.inputFile: self.do_source( self.inputFile ) return @@ -63,7 +77,7 @@ class CLI( Cmd ): node.sendInt() node.monitor() if self.isatty(): - quietRun( 'stty sane' ) + quietRun( 'stty echo sane intr "^C"' ) self.cmdloop() break except KeyboardInterrupt: @@ -151,16 +165,16 @@ class CLI( Cmd ): # pylint: enable-msg=W0703,W0122 - def do_pingall( self, _line ): + def do_pingall( self, line ): "Ping between all hosts." - self.mn.pingAll() + self.mn.pingAll( line ) def do_pingpair( self, _line ): "Ping between first two hosts, useful for testing." self.mn.pingPair() def do_pingallfull( self, _line ): - "Ping between first two hosts, returns all ping results." + "Ping between all hosts, returns all ping results." self.mn.pingAllFull() def do_pingpairfull( self, _line ): @@ -187,7 +201,7 @@ class CLI( Cmd ): error( 'invalid number of args: iperf src dst\n' ) def do_iperfudp( self, line ): - "Simple iperf TCP test between two (optionally specified) hosts." + "Simple iperf UDP test between two (optionally specified) hosts." args = line.split() if not args: self.mn.iperf( l4Type='UDP' ) @@ -297,6 +311,7 @@ class CLI( Cmd ): break except IOError: error( 'error reading file %s\n' % args[ 0 ] ) + self.inputFile.close() self.inputFile = None def do_dpctl( self, line ): @@ -331,13 +346,13 @@ class CLI( Cmd ): node = self.mn[ first ] rest = args.split( ' ' ) # Substitute IP addresses for node names in command - rest = [ self.mn[ arg ].defaultIntf().updateIP() + # If updateIP() returns None, then use node name + rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg if arg in self.mn else arg for arg in rest ] rest = ' '.join( rest ) # Run cmd on node: - builtin = isShellBuiltin( first ) - node.sendCmd( rest, printPid=( not builtin ) ) + node.sendCmd( rest ) self.waitForNode( node ) else: error( '*** Unknown command: %s\n' % line ) @@ -345,7 +360,7 @@ class CLI( Cmd ): # pylint: enable-msg=R0201 def waitForNode( self, node ): - "Wait for a node to finish, and print its output." + "Wait for a node to finish, and print its output." # Pollers nodePoller = poll() nodePoller.register( node.stdout ) @@ -363,7 +378,7 @@ class CLI( Cmd ): if False and self.inputFile: key = self.inputFile.read( 1 ) if key is not '': - node.write(key) + node.write( key ) else: self.inputFile = None if isReadable( self.inPoller ): @@ -375,8 +390,12 @@ class CLI( Cmd ): if not node.waiting: break except KeyboardInterrupt: + # There is an at least one race condition here, since + # it's possible to interrupt ourselves after we've + # read data but before it has been printed. node.sendInt() + # Helper functions def isReadable( poller ): diff --git a/mininet/log.py b/mininet/log.py index a046f50..5f96569 100644 --- a/mininet/log.py +++ b/mininet/log.py @@ -57,7 +57,7 @@ class StreamHandlerNoNewline( logging.StreamHandler ): class Singleton( type ): """Singleton pattern from Wikipedia - See http://en.wikipedia.org/wiki/SingletonPattern#Python + See http://en.wikipedia.org/wiki/Singleton_Pattern Intended to be used as a __metaclass_ param, as shown for the class below.""" @@ -69,7 +69,7 @@ class Singleton( type ): def __call__( cls, *args, **kw ): if cls.instance is None: cls.instance = super( Singleton, cls ).__call__( *args, **kw ) - return cls.instance + return cls.instance class MininetLogger( Logger, object ): diff --git a/mininet/net.py b/mininet/net.py index 1912fba..42ab0a7 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -90,29 +90,30 @@ import os import re import select import signal +import copy from time import sleep -from itertools import chain +from itertools import chain, groupby from mininet.cli import CLI -from mininet.log import info, error, debug, output -from mininet.node import Host, OVSKernelSwitch, Controller, NAT +from mininet.log import info, error, debug, output, warn +from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller, NAT from mininet.link import Link, Intf from mininet.util import quietRun, fixLimits, numCores, ensureRoot from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd from mininet.term import cleanUpScreens, makeTerms # Mininet version: should be consistent with README and LICENSE -VERSION = "2.1.0" +VERSION = "2.1.0+" class Mininet( object ): "Network emulation with hosts spawned in network namespaces." def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, - controller=Controller, link=Link, intf=Intf, + controller=DefaultController, link=Link, intf=Intf, build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', inNamespace=False, autoSetMacs=False, autoStaticArp=False, autoPinCpus=False, - listenPort=None ): + listenPort=None, waitConnected=False ): """Create Mininet object. topo: Topo (topology) object or None switch: default Switch class @@ -148,6 +149,7 @@ class Mininet( object ): self.numCores = numCores() self.nextCore = 0 # next core for pinning hosts to CPUs self.listenPort = listenPort + self.waitConn = waitConnected self.hosts = [] self.switches = [] @@ -163,6 +165,37 @@ class Mininet( object ): if topo and build: self.build() + + def waitConnected( self, timeout=None, delay=.5 ): + """wait for each switch to connect to a controller, + up to 5 seconds + timeout: time to wait, or None to wait indefinitely + delay: seconds to sleep per iteration + returns: True if all switches are connected""" + info( '*** Waiting for switches to connect\n' ) + time = 0 + remaining = list( self.switches ) + while True: + for switch in tuple( remaining ): + if switch.connected(): + info( '%s ' % switch ) + remaining.remove( switch ) + if not remaining: + info( '\n' ) + return True + if time > timeout and timeout is not None: + break + sleep( delay ) + time += delay + warn( 'Timed out after %d seconds\n' % time ) + for switch in remaining: + if not switch.connected(): + warn( 'Warning: %s is not connected to a controller\n' + % switch.name ) + else: + remaining.remove( switch ) + return not remaining + def addHost( self, name, cls=None, **params ): """Add host. name: name of host to add @@ -213,7 +246,7 @@ class Mininet( object ): if not controller: controller = self.controller # Construct new controller if one is not given - if isinstance(name, Controller): + if isinstance( name, Controller ): controller_new = name # Pylint thinks controller is a str() # pylint: disable=E1103 @@ -222,7 +255,7 @@ class Mininet( object ): else: controller_new = controller( name, **params ) # Add new controller to net - if controller_new: # allow controller-less setups + if controller_new: # allow controller-less setups self.controllers.append( controller_new ) self.nameToNode[ name ] = controller_new return controller_new @@ -338,7 +371,11 @@ class Mininet( object ): if type( classes ) is not list: classes = [ classes ] for i, cls in enumerate( classes ): - self.addController( 'c%d' % i, cls ) + # Allow Controller objects because nobody understands currying + if isinstance( cls, Controller ): + self.addController( cls ) + else: + self.addController( 'c%d' % i, cls ) info( '*** Adding hosts:\n' ) for hostName in topo.hosts(): @@ -369,7 +406,7 @@ class Mininet( object ): "Build mininet." if self.topo: self.buildFromTopo( self.topo ) - if ( self.inNamespace ): + if self.inNamespace: self.configureControlNetwork() info( '*** Configuring hosts\n' ) self.configHosts() @@ -415,13 +452,23 @@ class Mininet( object ): info( switch.name + ' ') switch.start( self.controllers ) info( '\n' ) + if self.waitConn: + self.waitConnected() def stop( self ): "Stop the controller(s), switches and hosts" + info( '*** Stopping %i controllers\n' % len( self.controllers ) ) + for controller in self.controllers: + info( controller.name + ' ' ) + controller.stop() + info( '\n' ) if self.terms: info( '*** Stopping %i terms\n' % len( self.terms ) ) self.stopXterms() info( '*** Stopping %i switches\n' % len( self.switches ) ) + for swclass, switches in groupby( sorted( self.switches, key=type ), type ): + if hasattr( swclass, 'batchShutdown' ): + swclass.batchShutdown( switches ) for switch in self.switches: info( switch.name + ' ' ) switch.stop() @@ -430,11 +477,6 @@ class Mininet( object ): for host in self.hosts: info( host.name + ' ' ) host.terminate() - info( '\n' ) - info( '*** Stopping %i controllers\n' % len( self.controllers ) ) - for controller in self.controllers: - info( controller.name + ' ' ) - controller.stop() info( '\n*** Done\n' ) def run( self, test, *args, **kwargs ): @@ -477,13 +519,13 @@ class Mininet( object ): "Parse ping output and return packets sent, received." # Check for downed link if 'connect: Network is unreachable' in pingOutput: - return (1, 0) + return 1, 0 r = r'(\d+) packets transmitted, (\d+) received' m = re.search( r, pingOutput ) if m is None: error( '*** Error: could not parse ping output: %s\n' % pingOutput ) - return (1, 0) + return 1, 0 sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) return sent, received @@ -518,7 +560,7 @@ class Mininet( object ): output( ( '%s ' % dest.name ) if received else 'X ' ) output( '\n' ) if packets > 0: - ploss = 100 * lost / packets + ploss = 100.0 * lost / packets received = packets - lost output( "*** Results: %i%% dropped (%d/%d received)\n" % ( ploss, received, packets ) ) @@ -589,10 +631,10 @@ class Mininet( object ): (rttmin, rttavg, rttmax, rttdev) ) return all_outputs - def pingAll( self ): + def pingAll( self, timeout=None ): """Ping between all hosts. returns: ploss packet loss percentage""" - return self.ping() + return self.ping( timeout=timeout ) def pingPair( self ): """Ping between first two hosts, useful for testing. @@ -627,7 +669,7 @@ class Mininet( object ): # XXX This should be cleaned up - def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ): + def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ): """Run iperf between two hosts. hosts: list of hosts; if None, uses opposite hosts l4Type: string, one of [ TCP, UDP ] @@ -650,6 +692,8 @@ class Mininet( object ): bwArgs = '-b ' + udpBw + ' ' elif l4Type != 'TCP': raise Exception( 'Unexpected l4 type: %s' % l4Type ) + if format: + iperfArgs += '-f %s ' %format server.sendCmd( iperfArgs + '-s', printPid=True ) servout = '' while server.lastPid is None: @@ -657,7 +701,7 @@ class Mininet( object ): if l4Type == 'TCP': while 'Connected' not in client.cmd( 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()): - output('waiting for iperf to start up...') + info( 'Waiting for iperf to start up...' ) sleep(.5) cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' + bwArgs ) diff --git a/mininet/node.py b/mininet/node.py index dba383d..7f8125d 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -16,6 +16,11 @@ Host: a virtual host. By default, a host is simply a shell; commands CPULimitedHost: a virtual host whose CPU bandwidth is limited by RT or CFS bandwidth limiting. +HostWithPrivateDirs: a virtual host that has user-specified private + directories. These may be temporary directories stored as a tmpfs, + or persistent directories that are mounted from another directory in + the root filesystem. + Switch: superclass for switch nodes. UserSwitch: a switch using the user-space switch from the OpenFlow @@ -47,6 +52,7 @@ Future enhancements: """ import os +import pty import re import signal import select @@ -59,6 +65,8 @@ from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin, numCores, retry, mountCgroups ) from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN from mininet.link import Link, Intf, TCIntf +from re import findall +from distutils.version import StrictVersion class Node( object ): """A virtual network node is simply a shell in a network namespace. @@ -118,16 +126,22 @@ class Node( object ): return # mnexec: (c)lose descriptors, (d)etach from tty, # (p)rint pid, and run in (n)amespace - opts = '-cdp' + opts = '-cd' if self.inNamespace: opts += 'n' - # bash -m: enable job control + # bash -m: enable job control, i: force interactive # -s: pass $* to shell, and make process easy to find in ps - cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ] - self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, - close_fds=True ) - self.stdin = self.shell.stdin - self.stdout = self.shell.stdout + # prompt is set to sentinel chr( 127 ) + os.environ[ 'PS1' ] = chr( 127 ) + cmd = [ 'mnexec', opts, 'bash', '--norc', '-mis', 'mininet:' + self.name ] + # Spawn a shell subprocess in a pseudo-tty, to disable buffering + # in the subprocess and insulate it from signals (e.g. SIGINT) + # received by the parent + master, slave = pty.openpty() + self.shell = Popen( cmd, stdin=slave, stdout=slave, stderr=slave, + close_fds=False ) + self.stdin = os.fdopen( master ) + self.stdout = self.stdin self.pid = self.shell.pid self.pollOut = select.poll() self.pollOut.register( self.stdout ) @@ -140,7 +154,14 @@ class Node( object ): self.lastCmd = None self.lastPid = None self.readbuf = '' + # Wait for prompt + while True: + data = self.read( 1024 ) + if data[ -1 ] == chr( 127 ): + break + self.pollOut.poll() self.waiting = False + self.cmd( 'stty -echo' ) def cleanup( self ): "Help python collect its garbage." @@ -186,7 +207,7 @@ class Node( object ): def terminate( self ): "Send kill signal to Node and clean up after it." if self.shell: - os.kill( self.pid, signal.SIGKILL ) + os.killpg( self.pid, signal.SIGKILL ) self.cleanup() def stop( self ): @@ -219,36 +240,29 @@ class Node( object ): # Replace empty commands with something harmless cmd = 'echo -n' self.lastCmd = cmd - printPid = printPid and not isShellBuiltin( cmd ) - if len( cmd ) > 0 and cmd[ -1 ] == '&': - # print ^A{pid}\n{sentinel} - cmd += ' printf "\\001%d\n\\177" $! \n' - else: - # print sentinel - cmd += '; printf "\\177"' - if printPid and not isShellBuiltin( cmd ): + if printPid and not isShellBuiltin( cmd ): + if len( cmd ) > 0 and cmd[ -1 ] == '&': + # print ^A{pid}\n so monitor() can set lastPid + cmd += ' printf "\\001%d\n" $! \n' + else: cmd = 'mnexec -p ' + cmd self.write( cmd + '\n' ) self.lastPid = None self.waiting = True - def sendInt( self, sig=signal.SIGINT ): + def sendInt( self, intr=chr( 3 ) ): "Interrupt running command." - if self.lastPid: - try: - os.kill( self.lastPid, sig ) - except OSError: - pass + self.write( intr ) - def monitor( self, timeoutms=None ): + def monitor( self, timeoutms=None, findPid=True ): """Monitor and return the output of a command. Set self.waiting to False if command has completed. timeoutms: timeout in ms or None to wait indefinitely.""" self.waitReadable( timeoutms ) data = self.read( 1024 ) # Look for PID - marker = chr( 1 ) + r'\d+\n' - if chr( 1 ) in data: + marker = chr( 1 ) + r'\d+\r\n' + if findPid and chr( 1 ) in data: markers = re.findall( marker, data ) if markers: self.lastPid = int( markers[ 0 ][ 1: ] ) @@ -725,6 +739,33 @@ class CPULimitedHost( Host ): mountCgroups() cls.inited = True +class HostWithPrivateDirs( Host ): + "Host with private directories" + + def __init__( self, name, *args, **kwargs ): + "privateDirs: list of private directory strings or tuples" + self.name = name + self.privateDirs = kwargs.pop( 'privateDirs', [] ) + Host.__init__( self, name, *args, **kwargs ) + self.mountPrivateDirs() + + def mountPrivateDirs( self ): + "mount private directories" + for directory in self.privateDirs: + if isinstance( directory, tuple ): + # mount given private directory + privateDir = directory[ 1 ] % self.__dict__ + mountPoint = directory[ 0 ] + self.cmd( 'mkdir -p %s' % privateDir ) + self.cmd( 'mkdir -p %s' % mountPoint ) + self.cmd( 'mount --bind %s %s' % + ( privateDir, mountPoint ) ) + else: + # mount temporary filesystem on directory + self.cmd( 'mkdir -p %s' % directory ) + self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory ) + + # Some important things to note: # @@ -754,27 +795,32 @@ class Switch( Node ): dpidLen = 16 # digits in dpid passed to switch def __init__( self, name, dpid=None, opts='', listenPort=None, **params): - """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1) + """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1) opts: additional switch options listenPort: port to listen on for dpctl connections""" Node.__init__( self, name, **params ) - self.dpid = dpid if dpid else self.defaultDpid() + self.dpid = self.defaultDpid( dpid ) self.opts = opts self.listenPort = listenPort if not self.inNamespace: self.controlIntf = Intf( 'lo', self, port=0 ) - def defaultDpid( self ): - "Derive dpid from switch name, s1 -> 1" - try: - dpid = int( re.findall( r'\d+', self.name )[ 0 ] ) - dpid = hex( dpid )[ 2: ] - dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid - return dpid - except IndexError: - raise Exception( 'Unable to derive default datapath ID - ' - 'please either specify a dpid or use a ' - 'canonical switch name such as s23.' ) + def defaultDpid( self, dpid=None ): + "Return correctly formatted dpid from dpid or switch name (s1 -> 1)" + if dpid: + # Remove any colons and make sure it's a good hex number + dpid = dpid.translate( None, ':' ) + assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0 + else: + # Use hex of the first number in the switch name + nums = re.findall( r'\d+', self.name ) + if nums: + dpid = hex( int( nums[ 0 ] ) )[ 2: ] + else: + raise Exception( 'Unable to derive default datapath ID - ' + 'please either specify a dpid or use a ' + 'canonical switch name such as s23.' ) + return '0' * ( self.dpidLen - len( dpid ) ) + dpid def defaultIntf( self ): "Return control interface" @@ -819,6 +865,8 @@ class UserSwitch( Switch ): '(openflow.org)' ) if self.listenPort: self.opts += ' --listen=ptcp:%i ' % self.listenPort + else: + self.opts += ' --listen=punix:/tmp/%s.listen' % self.name self.dpopts = dpopts @classmethod @@ -829,10 +877,13 @@ class UserSwitch( Switch ): def dpctl( self, *args ): "Run dpctl command" + listenAddr = None if not self.listenPort: - return "can't run dpctl without passive listening port" + listenAddr = 'unix:/tmp/%s.listen' % self.name + else: + listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort return self.cmd( 'dpctl ' + ' '.join( args ) + - ' tcp:127.0.0.1:%i' % self.listenPort ) + ' ' + listenAddr ) def connected( self ): "Is the switch connected to a controller?" @@ -849,10 +900,13 @@ class UserSwitch( Switch ): minspeed = ifspeed * 0.001 res = intf.config( **intf.params ) - parent = res['parent'] + + if res is None: # link may not have TC parameters + return # Re-add qdisc, root, and default classes user switch created, but # with new parent, as setup by Mininet's TCIntf + parent = res['parent'] intf.tc( "%s qdisc add dev %s " + parent + " handle 1: htb default 0xfffe" ) intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate " @@ -947,14 +1001,17 @@ class OVSLegacyKernelSwitch( Switch ): class OVSSwitch( Switch ): "Open vSwitch switch. Depends on ovs-vsctl." - def __init__( self, name, failMode='secure', datapath='kernel', **params ): + def __init__( self, name, failMode='secure', datapath='kernel', + inband=False, **params ): """Init. name: name for switch failMode: controller loss behavior (secure|open) - datapath: userspace or kernel mode (kernel|user)""" + datapath: userspace or kernel mode (kernel|user) + inband: use in-band control (False)""" Switch.__init__( self, name, **params ) self.failMode = failMode self.datapath = datapath + self.inband = inband @classmethod def setup( cls ): @@ -975,6 +1032,20 @@ class OVSSwitch( Switch ): 'You may wish to try ' '"service openvswitch-switch start".\n' ) exit( 1 ) + info = quietRun( 'ovs-vsctl --version' ) + cls.OVSVersion = findall( '\d+\.\d+', info )[ 0 ] + + @classmethod + def isOldOVS( cls ): + return ( StrictVersion( cls.OVSVersion ) < + StrictVersion( '1.10' ) ) + + @classmethod + def batchShutdown( cls, switches ): + "Call ovs-vsctl del-br on all OVSSwitches in a list" + quietRun( 'ovs-vsctl ' + + ' -- '.join( '--if-exists del-br %s' % s + for s in switches ) ) def dpctl( self, *args ): "Run ovs-ofctl command" @@ -1025,30 +1096,51 @@ class OVSSwitch( Switch ): self.cmd( 'ifconfig lo up' ) # Annoyingly, --if-exists option seems not to work self.cmd( 'ovs-vsctl del-br', self ) - self.cmd( 'ovs-vsctl add-br', self ) - if self.datapath == 'user': - self.cmd( 'ovs-vsctl set bridge', self,'datapath_type=netdev' ) int( self.dpid, 16 ) # DPID must be a hex string - self.cmd( 'ovs-vsctl -- set Bridge', self, - 'other_config:datapath-id=' + self.dpid ) - self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode ) - for intf in self.intfList(): - if not intf.IP(): - self.attach( intf ) - # Add controllers - clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) - for c in controllers ] ) + # Interfaces and controllers + intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) + + '-- set Interface %s ' % intf + + 'ofport_request=%s ' % self.ports[ intf ] + for intf in self.intfList() if not intf.IP() ) + clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port ) + for c in controllers ) if self.listenPort: clist += ' ptcp:%s' % self.listenPort - self.cmd( 'ovs-vsctl set-controller', self, clist ) + # Construct big ovs-vsctl command for new versions of OVS + if not self.isOldOVS(): + cmd = ( 'ovs-vsctl add-br %s ' % self + + '-- set Bridge %s ' % self + + 'other_config:datapath-id=%s ' % self.dpid + + '-- set-fail-mode %s %s ' % ( self, self.failMode ) + + intfs + + '-- set-controller %s %s ' % ( self, clist ) ) + # Construct ovs-vsctl commands for old versions of OVS + else: + self.cmd( 'ovs-vsctl add-br', self ) + for intf in self.intfList(): + if not intf.IP(): + self.cmd( 'ovs-vsctl add-port', self, intf ) + cmd = ( 'ovs-vsctl set Bridge %s ' % self + + 'other_config:datapath-id=%s ' % self.dpid + + '-- set-fail-mode %s %s ' % ( self, self.failMode ) + + '-- set-controller %s %s ' % ( self, clist ) ) + if not self.inband: + cmd += ( '-- set bridge %s ' + 'other-config:disable-in-band=true ' % self ) + if self.datapath == 'user': + cmd += '-- set bridge %s datapath_type=netdev ' % self # Reconnect quickly to controllers (1s vs. 15s max_backoff) for uuid in self.controllerUUIDs(): if uuid.count( '-' ) != 4: # Doesn't look like a UUID continue uuid = uuid.strip() - self.cmd( 'ovs-vsctl set Controller', uuid, - 'max_backoff=1000' ) + cmd += '-- set Controller %smax_backoff=1000 ' % uuid + # Do it!! + self.cmd( cmd ) + for intf in self.intfList(): + self.TCReapply( intf ) + def stop( self ): "Terminate OVS switch." @@ -1063,8 +1155,9 @@ OVSKernelSwitch = OVSSwitch class IVSSwitch(Switch): """IVS virtual switch""" - def __init__( self, name, **kwargs ): + def __init__( self, name, verbose=True, **kwargs ): Switch.__init__( self, name, **kwargs ) + self.verbose = verbose @classmethod def setup( cls ): @@ -1079,12 +1172,19 @@ class IVSSwitch(Switch): 'not be loaded. Try modprobe openvswitch.\n' ) exit( 1 ) + @classmethod + def batchShutdown( cls, switches ): + "Kill each IVS switch, to be waited on later in stop()" + for switch in switches: + switch.cmd( 'kill %ivs' ) + def start( self, controllers ): "Start up a new IVS switch" args = ['ivs'] args.extend( ['--name', self.name] ) args.extend( ['--dpid', self.dpid] ) - args.extend( ['--verbose'] ) + if self.verbose: + args.extend( ['--verbose'] ) for intf in self.intfs.values(): if not intf.IP(): args.extend( ['-i', intf.name] ) @@ -1096,11 +1196,13 @@ class IVSSwitch(Switch): logfile = '/tmp/ivs.%s.log' % self.name + self.cmd( 'ifconfig lo up' ) self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 ' % ( self.__class__.__name__, self.name, self.IP(), self.port, self.pid ) - + @classmethod + def isAvailable( self ): + return quietRun( 'which controller' ) class OVSController( Controller ): "Open vSwitch controller" def __init__( self, name, command='ovs-controller', **kwargs ): + if quietRun( 'which test-controller' ): + command = 'test-controller' Controller.__init__( self, name, command=command, **kwargs ) - + @classmethod + def isAvailable( self ): + return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' ) class NOX( Controller ): "Controller to run a NOX application." @@ -1245,6 +1354,12 @@ class RemoteController( Controller ): warn( "Unable to contact the remote controller" " at %s:%d\n" % ( self.ip, self.port ) ) +def DefaultController( name, order=[ Controller, OVSController ], **kwargs ): + "find any controller that is available and run it" + for controller in order: + if controller.isAvailable(): + return controller( name, **kwargs ) + class NAT( Node ): """NAT: Provides connectivity to external network""" @@ -1311,3 +1426,4 @@ class NAT( Node ): self.cmd( 'sysctl net.ipv4.ip_forward=0' ) super( NAT, self ).terminate() + diff --git a/mininet/nodelib.py b/mininet/nodelib.py new file mode 100644 index 0000000..2eb8046 --- /dev/null +++ b/mininet/nodelib.py @@ -0,0 +1,51 @@ +""" +Node Library for Mininet + +This contains additional Node types which you may find to be useful +""" + +from mininet.net import Mininet +from mininet.topo import Topo +from mininet.node import Switch +from mininet.log import setLogLevel, info + + +class LinuxBridge( Switch ): + "Linux Bridge (with optional spanning tree)" + + nextPrio = 100 # next bridge priority for spanning tree + + def __init__( self, name, stp=False, prio=None, **kwargs ): + """stp: use spanning tree protocol? (default False) + prio: optional explicit bridge priority for STP""" + self.stp = stp + if prio: + self.prio = prio + else: + self.prio = LinuxBridge.nextPrio + LinuxBridge.nextPrio += 1 + Switch.__init__( self, name, **kwargs ) + + def connected( self ): + "Are we forwarding yet?" + if self.stp: + return 'forwarding' in self.cmd( 'brctl showstp', self ) + else: + return True + + def start( self, controllers ): + self.cmd( 'ifconfig', self, 'down' ) + self.cmd( 'brctl delbr', self ) + self.cmd( 'brctl addbr', self ) + if self.stp: + self.cmd( 'brctl setbridgeprio', self.prio ) + self.cmd( 'brctl stp', self, 'on' ) + for i in self.intfList(): + if self.name in i.name: + self.cmd( 'brctl addif', self, i ) + self.cmd( 'ifconfig', self, 'up' ) + + def stop( self ): + self.cmd( 'ifconfig', self, 'down' ) + self.cmd( 'brctl delbr', self ) + diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py index c9d288b..c888e29 100755 --- a/mininet/test/test_hifi.py +++ b/mininet/test/test_hifi.py @@ -53,8 +53,11 @@ class testOptionsTopoCommon( object ): """Check that a given value is within a tolerance of expected tolerance_frac: less-than-1.0 value; 0.8 would yield 20% tolerance. """ - self.assertTrue( float(measured) >= float(expected) * tolerance_frac ) - self.assertTrue( float(measured) >= float(expected) * tolerance_frac ) + self.assertGreaterEqual( float(measured), + float(expected) * tolerance_frac ) + self.assertLessEqual( float( measured ), + float(expected) + (1-tolerance_frac) + * float( expected ) ) def testCPULimits( self ): "Verify topology creation with CPU limits set for both schedulers." @@ -68,19 +71,20 @@ class testOptionsTopoCommon( object ): mn.start() results = mn.runCpuLimitTest( cpu=CPU_FRACTION ) mn.stop() - for cpu in results: - self.assertWithinTolerance( cpu, CPU_FRACTION, CPU_TOLERANCE ) + for pct in results: + #divide cpu by 100 to convert from percentage to fraction + self.assertWithinTolerance( pct/100, CPU_FRACTION, CPU_TOLERANCE ) def testLinkBandwidth( self ): "Verify that link bandwidths are accurate within a bound." - BW = 5 # Mbps + BW = .5 # Mbps BW_TOLERANCE = 0.8 # BW fraction below which test should fail # Verify ability to create limited-link topo first; lopts = { 'bw': BW, 'use_htb': True } # Also verify correctness of limit limitng within a bound. mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ), link=TCLink, switch=self.switchClass ) - bw_strs = mn.run( mn.iperf ) + bw_strs = mn.run( mn.iperf, format='m' ) for bw_str in bw_strs: bw = float( bw_str.split(' ')[0] ) self.assertWithinTolerance( bw, BW, BW_TOLERANCE ) @@ -91,7 +95,7 @@ class testOptionsTopoCommon( object ): DELAY_TOLERANCE = 0.8 # Delay fraction below which test should fail lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True } mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ), - link=TCLink, switch=self.switchClass ) + link=TCLink, switch=self.switchClass, autoStaticArp=True ) ping_delays = mn.run( mn.pingFull ) test_outputs = ping_delays[0] # Ignore unused variables below @@ -102,9 +106,10 @@ class testOptionsTopoCommon( object ): # pylint: enable-msg=W0612 for rttval in [rttmin, rttavg, rttmax]: # Multiply delay by 4 to cover there & back on two links - self.assertWithinTolerance( rttval, DELAY_MS * 4.0, + self.assertWithinTolerance( rttval, DELAY_MS * 4.0, DELAY_TOLERANCE) + def testLinkLoss( self ): "Verify that we see packet drops with a high configured loss rate." LOSS_PERCENT = 99 @@ -120,7 +125,7 @@ class testOptionsTopoCommon( object ): for _ in range(REPS): dropped_total += mn.ping(timeout='1') mn.stop() - self.assertTrue(dropped_total > 0) + self.assertGreater( dropped_total, 0 ) def testMostOptions( self ): "Verify topology creation with most link options and CPU limits." diff --git a/mininet/test/test_nets.py b/mininet/test/test_nets.py index 159ba34..330e9a8 100755 --- a/mininet/test/test_nets.py +++ b/mininet/test/test_nets.py @@ -66,7 +66,7 @@ class testLinearCommon( object ): def testLinear5( self ): "Ping test on a 5-switch topology" - mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host, Controller ) + mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host, Controller, waitConnected=True ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) diff --git a/mininet/test/test_walkthrough.py b/mininet/test/test_walkthrough.py index 6fa1ce0..2c2d6b0 100755 --- a/mininet/test/test_walkthrough.py +++ b/mininet/test/test_walkthrough.py @@ -9,7 +9,6 @@ TODO: missing xterm test import unittest import pexpect import os -from time import sleep from mininet.util import quietRun class testWalkthrough( unittest.TestCase ): diff --git a/mininet/topo.py b/mininet/topo.py index 2ab455c..cc458fd 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -48,18 +48,25 @@ class MultiGraph( object ): class Topo(object): "Data center network representation for structured multi-trees." - def __init__(self, hopts=None, sopts=None, lopts=None): - """Topo object: + def __init__(self, *args, **params): + """Topo object. + Optional named parameters: hinfo: default host options sopts: default switch options - lopts: default link options""" + lopts: default link options + calls build()""" self.g = MultiGraph() self.node_info = {} self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects - self.hopts = {} if hopts is None else hopts - self.sopts = {} if sopts is None else sopts - self.lopts = {} if lopts is None else lopts + self.hopts = params.pop( 'hopts', {} ) + self.sopts = params.pop( 'sopts', {} ) + self.lopts = params.pop( 'lopts', {} ) self.ports = {} # ports[src][dst] is port on src that connects to dst + self.build( *args, **params ) + + def build( self, *args, **params ): + "Override this method to build your topology." + pass def addNode(self, name, **opts): """Add Node to graph. @@ -168,7 +175,7 @@ class Topo(object): ''' if src in self.ports and dst in self.ports[src]: assert dst in self.ports and src in self.ports[dst] - return (self.ports[src][dst], self.ports[dst][src]) + return self.ports[src][dst], self.ports[dst][src] def linkInfo( self, src, dst ): "Return link metadata" @@ -194,76 +201,56 @@ class Topo(object): "Items sorted in natural (i.e. alphabetical) order" return sorted(items, key=natural) -class SingleSwitchTopo(Topo): - '''Single switch connected to k hosts.''' - def __init__(self, k=2, **opts): - '''Init. - - @param k number of hosts - @param enable_all enables all nodes and switches? - ''' - super(SingleSwitchTopo, self).__init__(**opts) +class SingleSwitchTopo( Topo ): + "Single switch connected to k hosts." + def build( self, k=2, **opts ): + "k: number of hosts" self.k = k - - switch = self.addSwitch('s1') - for h in irange(1, k): - host = self.addHost('h%s' % h) - self.addLink(host, switch) + switch = self.addSwitch( 's1' ) + for h in irange( 1, k ): + host = self.addHost( 'h%s' % h ) + self.addLink( host, switch ) -class SingleSwitchReversedTopo(Topo): - '''Single switch connected to k hosts, with reversed ports. +class SingleSwitchReversedTopo( Topo ): + """Single switch connected to k hosts, with reversed ports. + The lowest-numbered host is connected to the highest-numbered port. + Useful to verify that Mininet properly handles custom port numberings.""" - The lowest-numbered host is connected to the highest-numbered port. - - Useful to verify that Mininet properly handles custom port numberings. - ''' - def __init__(self, k=2, **opts): - '''Init. - - @param k number of hosts - @param enable_all enables all nodes and switches? - ''' - super(SingleSwitchReversedTopo, self).__init__(**opts) + def build( self, k=2 ): + "k: number of hosts" self.k = k - switch = self.addSwitch('s1') - for h in irange(1, k): - host = self.addHost('h%s' % h) - self.addLink(host, switch, - port1=0, port2=(k - h + 1)) + switch = self.addSwitch( 's1' ) + for h in irange( 1, k ): + host = self.addHost( 'h%s' % h ) + self.addLink( host, switch, + port1=0, port2=( k - h + 1 ) ) -class LinearTopo(Topo): +class LinearTopo( Topo ): "Linear topology of k switches, with n hosts per switch." - def __init__(self, k=2, n=1, **opts): - """Init. - k: number of switches - n: number of hosts per switch - hconf: host configuration options - lconf: link configuration options""" - - super(LinearTopo, self).__init__(**opts) - + def build( self, k=2, n=1, **opts): + """k: number of switches + n: number of hosts per switch""" self.k = k self.n = n if n == 1: genHostName = lambda i, j: 'h%s' % i else: - genHostName = lambda i, j: 'h%ss%d' % (j, i) - + genHostName = lambda i, j: 'h%ss%d' % ( j, i ) lastSwitch = None - for i in irange(1, k): + for i in irange( 1, k ): # Add switch - switch = self.addSwitch('s%s' % i) + switch = self.addSwitch( 's%s' % i ) # Add hosts to switch - for j in irange(1, n): - host = self.addHost(genHostName(i, j)) - self.addLink(host, switch) + for j in irange( 1, n ): + host = self.addHost( genHostName( i, j ) ) + self.addLink( host, switch ) # Connect switch to previous if lastSwitch: - self.addLink(switch, lastSwitch) + self.addLink( switch, lastSwitch ) lastSwitch = switch diff --git a/mininet/topolib.py b/mininet/topolib.py index 63ba36d..6ee6f0c 100644 --- a/mininet/topolib.py +++ b/mininet/topolib.py @@ -6,8 +6,7 @@ from mininet.net import Mininet class TreeTopo( Topo ): "Topology for a tree network with a given depth and fanout." - def __init__( self, depth=1, fanout=2 ): - super( TreeTopo, self ).__init__() + def build( self, depth=1, fanout=2 ): # Numbering: h1..N, s1..M self.hostNum = 1 self.switchNum = 1 @@ -34,3 +33,37 @@ def TreeNet( depth=1, fanout=2, **kwargs ): "Convenience function for creating tree networks." topo = TreeTopo( depth, fanout ) return Mininet( topo, **kwargs ) + + +class TorusTopo( Topo ): + """2-D Torus topology + WARNING: this topology has LOOPS and WILL NOT WORK + with the default controller or any Ethernet bridge + without STP turned on! It can be used with STP, e.g.: + # mn --topo torus,3,3 --switch lxbr,stp=1 --test pingall""" + + def build( self, x, y ): + if x < 3 or y < 3: + raise Exception( 'Please use 3x3 or greater for compatibility ' + 'with 2.1' ) + hosts, switches, dpid = {}, {}, 0 + # Create and wire interior + for i in range( 0, x ): + for j in range( 0, y ): + loc = '%dx%d' % ( i + 1, j + 1 ) + # dpid cannot be zero for OVS + dpid = ( i + 1 ) * 256 + ( j + 1 ) + switch = switches[ i, j ] = self.addSwitch( 's' + loc, dpid='%016x' % dpid ) + host = hosts[ i, j ] = self.addHost( 'h' + loc ) + self.addLink( host, switch ) + # Connect switches + for i in range( 0, x ): + for j in range( 0, y ): + sw1 = switches[ i, j ] + sw2 = switches[ i, ( j + 1 ) % y ] + sw3 = switches[ ( i + 1 ) % x, j ] + self.addLink( sw1, sw2 ) + self.addLink( sw1, sw3 ) + + + diff --git a/mininet/util.py b/mininet/util.py index 1b6440b..5cb27f4 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -155,7 +155,12 @@ def makeIntfPair( intf1, intf2 ): quietRun( 'ip link del ' + intf2 ) # Create new pair cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2 - return quietRun( cmd ) + cmdOutput = quietRun( cmd ) + if cmdOutput == '': + return True + else: + error( "Error creating interface pair: %s " % cmdOutput ) + return False def retry( retries, delaySecs, fn, *args, **keywords ): """Try something several times before giving up. @@ -183,8 +188,7 @@ def moveIntfNoRetry( intf, dstNode, srcNode=None, printError=False ): srcNode.cmd( cmd ) else: quietRun( cmd ) - links = dstNode.cmd( 'ip link show' ) - if not ( ' %s:' % intf ) in links: + if ( ' %s:' % intf ) not in dstNode.cmd( 'ip link show', intf ): if printError: error( '*** Error: moveIntf: ' + intf + ' not successfully moved to ' + dstNode.name + '\n' ) @@ -269,7 +273,7 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ): ipBaseNum: option base IP address as int returns IP address as string""" imax = 0xffffffff >> prefixLen - assert i <= imax + assert i <= imax, 'Not enough IP addresses in the subnet' mask = 0xffffffff ^ imax ipnum = ( ipBaseNum & mask ) + i return ipStr( ipnum ) @@ -277,6 +281,8 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ): def ipParse( ip ): "Parse an IP address and return an unsigned int." args = [ int( arg ) for arg in ip.split( '.' ) ] + while ( len(args) < 4 ): + args.append( 0 ) return ipNum( *args ) def netParse( ipstr ): @@ -286,6 +292,10 @@ def netParse( ipstr ): if '/' in ipstr: ip, pf = ipstr.split( '/' ) prefixLen = int( pf ) + #if no prefix is specified, set the prefix to 24 + else: + ip = ipstr + prefixLen = 24 return ipParse( ip ), prefixLen def checkInt( s ): diff --git a/mnexec.c b/mnexec.c index a563499..c7103d4 100644 --- a/mnexec.c +++ b/mnexec.c @@ -5,7 +5,7 @@ * * - closing all file descriptors except stdin/out/error * - detaching from a controlling tty using setsid - * - running in a network namespace + * - running in network and mount namespaces * - printing out the pid of a process so we can identify it later * - attaching to a namespace and cgroup * - setting RT scheduling @@ -13,6 +13,7 @@ * Partially based on public domain setsid(1) */ +#define _GNU_SOURCE #include #include #include @@ -20,8 +21,9 @@ #include #include #include -#include #include +#include +#include #if !defined(VERSION) #define VERSION "(devel)" @@ -34,9 +36,9 @@ void usage(char *name) "Options:\n" " -c: close all file descriptors except stdin/out/error\n" " -d: detach from tty by calling setsid()\n" - " -n: run in new network namespace\n" + " -n: run in new network and mount namespaces\n" " -p: print ^A + pid\n" - " -a pid: attach to pid's network namespace\n" + " -a pid: attach to pid's network and mount namespaces\n" " -g group: add to cgroup\n" " -r rtprio: run with SCHED_RR (usually requires -g)\n" " -v: print version\n", @@ -62,7 +64,7 @@ void validate(char *path) } /* Add our pid to cgroup */ -int cgroup(char *gname) +void cgroup(char *gname) { static char path[PATH_MAX]; static char *groups[] = { @@ -121,11 +123,16 @@ int main(int argc, char *argv[]) setsid(); break; case 'n': - /* run in network namespace */ - if (unshare(CLONE_NEWNET) == -1) { + /* run in network and mount namespaces */ + if (unshare(CLONE_NEWNET|CLONE_NEWNS) == -1) { perror("unshare"); return 1; } + /* mount sysfs to pick up the new network namespace */ + if (mount("sysfs", "/sys", "sysfs", MS_MGC_VAL, NULL) == -1) { + perror("mount"); + return 1; + } break; case 'p': /* print pid */ @@ -133,7 +140,7 @@ int main(int argc, char *argv[]) fflush(stdout); break; case 'a': - /* Attach to pid's network namespace */ + /* Attach to pid's network namespace and mount namespace*/ pid = atoi(optarg); sprintf(path, "/proc/%d/ns/net", pid ); nsid = open(path, O_RDONLY); @@ -145,6 +152,16 @@ int main(int argc, char *argv[]) perror("setns"); return 1; } + sprintf(path, "/proc/%d/ns/mnt", pid ); + nsid = open(path, O_RDONLY); + if (nsid < 0) { + perror(path); + return 1; + } + if (setns(nsid, 0) != 0) { + perror("setns"); + return 1; + } break; case 'g': /* Attach to cgroup */ @@ -176,4 +193,6 @@ int main(int argc, char *argv[]) } usage(argv[0]); + + return 0; } diff --git a/util/install.sh b/util/install.sh index 59e9bbf..b1df4a1 100755 --- a/util/install.sh +++ b/util/install.sh @@ -63,22 +63,11 @@ echo "Detected Linux distribution: $DIST $RELEASE $CODENAME $ARCH" # Kernel params -if [ "$DIST" = "Ubuntu" ]; then - if [ "$RELEASE" = "10.04" ]; then - KERNEL_NAME='3.0.0-15-generic' - else - KERNEL_NAME=`uname -r` - fi - KERNEL_HEADERS=linux-headers-${KERNEL_NAME} -elif [ "$DIST" = "Debian" ] && [ "$ARCH" = "i386" ] && [ "$CODENAME" = "lenny" ]; then - KERNEL_NAME=2.6.33.1-mininet - KERNEL_HEADERS=linux-headers-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb - KERNEL_IMAGE=linux-image-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb -elif [ "$DIST" = "Fedora" ]; then - KERNEL_NAME=`uname -r` - KERNEL_HEADERS=kernel-headers-${KERNEL_NAME} -else - echo "Install.sh currently only supports Ubuntu, Debian Lenny i386 and Fedora." +KERNEL_NAME=`uname -r` +KERNEL_HEADERS=kernel-headers-${KERNEL_NAME} + +if ! echo $DIST | egrep 'Ubuntu|Debian|Fedora'; then + echo "Install.sh currently only supports Ubuntu, Debian and Fedora." exit 1 fi @@ -96,8 +85,6 @@ OVS_BUILDSUFFIX=-ignore # was -2 OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar OVS_TAG=v$OVS_RELEASE -IVS_TAG=v0.3 - # Command-line versions overrides that simplify custom VM creation # To use, pass in the vars on the cmd line before install.sh, e.g. # WS_DISSECTOR_REV=pre-ws-1.10.0 install.sh -w @@ -108,31 +95,7 @@ OF13_SWITCH_REV=${OF13_SWITCH_REV:-""} function kernel { echo "Install Mininet-compatible kernel if necessary" sudo apt-get update - if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then - $install linux-image-$KERNEL_NAME - elif [ "$DIST" = "Debian" ]; then - # The easy approach: download pre-built linux-image and linux-headers packages: - wget -c $KERNEL_LOC/$KERNEL_HEADERS - wget -c $KERNEL_LOC/$KERNEL_IMAGE - - # Install custom linux headers and image: - $pkginst $KERNEL_IMAGE $KERNEL_HEADERS - - # The next two steps are to work around a bug in newer versions of - # kernel-package, which fails to add initrd images with the latest kernels. - # See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=525032 - # Generate initrd image if the .deb didn't install it: - if ! test -e /boot/initrd.img-${KERNEL_NAME}; then - sudo update-initramfs -c -k ${KERNEL_NAME} - fi - - # Ensure /boot/grub/menu.lst boots with initrd image: - sudo update-grub - - # The default should be the new kernel. Otherwise, you may need to modify - # /boot/grub/menu.lst to set the default to the entry corresponding to the - # kernel you just installed. - fi + $install linux-image-$KERNEL_NAME } function kernel_clean { @@ -176,10 +139,9 @@ function mn_dev { # -user switch # The instructions below are an abbreviated version from # http://www.openflowswitch.org/wk/index.php/Debian_Install -# ... modified to use Debian Lenny rather than unstable. function of { echo "Installing OpenFlow reference implementation..." - cd $BUILD_DIR/ + cd $BUILD_DIR $install autoconf automake libtool make gcc if [ "$DIST" = "Fedora" ]; then $install git pkgconfig glibc-devel @@ -300,111 +262,95 @@ function wireshark { } +# Install Open vSwitch specific version Ubuntu package +function ubuntuOvs { + echo "Creating and Installing Open vSwitch packages..." + + OVS_SRC=$BUILD_DIR/openvswitch + OVS_TARBALL_LOC=http://openvswitch.org/releases + + if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` = 1 ]; then + rm -rf $OVS_SRC + mkdir -p $OVS_SRC + cd $OVS_SRC + + if wget $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz 2> /dev/null; then + tar xzf openvswitch-$OVS_RELEASE.tar.gz + else + echo "Failed to find OVS at $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz" + cd $BUILD_DIR + return + fi + + # Remove any old packages + $remove openvswitch-common openvswitch-datapath-dkms openvswitch-controller \ + openvswitch-pki openvswitch-switch + + # Get build deps + $install build-essential fakeroot debhelper autoconf automake libssl-dev \ + pkg-config bzip2 openssl python-all procps python-qt4 \ + python-zopeinterface python-twisted-conch dkms + + # Build OVS + cd $BUILD_DIR/openvswitch/openvswitch-$OVS_RELEASE + DEB_BUILD_OPTIONS='parallel=2 nocheck' fakeroot debian/rules binary + cd .. + $pkginst openvswitch-common_$OVS_RELEASE*.deb openvswitch-datapath-dkms_$OVS_RELEASE*.deb \ + openvswitch-pki_$OVS_RELEASE*.deb openvswitch-switch_$OVS_RELEASE*.deb + if $pkginst openvswitch-controller_$OVS_RELEASE*.deb; then + echo "Ignoring error installing openvswitch-controller" + fi + + modinfo openvswitch + sudo ovs-vsctl show + # Switch can run on its own, but + # Mininet should control the controller + # This appears to only be an issue on Ubuntu/Debian + if sudo service openvswitch-controller stop; then + echo "Stopped running controller" + fi + if [ -e /etc/init.d/openvswitch-controller ]; then + sudo update-rc.d openvswitch-controller disable + fi + else + echo "Failed to install Open vSwitch. OS must be Ubuntu >= 12.04" + cd $BUILD_DIR + return + fi +} + + # Install Open vSwitch -# Instructions derived from OVS INSTALL, INSTALL.OpenFlow and README files. function ovs { echo "Installing Open vSwitch..." - if [ "$DIST" = "Fedora" ]; then - # Just install Fedora's openvswitch RPMS + if [ "$DIST" == "Fedora" ]; then $install openvswitch openvswitch-controller return fi - OVS_SRC=$BUILD_DIR/openvswitch - OVS_BUILD=$OVS_SRC/build-$KERNEL_NAME - OVS_KMODS=($OVS_BUILD/datapath/linux/{openvswitch_mod.ko,brcompat_mod.ko}) - - # Required for module build/dkms install - $install $KERNEL_HEADERS - - ovspresent=0 - - # First see if we have packages - # XXX wget -c seems to fail from github/amazon s3 - cd /tmp - if wget $OVS_PACKAGE_LOC/$OVS_PACKAGE_NAME 2> /dev/null; then - $install patch dkms fakeroot python-argparse - tar xf $OVS_PACKAGE_NAME - orig=`tar tf $OVS_PACKAGE_NAME` - # Now install packages in reasonable dependency order - order='dkms common pki openvswitch-switch brcompat controller' - pkgs="" - for p in $order; do - pkg=`echo "$orig" | grep $p` - # Annoyingly, things seem to be missing without this flag - $pkginst --force-confmiss $pkg - done - ovspresent=1 + # Manually installing openvswitch-datapath may be necessary + # for manually built kernel .debs using Debian's defective kernel + # packaging, which doesn't yield usable headers. + if ! dpkg --get-selections | grep openvswitch-datapath; then + # If you've already installed a datapath, assume you + # know what you're doing and don't need dkms datapath. + # Otherwise, install it. + $install openvswitch-datapath-dkms fi - # Otherwise try distribution's OVS packages - if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 11.10` = 1 ]; then - if ! dpkg --get-selections | grep openvswitch-datapath; then - # If you've already installed a datapath, assume you - # know what you're doing and don't need dkms datapath. - # Otherwise, install it. - $install openvswitch-datapath-dkms - fi - if $install openvswitch-switch openvswitch-controller; then - echo "Ignoring error installing openvswitch-controller" - fi - ovspresent=1 - fi + $install openvswitch-switch openvswitch-controller # Switch can run on its own, but # Mininet should control the controller + # This appears to only be an issue on Ubuntu/Debian + if sudo service openvswitch-controller stop; then + echo "Stopped running controller" + fi if [ -e /etc/init.d/openvswitch-controller ]; then - if sudo service openvswitch-controller stop; then - echo "Stopped running controller" - fi sudo update-rc.d openvswitch-controller disable fi - - if [ $ovspresent = 1 ]; then - echo "Done (hopefully) installing packages" - cd $BUILD_DIR - return - fi - - # Otherwise attempt to install from source - - $install pkg-config gcc make python-dev libssl-dev libtool - - if [ "$DIST" = "Debian" ]; then - if [ "$CODENAME" = "lenny" ]; then - $install git-core - # Install Autoconf 2.63+ backport from Debian Backports repo: - # Instructions from http://backports.org/dokuwiki/doku.php?id=instructions - sudo su -c "echo 'deb http://www.backports.org/debian lenny-backports main contrib non-free' >> /etc/apt/sources.list" - sudo apt-get update - sudo apt-get -y --force-yes install debian-backports-keyring - sudo apt-get -y --force-yes -t lenny-backports install autoconf - fi - else - $install git - fi - - # Install OVS from release - cd $BUILD_DIR/ - git clone git://openvswitch.org/openvswitch $OVS_SRC - cd $OVS_SRC - git checkout $OVS_TAG - ./boot.sh - BUILDDIR=/lib/modules/${KERNEL_NAME}/build - if [ ! -e $BUILDDIR ]; then - echo "Creating build sdirectory $BUILDDIR" - sudo mkdir -p $BUILDDIR - fi - opts="--with-linux=$BUILDDIR" - mkdir -p $OVS_BUILD - cd $OVS_BUILD - ../configure $opts - make - sudo make install - - modprobe } function remove_ovs { @@ -438,7 +384,7 @@ function ivs { # Install IVS from source cd $BUILD_DIR - git clone git://github.com/floodlight/ivs $IVS_SRC -b $IVS_TAG --recursive + git clone git://github.com/floodlight/ivs $IVS_SRC --recursive cd $IVS_SRC make sudo make install @@ -555,7 +501,7 @@ function cbench { $install libsnmp-dev libpcap-dev libconfig-dev fi cd $BUILD_DIR/ - git clone git://openflow.org/oflops.git + git clone git://gitosis.stanford.edu/oflops.git cd oflops sh boot.sh || true # possible error in autoreconf, so run twice sh boot.sh @@ -706,7 +652,7 @@ function vm_clean { } function usage { - printf '\nUsage: %s [-abcdfhikmnprtvwx03]\n\n' $(basename $0) >&2 + printf '\nUsage: %s [-abcdfhikmnprtvVwx03]\n\n' $(basename $0) >&2 printf 'This install script attempts to install useful packages\n' >&2 printf 'for Mininet. It should (hopefully) work on Ubuntu 11.10+\n' >&2 @@ -731,6 +677,7 @@ function usage { printf -- ' -s : place dependency (S)ource/build trees in \n' >&2 printf -- ' -t: complete o(T)her Mininet VM setup tasks\n' >&2 printf -- ' -v: install Open (V)switch\n' >&2 + printf -- ' -V : install a particular version of Open (V)switch on Ubuntu\n' >&2 printf -- ' -w: install OpenFlow (W)ireshark dissector\n' >&2 printf -- ' -x: install NO(X) Classic OpenFlow controller\n' >&2 printf -- ' -0: (default) -0[fx] installs OpenFlow 1.0 versions\n' >&2 @@ -744,7 +691,7 @@ if [ $# -eq 0 ] then all else - while getopts 'abcdefhikmnprs:tvwx03' OPTION + while getopts 'abcdefhikmnprs:tvV:wx03' OPTION do case $OPTION in a) all;; @@ -769,6 +716,8 @@ else echo "Dependency installation directory: $BUILD_DIR";; t) vm_other;; v) ovs;; + V) OVS_RELEASE=$OPTARG; + ubuntuOvs;; w) wireshark;; x) case $OF_VERSION in 1.0) nox;; diff --git a/util/versioncheck.py b/util/versioncheck.py index d9e5483..7180f49 100755 --- a/util/versioncheck.py +++ b/util/versioncheck.py @@ -8,7 +8,7 @@ version = 'Mininet ' + co( 'PYTHONPATH=. bin/mn --version', shell=True ) version = version.strip() # Find all Mininet path references -lines = co( "grep -or 'Mininet \w\.\w\.\w\w*' *", shell=True ) +lines = co( "grep -or 'Mininet \w\+\.\w\+\.\w\+[+]*' *", shell=True ) error = False diff --git a/util/vm/build.py b/util/vm/build.py index eda70e4..ad454f0 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -83,6 +83,18 @@ isoURLs = { 'saucy64server': 'http://mirrors.kernel.org/ubuntu-releases/13.10/' 'ubuntu-13.10-server-amd64.iso', + 'trusty32server': + 'http://mirrors.kernel.org/ubuntu-releases/14.04/' + 'ubuntu-14.04-server-i386.iso', + 'trusty64server': + 'http://mirrors.kernel.org/ubuntu-releases/14.04/' + 'ubuntu-14.04-server-amd64.iso', + 'utopic32server': + 'http://mirrors.kernel.org/ubuntu-releases/14.10/' + 'ubuntu-14.10-server-i386.iso', + 'utopic64server': + 'http://mirrors.kernel.org/ubuntu-releases/14.10/' + 'ubuntu-14.10-server-amd64.iso', } @@ -91,6 +103,18 @@ def OSVersion( flavor ): urlbase = path.basename( isoURLs.get( flavor, 'unknown' ) ) return path.splitext( urlbase )[ 0 ] +def OVFOSNameID( flavor ): + "Return OVF-specified ( OS Name, ID ) for flavor" + version = OSVersion( flavor ) + arch = archFor( flavor ) + if 'ubuntu' in version: + map = { 'i386': ( 'Ubuntu', 93 ), + 'x86_64': ( 'Ubuntu 64-bit', 94 ) } + else: + map = { 'i386': ( 'Linux', 36 ), + 'x86_64': ( 'Linux 64-bit', 101 ) } + osname, osid = map[ arch ] + return osname, osid LogStartTime = time() LogFile = None @@ -140,8 +164,7 @@ def depend(): run( 'sudo apt-get -y update' ) run( 'sudo apt-get install -y' ' kvm cloud-utils genisoimage qemu-kvm qemu-utils' - ' e2fsprogs ' - ' landscape-client' + ' e2fsprogs dnsmasq curl' ' python-setuptools mtools zip' ) run( 'sudo easy_install pexpect' ) @@ -440,16 +463,16 @@ def boot( cow, kernel, initrd, logfile, memory=1024 ): return vm -def login( vm ): +def login( vm, user='mininet', password='mininet' ): "Log in to vm (pexpect object)" log( '* Waiting for login prompt' ) vm.expect( 'login: ' ) log( '* Logging in' ) - vm.sendline( 'mininet' ) + vm.sendline( user ) log( '* Waiting for password prompt' ) vm.expect( 'Password: ' ) log( '* Sending password' ) - vm.sendline( 'mininet' ) + vm.sendline( password ) log( '* Waiting for login...' ) @@ -485,6 +508,10 @@ def coreTest( vm, prompt=Prompt ): log( '* Test', test, 'output:' ) log( vm.before ) +def noneTest( vm ): + "This test does nothing" + vm.sendline( 'echo' ) + def examplesquickTest( vm, prompt=Prompt ): "Quick test of mininet examples" vm.sendline( 'sudo apt-get install python-pexpect' ) @@ -507,8 +534,13 @@ def walkthroughTest( vm, prompt=Prompt ): def checkOutBranch( vm, branch, prompt=Prompt ): - vm.sendline( 'cd ~/mininet; git fetch; git pull --rebase; git checkout ' - + branch ) + # This is a bit subtle; it will check out an existing branch (e.g. master) + # if it exists; otherwise it will create a detached branch. + # The branch will be rebased to its parent on origin. + # This probably doesn't matter since we're running on a COW disk + # anyway. + vm.sendline( 'cd ~/mininet; git fetch --all; git checkout ' + + branch + '; git pull --rebase origin ' + branch ) vm.expect( prompt ) vm.sendline( 'sudo make install' ) @@ -523,12 +555,16 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ): log( '* Waiting for output' ) vm.expect( prompt ) log( '* Fetching Mininet VM install script' ) + branch = Branch if Branch else 'master' vm.sendline( 'wget ' - 'https://raw.github.com/mininet/mininet/master/util/vm/' - 'install-mininet-vm.sh' ) + 'https://raw.github.com/mininet/mininet/%s/util/vm/' + 'install-mininet-vm.sh' % branch ) vm.expect( prompt ) log( '* Running VM install script' ) - vm.sendline( 'bash install-mininet-vm.sh' ) + installcmd = 'bash install-mininet-vm.sh' + if Branch: + installcmd += ' ' + Branch + vm.sendline( installcmd ) vm.expect ( 'password for mininet: ' ) vm.sendline( 'mininet' ) log( '* Waiting for script to complete... ' ) @@ -541,6 +577,13 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ): log( '* Mininet version: ', version ) log( '* Testing Mininet' ) runTests( vm, tests=tests, pre=pre, post=post ) + # Ubuntu adds this because we install via a serial console, + # but we want the VM to boot via the VM console. Otherwise + # we get the message 'error: terminal "serial" not found' + log( '* Disabling serial console' ) + vm.sendline( "sudo sed -i -e 's/^GRUB_TERMINAL=serial/#GRUB_TERMINAL=serial/' " + "/etc/default/grub; sudo update-grub" ) + vm.expect( prompt ) log( '* Shutting down' ) vm.sendline( 'sync; sudo shutdown -h now' ) log( '* Waiting for EOF/shutdown' ) @@ -577,13 +620,13 @@ OVFTemplate = """ xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - + Virtual disk information - + The list of logical networks @@ -591,26 +634,30 @@ OVFTemplate = """ The nat network - -A Mininet Virtual Machine (%s) -mininet-vm + +%(vminfo)s (%(name)s) +%(vmname)s + +The kind of installed guest operating system +%(osname)s + Virtual hardware requirements hertz * 10^6 Number of Virtual CPUs -1 virtual CPU(s) +%(cpus)s virtual CPU(s) 1 3 -1 +%(cpus)s byte * 2^20 Memory Size -%dMB of memory +%(mem)dMB of memory 2 4 -%d +%(mem)d 0 @@ -653,16 +700,24 @@ OVFTemplate = """ """ -def generateOVF( name, diskname, disksize, mem=1024 ): +def generateOVF( name, osname, osid, diskname, disksize, mem=1024, cpus=1, + vmname='Mininet-VM', vminfo='A Mininet Virtual Machine' ): """Generate (and return) OVF file "name.ovf" name: root name of OVF file to generate + osname: OS name for OVF (Ubuntu | Ubuntu 64-bit) + osid: OS ID for OVF (93 | 94 ) diskname: name of disk file disksize: size of virtual disk in bytes - mem: VM memory size in MB""" + mem: VM memory size in MB + cpus: # of virtual CPUs + vmname: Name for VM (default name when importing) + vmimfo: Brief description of VM for OVF""" ovf = name + '.ovf' filesize = stat( diskname )[ ST_SIZE ] - # OVFTemplate uses the memory size twice in a row - xmltext = OVFTemplate % ( diskname, filesize, disksize, name, mem, mem ) + params = dict( osname=osname, osid=osid, diskname=diskname, + filesize=filesize, disksize=disksize, name=name, + mem=mem, cpus=cpus, vmname=vmname, vminfo=vminfo ) + xmltext = OVFTemplate % params with open( ovf, 'w+' ) as f: f.write( xmltext ) return ovf @@ -689,6 +744,8 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ): date = strftime( '%y%m%d-%H-%M-%S', lstart) ovfdate = strftime( '%y%m%d', lstart ) dir = 'mn-%s-%s' % ( flavor, date ) + if Branch: + dir = 'mn-%s-%s-%s' % ( Branch, flavor, date ) try: os.mkdir( dir ) except: @@ -710,13 +767,16 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ): vm = boot( volume, kernel, initrd, logfile, memory=memory ) version = interact( vm, tests=tests, pre=pre, post=post ) size = qcow2size( volume ) - vmdk = convert( volume, basename='mininet-vm-' + archFor( flavor ) ) + arch = archFor( flavor ) + vmdk = convert( volume, basename='mininet-vm-' + arch ) if not SaveQCOW2: log( '* Removing qcow2 volume', volume ) os.remove( volume ) log( '* Converted VM image stored as', abspath( vmdk ) ) ovfname = 'mininet-%s-%s-%s' % ( version, ovfdate, OSVersion( flavor ) ) - ovf = generateOVF( diskname=vmdk, disksize=size, name=ovfname ) + osname, osid = OVFOSNameID( flavor ) + ovf = generateOVF( name=ovfname, osname=osname, osid=osid, + diskname=vmdk, disksize=size ) log( '* Generated OVF descriptor file', ovf ) if Zip: log( '* Generating .zip file' ) @@ -731,6 +791,9 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ): def runTests( vm, tests=None, pre='', post='', prompt=Prompt ): "Run tests (list) in vm (pexpect object)" + if Branch: + checkOutBranch( vm, branch=Branch ) + vm.expect( prompt ) if not tests: tests = [] if pre: @@ -761,8 +824,8 @@ def getMininetVersion( vm ): return version -def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt, - memory=1024 ): +def bootAndRun( image, prompt=Prompt, memory=1024, outputFile=None, + runFunction=None, **runArgs ): """Boot and test VM tests: list of tests to run pre: command line to run in VM before tests @@ -790,15 +853,16 @@ def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt, login( vm ) log( '* Waiting for prompt after login' ) vm.expect( prompt ) - if Branch: - checkOutBranch( vm, branch=Branch ) - vm.expect( prompt ) - runTests( vm, tests=tests, pre=pre, post=post ) - # runTests eats its last prompt, but maybe it shouldn't... + # runFunction should begin with sendline and should eat its last prompt + if runFunction: + runFunction( vm, **runArgs ) log( '* Shutting down' ) vm.sendline( 'sudo shutdown -h now ' ) log( '* Waiting for shutdown' ) vm.wait() + if outputFile: + log( '* Saving temporary image to %s' % outputFile ) + convert( cow, outputFile ) log( '* Removing temporary dir', tmpdir ) srun( 'rm -rf ' + tmpdir ) elapsed = time() - bootTestStart @@ -859,12 +923,13 @@ def parseArgs(): parser.add_argument( '-p', '--post', metavar='cmd', default='', help='specify a command line to run after tests' ) parser.add_argument( '-b', '--branch', metavar='branch', - help='For an existing VM image, check out and install' - ' this branch before testing' ) + help='branch to install and/or check out and test' ) parser.add_argument( 'flavor', nargs='*', help='VM flavor(s) to build (e.g. raring32server)' ) parser.add_argument( '-z', '--zip', action='store_true', help='archive .ovf and .vmdk into .zip file' ) + parser.add_argument( '-o', '--out', + help='output file for test image (vmdk)' ) args = parser.parse_args() if args.depend: depend() @@ -896,8 +961,8 @@ def parseArgs(): log( '* BUILD FAILED with exception: ', e ) exit( 1 ) for image in args.image: - bootAndRunTests( image, tests=args.test, pre=args.run, - post=args.post, memory=args.memory) + bootAndRun( image, runFunction=runTests, tests=args.test, pre=args.run, + post=args.post, memory=args.memory, outputFile=args.out ) if not ( args.depend or args.list or args.clean or args.flavor or args.image ): parser.print_help() diff --git a/util/vm/install-mininet-vm.sh b/util/vm/install-mininet-vm.sh index aa93de4..3c82e75 100755 --- a/util/vm/install-mininet-vm.sh +++ b/util/vm/install-mininet-vm.sh @@ -3,6 +3,8 @@ # This script is intended to install Mininet into # a brand-new Ubuntu virtual machine, # to create a fully usable "tutorial" VM. +# +# optional argument: Mininet branch to install set -e echo `whoami` ALL=NOPASSWD: ALL | sudo tee -a /etc/sudoers > /dev/null sudo sed -i -e 's/Default/#Default/' /etc/sudoers @@ -25,11 +27,16 @@ fi if [ -e /etc/rc.local.backup ]; then sudo mv /etc/rc.local.backup /etc/rc.local fi -# Install Mininet +# Fetch Mininet sudo apt-get -y install git-core openssh-server git clone git://github.com/mininet/mininet -cd mininet -cd +# Optionally check out branch +if [ "$1" != "" ]; then + pushd mininet + git checkout -b $1 $1 + popd +fi +# Install Mininet time mininet/util/install.sh # Finalize VM time mininet/util/install.sh -tc @@ -38,4 +45,3 @@ time mininet/util/install.sh -tc # echo "export NOX_CORE_DIR=~/noxcore/build/src/" >> .bashrc #fi echo "Done preparing Mininet VM." -