From 387250cd83d818e8cf1a48cdbd3475dadcfc92c0 Mon Sep 17 00:00:00 2001 From: Jose Pedro Oliveira Date: Thu, 19 Sep 2013 15:25:22 +0100 Subject: [PATCH 001/140] Fixes compiler warning: implicit declaration of function 'isalnum' --- mnexec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mnexec.c b/mnexec.c index a563499..3171bab 100644 --- a/mnexec.c +++ b/mnexec.c @@ -22,6 +22,7 @@ #include #include #include +#include #if !defined(VERSION) #define VERSION "(devel)" From b8fd3d2d8e479604b0f1bfd932331e203d853472 Mon Sep 17 00:00:00 2001 From: Jose Pedro Oliveira Date: Thu, 19 Sep 2013 15:26:26 +0100 Subject: [PATCH 002/140] Fixes compiler warning: control reaches end of non-void function --- mnexec.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mnexec.c b/mnexec.c index 3171bab..3d24743 100644 --- a/mnexec.c +++ b/mnexec.c @@ -177,4 +177,6 @@ int main(int argc, char *argv[]) } usage(argv[0]); + + return 0; } From f5737aa3cc72713a4b3028a12a6be67d051917bb Mon Sep 17 00:00:00 2001 From: Jose Pedro Oliveira Date: Sat, 28 Sep 2013 01:34:01 +0100 Subject: [PATCH 003/140] The header file limits.h was being included twice --- mnexec.c | 1 - 1 file changed, 1 deletion(-) diff --git a/mnexec.c b/mnexec.c index 3d24743..c30b4a6 100644 --- a/mnexec.c +++ b/mnexec.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include From 8842e450dba6fb874502f4632b4ea1f893e89da0 Mon Sep 17 00:00:00 2001 From: Jose Pedro Oliveira Date: Sat, 28 Sep 2013 01:36:02 +0100 Subject: [PATCH 004/140] Turns on gcc warnings (CFLAGS += -Wall -Wextra) --- Makefile | 2 ++ 1 file changed, 2 insertions(+) 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: From 2941bbae2d023518edaf8eab443c2bbcec755d56 Mon Sep 17 00:00:00 2001 From: Jose Pedro Oliveira Date: Sat, 28 Sep 2013 01:37:51 +0100 Subject: [PATCH 005/140] Defines _GNU_SOURCE (required by the unshare syscall) --- mnexec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mnexec.c b/mnexec.c index c30b4a6..ea8fb6f 100644 --- a/mnexec.c +++ b/mnexec.c @@ -13,6 +13,7 @@ * Partially based on public domain setsid(1) */ +#define _GNU_SOURCE #include #include #include From 8f80f875b5713bd0077251676c4813b4cf68d51d Mon Sep 17 00:00:00 2001 From: Jose Pedro Oliveira Date: Sat, 28 Sep 2013 01:42:05 +0100 Subject: [PATCH 006/140] Change cgroup() return type from int to void (avoids a gcc warning) --- mnexec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mnexec.c b/mnexec.c index ea8fb6f..b7be9bc 100644 --- a/mnexec.c +++ b/mnexec.c @@ -63,7 +63,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[] = { From a6e55e352847915136b5ed5c2a87a557a74ea21a Mon Sep 17 00:00:00 2001 From: Fernando Cappi Date: Sat, 5 Oct 2013 22:49:15 -0300 Subject: [PATCH 007/140] Update Oflops repository in install.sh --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 59e9bbf..10a6e2e 100755 --- a/util/install.sh +++ b/util/install.sh @@ -555,7 +555,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 From 4a94f76ec31b96b280e38e7b19ede878129ffc55 Mon Sep 17 00:00:00 2001 From: Rich Lane Date: Tue, 8 Oct 2013 16:59:02 -0700 Subject: [PATCH 008/140] IVSSwitch: bring up lo interface --- mininet/node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mininet/node.py b/mininet/node.py index 9e93957..52e6896 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1094,6 +1094,7 @@ class IVSSwitch(Switch): logfile = '/tmp/ivs.%s.log' % self.name + self.cmd( 'ifconfig lo up' ) self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 Date: Thu, 24 Oct 2013 15:11:43 -0700 Subject: [PATCH 009/140] Only restart network-manager if we've changed config --- examples/nat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/nat.py b/examples/nat.py index fcad85c..f8fb0b1 100755 --- a/examples/nat.py +++ b/examples/nat.py @@ -63,9 +63,9 @@ def fixNetworkManager( root, intf ): 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 From cd238fe5678576b9890c7d5c59c1e1a1bc21713c Mon Sep 17 00:00:00 2001 From: Baohua Yang Date: Fri, 15 Nov 2013 14:24:51 +0800 Subject: [PATCH 010/140] Fix error wiki url. --- mininet/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/log.py b/mininet/log.py index a046f50..09a664a 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.""" From 54615659458432f3479f8503ccb6cea66fcfdcd6 Mon Sep 17 00:00:00 2001 From: Rich Lane Date: Sun, 17 Nov 2013 22:38:30 -0800 Subject: [PATCH 011/140] mnexec: mount sysfs in each container The sysfs filesystem is [tagged][1] with a set of namespaces when mounted, taken from the mounting process. Among other things, this controls which network devices will show up in /sys/class/net and /sys/class/net/bonding_masters. Without this change, mininet will not mount sysfs in a node. Attempting to configure a bond interface in a node will only affect the parent namespace. This change mounts a new sysfs filesystem in each node. To prevent this mount from affecting the parent namespace the mount namespace is also unshared. [1]: https://www.kernel.org/doc/Documentation/filesystems/sysfs-tagging.txt --- mnexec.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/mnexec.c b/mnexec.c index b7be9bc..fee3d25 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 @@ -23,6 +23,7 @@ #include #include #include +#include #if !defined(VERSION) #define VERSION "(devel)" @@ -35,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", @@ -122,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 */ From 5da9376222fa476102bf6e84d569f9e96763fadc Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 20 Nov 2013 22:15:26 -0800 Subject: [PATCH 012/140] Depend: add dnsmasq (needed), remove landscape-client (not) --- util/vm/build.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/util/vm/build.py b/util/vm/build.py index eda70e4..daebb34 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -140,8 +140,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' ' python-setuptools mtools zip' ) run( 'sudo easy_install pexpect' ) @@ -581,8 +580,8 @@ OVFTemplate = """ Virtual disk information - From c7e86f9374bb774db5a2f6e1c01a37c5e53c99a3 Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Thu, 5 Dec 2013 17:30:09 -0800 Subject: [PATCH 013/140] fixing CLI host rewriting when host's default interface does not have an IP --- mininet/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mininet/cli.py b/mininet/cli.py index 3ca190f..2ff9825 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -331,7 +331,8 @@ 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 ) From fc7c919b43cf0fd48982c8deadc6054a94f4fe2d Mon Sep 17 00:00:00 2001 From: Baohua Yang Date: Wed, 11 Dec 2013 14:41:21 +0800 Subject: [PATCH 014/140] Close the file when done with it. --- mininet/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mininet/cli.py b/mininet/cli.py index 3ca190f..67823de 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -297,6 +297,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 ): From 3e2333e5edf848cc1acd6f04a54d6894e3b5e090 Mon Sep 17 00:00:00 2001 From: Baohua Yang Date: Wed, 11 Dec 2013 15:19:24 +0800 Subject: [PATCH 015/140] Correct the comments of do_pingallfull() and do_iperfudp() --- mininet/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/cli.py b/mininet/cli.py index 67823de..aa3c2b3 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -160,7 +160,7 @@ class CLI( Cmd ): 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 +187,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' ) From 96952b92f8c2f56865dff64597dd212c86d6ebe4 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 11 Dec 2013 18:08:33 -0800 Subject: [PATCH 016/140] 2.1.0 -> 2.1.0+ --- INSTALL | 2 +- LICENSE | 2 +- README.md | 8 ++++---- mininet/net.py | 2 +- util/versioncheck.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/INSTALL b/INSTALL index 494638c..f003c08 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 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/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/mininet/net.py b/mininet/net.py index 4994f9e..55bb1c8 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -102,7 +102,7 @@ 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." diff --git a/util/versioncheck.py b/util/versioncheck.py index d9e5483..cdf6c22 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[\w\+]*' *", shell=True ) error = False From 312c386cda37b8d39c82c30739cdc7162a5035c7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 11 Dec 2013 18:29:58 -0800 Subject: [PATCH 017/140] Fix regex to support Mininet 20.30.40+++ --- util/versioncheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/versioncheck.py b/util/versioncheck.py index cdf6c22..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 From 17dbc7e055b86c0e55064dbc69fa84b7d948d1ae Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 11 Dec 2013 18:33:54 -0800 Subject: [PATCH 018/140] Minor codecheck fixes --- examples/sshd.py | 2 +- mininet/test/test_walkthrough.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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/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 ): From 49994c8915c118f39a35e74518a3450b37b64ff0 Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Thu, 12 Dec 2013 15:38:45 -0800 Subject: [PATCH 019/140] adding noneTest to Mininet vm build script --- util/vm/build.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/vm/build.py b/util/vm/build.py index daebb34..6f8b521 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -484,6 +484,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' ) From fb51cdaca3189c20aaff1c60599a70b4f74f6c2d Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Thu, 12 Dec 2013 18:38:20 -0800 Subject: [PATCH 020/140] adding output file to build.py --- util/vm/build.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/util/vm/build.py b/util/vm/build.py index 6f8b521..dda4b93 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -765,7 +765,7 @@ def getMininetVersion( vm ): def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt, - memory=1024 ): + memory=1024, outputFile=None ): """Boot and test VM tests: list of tests to run pre: command line to run in VM before tests @@ -802,6 +802,9 @@ def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt, 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 @@ -868,6 +871,8 @@ def parseArgs(): 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() @@ -900,7 +905,8 @@ def parseArgs(): exit( 1 ) for image in args.image: bootAndRunTests( image, tests=args.test, pre=args.run, - post=args.post, memory=args.memory) + 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() From c34a000e7832d73da2c1de6d8cb46ef46a0cb0e2 Mon Sep 17 00:00:00 2001 From: backb1 Date: Sat, 14 Dec 2013 16:11:33 +0100 Subject: [PATCH 021/140] Do not resolve addresses --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 52e6896..3d1876d 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1146,7 +1146,7 @@ class Controller( Node ): listening = self.cmd( "echo A | telnet -e A %s %d" % ( self.ip, self.port ) ) if 'Connected' in listening: - servers = self.cmd( 'netstat -atp' ).split( '\n' ) + servers = self.cmd( 'netstat -natp' ).split( '\n' ) pstr = ':%d ' % self.port clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ] raise Exception( "Please shut down the controller which is" From 2200d8d1736bd9c042653600e17d71fa9fbcbf00 Mon Sep 17 00:00:00 2001 From: Rich Lane Date: Fri, 13 Dec 2013 17:39:38 -0800 Subject: [PATCH 022/140] node: kill entire process group in terminate mnexec already puts the shell into its own process group. Killing the entire process group cleans up after any background processes the user left running. --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 52e6896..64f143a 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -184,7 +184,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 ): From 8bb830824b76e6fcc5f6bad6256b0fa8780c9eaa Mon Sep 17 00:00:00 2001 From: Baohua Yang Date: Wed, 11 Dec 2013 14:41:21 +0800 Subject: [PATCH 023/140] Close the file when done with it. --- mininet/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mininet/cli.py b/mininet/cli.py index 2ff9825..3df99ad 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -297,6 +297,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 ): From e6869112100f67bc0ee9466348d4118d0dc830dd Mon Sep 17 00:00:00 2001 From: Baohua Yang Date: Wed, 11 Dec 2013 15:19:24 +0800 Subject: [PATCH 024/140] Correct the comments of do_pingallfull() and do_iperfudp() --- mininet/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/cli.py b/mininet/cli.py index 3df99ad..08638c8 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -160,7 +160,7 @@ class CLI( Cmd ): 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 +187,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' ) From aee33863c8c24ac96702b151a7b59739ce2454c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20L=C3=A9one?= Date: Fri, 20 Dec 2013 14:41:22 +0100 Subject: [PATCH 025/140] fixup: Simplify_chained_comparaison --- examples/test/test_simpleperf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/test/test_simpleperf.py b/examples/test/test_simpleperf.py index 7d44c9c..0c57cef 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' ) From 824afb84c94b35fbc16c4c0e8ba66180913efe00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20L=C3=A9one?= Date: Fri, 20 Dec 2013 14:43:30 +0100 Subject: [PATCH 026/140] fixup: useless_parenthesis --- examples/nat.py | 2 +- mininet/net.py | 6 +++--- mininet/topo.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/nat.py b/examples/nat.py index f8fb0b1..dada363 100755 --- a/examples/nat.py +++ b/examples/nat.py @@ -59,7 +59,7 @@ 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 ) diff --git a/mininet/net.py b/mininet/net.py index 55bb1c8..91e3542 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -355,7 +355,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() @@ -463,13 +463,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 diff --git a/mininet/topo.py b/mininet/topo.py index 2ab455c..f9c421f 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -168,7 +168,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" From e10703cd7bfbeacd1588107ccb7d2ee49647c61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20L=C3=A9one?= Date: Fri, 20 Dec 2013 14:46:38 +0100 Subject: [PATCH 027/140] fixup: unresolved_references --- examples/test/test_hwintf.py | 8 +++++--- examples/test/test_sshd.py | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) 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_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 ): From 3780d9cda87516dc13b5d4b444ff6751231e6187 Mon Sep 17 00:00:00 2001 From: Baohua Yang Date: Wed, 11 Dec 2013 14:41:21 +0800 Subject: [PATCH 028/140] Close the file when done with it. --- mininet/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mininet/cli.py b/mininet/cli.py index 2ff9825..3df99ad 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -297,6 +297,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 ): From 24fe68d925dabb5f15c33e6eceae0a0a33ae594c Mon Sep 17 00:00:00 2001 From: Baohua Yang Date: Wed, 11 Dec 2013 15:19:24 +0800 Subject: [PATCH 029/140] Correct the comments of do_pingallfull() and do_iperfudp() --- mininet/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/cli.py b/mininet/cli.py index 3df99ad..08638c8 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -160,7 +160,7 @@ class CLI( Cmd ): 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 +187,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' ) From 9aefda7c1a51e71ec06c3c4152f10fbecabfa657 Mon Sep 17 00:00:00 2001 From: Gregory Gee Date: Thu, 2 Jan 2014 21:40:46 -0500 Subject: [PATCH 030/140] Uploading MiniEdit 2.1.0.7.1 Many changes since master version. - save/load topologies - host, controller, switch and link properties - link actions to set status up/down --- examples/miniedit.py | 2418 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 2343 insertions(+), 75 deletions(-) diff --git a/examples/miniedit.py b/examples/miniedit.py index 2883c1c..8b348be 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -6,30 +6,917 @@ 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/ """ -from Tkinter import Frame, Button, Label, Scrollbar, Canvas -from Tkinter import Menu, BitmapImage, PhotoImage, Wm, Toplevel +MINIEDIT_VERSION = '2.1.0.7.1' + +from optparse import OptionParser +from Tkinter import * +from tkMessageBox import showinfo, showerror, showwarning +from subprocess import call +import tkFont +import csv +import tkFileDialog +import tkSimpleDialog +import re +import json +from distutils.version import StrictVersion +import os +import sys + +if 'PYTHONPATH' in os.environ: + sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path # someday: from ttk import * -from mininet.log import setLogLevel -from mininet.net import Mininet -from mininet.util import ipStr +from mininet.log import info, error, debug, output, setLogLevel +from mininet.net import Mininet, VERSION +from mininet.util import ipStr, netParse, ipAdd, quietRun +from mininet.util import buildTopo from mininet.term import makeTerm, cleanUpScreens +from mininet.node import Controller, RemoteController, NOX, OVSController +from mininet.node import CPULimitedHost, Host +from mininet.node import OVSKernelSwitch, OVSSwitch +from mininet.link import TCLink, Intf +from mininet.cli import CLI +from mininet.moduledeps import moduleDeps, pathCheck +from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo +from mininet.topolib import TreeTopo + +print 'MiniEdit running against MiniNet '+VERSION +if StrictVersion(VERSION) > StrictVersion('2.0'): + from mininet.node import IVSSwitch + +TOPODEF = 'none' +TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), + 'linear': LinearTopo, + 'reversed': SingleSwitchReversedTopo, + 'single': SingleSwitchTopo, + 'none': None, + 'tree': TreeTopo } +CONTROLLERDEF = 'ref' +CONTROLLERS = { 'ref': Controller, + 'ovsc': OVSController, + 'nox': NOX, + 'remote': RemoteController, + 'none': lambda name: None } + +class InbandController( RemoteController ): + + def checkListening( self ): + "Overridden to do nothing." + return + +class customOvs(OVSSwitch): + + def __init__( self, name, failMode='secure', datapath='kernel', **params ): + OVSSwitch.__init__( self, name, **params ) + self.switchIP = None + + def getSwitchIP(self): + return self.switchIP + + def setSwitchIP(self, ip): + self.switchIP = ip + + def getOpenFlowVersion(self): + return self.openFlowVersions + + def setOpenFlowVersion(self, versions): + self.openFlowVersions = [] + if versions['ovsOf10'] == '1': + self.openFlowVersions.append('OpenFlow10') + if versions['ovsOf11'] == '1': + self.openFlowVersions.append('OpenFlow11') + if versions['ovsOf12'] == '1': + self.openFlowVersions.append('OpenFlow12') + if versions['ovsOf13'] == '1': + self.openFlowVersions.append('OpenFlow13') + + def configureOpenFlowVersion(self): + if not ( 'OpenFlow11' in self.openFlowVersions or + 'OpenFlow12' in self.openFlowVersions or + 'OpenFlow13' in self.openFlowVersions ): + return + + protoList = ",".join(self.openFlowVersions) + print 'Configuring OpenFlow to '+protoList + self.cmd( 'ovs-vsctl -- set bridge', self, 'protocols='+protoList) + + def start( self, controllers ): + # Call superclass constructor + OVSSwitch.start( self, controllers ) + # Set OpenFlow Versions + self.configureOpenFlowVersion() + # Set Switch IP address + if (self.switchIP is not None): + self.cmd( 'ifconfig', self, self.switchIP ) + +class PrefsDialog(tkSimpleDialog.Dialog): + + def __init__(self, parent, title, prefDefaults): + + self.prefValues = prefDefaults + + tkSimpleDialog.Dialog.__init__(self, parent, title) + + def body(self, master): + self.rootFrame = master + self.leftfieldFrame = Frame(self.rootFrame, padx=5, pady=5) + self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2) + self.rightfieldFrame = Frame(self.rootFrame, padx=5, pady=5) + self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2) + + + # Field for Base IP + Label(self.leftfieldFrame, text="IP Base:").grid(row=0, sticky=E) + self.ipEntry = Entry(self.leftfieldFrame) + self.ipEntry.grid(row=0, column=1) + ipBase = self.prefValues['ipBase'] + self.ipEntry.insert(0, ipBase) + + # Selection of terminal type + Label(self.leftfieldFrame, text="Default Terminal:").grid(row=1, sticky=E) + self.terminalVar = StringVar(self.leftfieldFrame) + self.terminalOption = OptionMenu(self.leftfieldFrame, self.terminalVar, "xterm", "gterm") + self.terminalOption.grid(row=1, column=1, sticky=W) + terminalType = self.prefValues['terminalType'] + self.terminalVar.set(terminalType) + + # Field for CLI + Label(self.leftfieldFrame, text="Start CLI:").grid(row=2, sticky=E) + self.cliStart = IntVar() + self.cliButton = Checkbutton(self.leftfieldFrame, variable=self.cliStart) + self.cliButton.grid(row=2, column=1, sticky=W) + if self.prefValues['startCLI'] == '0': + self.cliButton.deselect() + else: + self.cliButton.select() + + # Selection of switch type + Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E) + self.switchType = StringVar(self.leftfieldFrame) + self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch", "Indigo Virtual Switch") + self.switchTypeMenu.grid(row=3, column=1, sticky=W) + switchTypePref = self.prefValues['switchType'] + if switchTypePref == 'ivs': + self.switchType.set("Indigo Virtual Switch") + else: + self.switchType.set("Open vSwitch") + + + # Fields for OVS OpenFlow version + ovsFrame= LabelFrame(self.leftfieldFrame, text='Open vSwitch', padx=5, pady=5) + ovsFrame.grid(row=4, column=0, columnspan=2, sticky=EW) + Label(ovsFrame, text="OpenFlow 1.0:").grid(row=0, sticky=E) + Label(ovsFrame, text="OpenFlow 1.1:").grid(row=1, sticky=E) + Label(ovsFrame, text="OpenFlow 1.2:").grid(row=2, sticky=E) + Label(ovsFrame, text="OpenFlow 1.3:").grid(row=3, sticky=E) + + self.ovsOf10 = IntVar() + self.covsOf10 = Checkbutton(ovsFrame, variable=self.ovsOf10) + self.covsOf10.grid(row=0, column=1, sticky=W) + if self.prefValues['openFlowVersions']['ovsOf10'] == '0': + self.covsOf10.deselect() + else: + self.covsOf10.select() + + self.ovsOf11 = IntVar() + self.covsOf11 = Checkbutton(ovsFrame, variable=self.ovsOf11) + self.covsOf11.grid(row=1, column=1, sticky=W) + if self.prefValues['openFlowVersions']['ovsOf11'] == '0': + self.covsOf11.deselect() + else: + self.covsOf11.select() + + self.ovsOf12 = IntVar() + self.covsOf12 = Checkbutton(ovsFrame, variable=self.ovsOf12) + self.covsOf12.grid(row=2, column=1, sticky=W) + if self.prefValues['openFlowVersions']['ovsOf12'] == '0': + self.covsOf12.deselect() + else: + self.covsOf12.select() + + self.ovsOf13 = IntVar() + self.covsOf13 = Checkbutton(ovsFrame, variable=self.ovsOf13) + self.covsOf13.grid(row=3, column=1, sticky=W) + if self.prefValues['openFlowVersions']['ovsOf13'] == '0': + self.covsOf13.deselect() + else: + self.covsOf13.select() + + # Field for DPCTL listen port + Label(self.leftfieldFrame, text="dpctl port:").grid(row=5, sticky=E) + self.dpctlEntry = Entry(self.leftfieldFrame) + self.dpctlEntry.grid(row=5, column=1) + if 'dpctl' in self.prefValues: + self.dpctlEntry.insert(0, self.prefValues['dpctl']) + + # sFlow + sflowValues = self.prefValues['sflow'] + self.sflowFrame= LabelFrame(self.rightfieldFrame, text='sFlow Profile for Open vSwitch', padx=5, pady=5) + self.sflowFrame.grid(row=0, column=0, columnspan=2, sticky=EW) + + Label(self.sflowFrame, text="Target:").grid(row=0, sticky=E) + self.sflowTarget = Entry(self.sflowFrame) + self.sflowTarget.grid(row=0, column=1) + self.sflowTarget.insert(0, sflowValues['sflowTarget']) + + Label(self.sflowFrame, text="Sampling:").grid(row=1, sticky=E) + self.sflowSampling = Entry(self.sflowFrame) + self.sflowSampling.grid(row=1, column=1) + self.sflowSampling.insert(0, sflowValues['sflowSampling']) + + Label(self.sflowFrame, text="Header:").grid(row=2, sticky=E) + self.sflowHeader = Entry(self.sflowFrame) + self.sflowHeader.grid(row=2, column=1) + self.sflowHeader.insert(0, sflowValues['sflowHeader']) + + Label(self.sflowFrame, text="Polling:").grid(row=3, sticky=E) + self.sflowPolling = Entry(self.sflowFrame) + self.sflowPolling.grid(row=3, column=1) + self.sflowPolling.insert(0, sflowValues['sflowPolling']) + + # NetFlow + nflowValues = self.prefValues['netflow'] + self.nFrame= LabelFrame(self.rightfieldFrame, text='NetFlow Profile for Open vSwitch', padx=5, pady=5) + self.nFrame.grid(row=1, column=0, columnspan=2, sticky=EW) + + Label(self.nFrame, text="Target:").grid(row=0, sticky=E) + self.nflowTarget = Entry(self.nFrame) + self.nflowTarget.grid(row=0, column=1) + self.nflowTarget.insert(0, nflowValues['nflowTarget']) + + Label(self.nFrame, text="Active Timeout:").grid(row=1, sticky=E) + self.nflowTimeout = Entry(self.nFrame) + self.nflowTimeout.grid(row=1, column=1) + self.nflowTimeout.insert(0, nflowValues['nflowTimeout']) + + Label(self.nFrame, text="Add ID to Interface:").grid(row=2, sticky=E) + self.nflowAddId = IntVar() + self.nflowAddIdButton = Checkbutton(self.nFrame, variable=self.nflowAddId) + self.nflowAddIdButton.grid(row=2, column=1, sticky=W) + if nflowValues['nflowAddId'] == '0': + self.nflowAddIdButton.deselect() + else: + self.nflowAddIdButton.select() + + # initial focus + return self.ipEntry + + def apply(self): + ipBase = self.ipEntry.get() + terminalType = self.terminalVar.get() + startCLI = str(self.cliStart.get()) + sw = self.switchType.get() + dpctl = self.dpctlEntry.get() + + ovsOf10 = str(self.ovsOf10.get()) + ovsOf11 = str(self.ovsOf11.get()) + ovsOf12 = str(self.ovsOf12.get()) + ovsOf13 = str(self.ovsOf13.get()) + + sflowValues = {'sflowTarget':self.sflowTarget.get(), + 'sflowSampling':self.sflowSampling.get(), + 'sflowHeader':self.sflowHeader.get(), + 'sflowPolling':self.sflowPolling.get()} + nflowvalues = {'nflowTarget':self.nflowTarget.get(), + 'nflowTimeout':self.nflowTimeout.get(), + 'nflowAddId':str(self.nflowAddId.get())} + self.result = {'ipBase':ipBase, + 'terminalType':terminalType, + 'dpctl':dpctl, + 'sflow':sflowValues, + 'netflow':nflowvalues, + 'startCLI':startCLI} + if sw == 'Indigo Virtual Switch': + self.result['switchType'] = 'ivs' + if StrictVersion(VERSION) < StrictVersion('2.1'): + self.ovsOk = False + showerror(title="Error", + message='MiniNet version 2.1+ required. You have '+VERSION+'.') + else: + self.result['switchType'] = 'ovs' + + self.ovsOk = True + if ovsOf11 == "1": + ovsVer = self.getOvsVersion() + if StrictVersion(ovsVer) < StrictVersion('2.0'): + self.ovsOk = False + showerror(title="Error", + message='Open vSwitch version 2.0+ required. You have '+ovsVer+'.') + if ovsOf12 == "1" or ovsOf13 == "1": + ovsVer = self.getOvsVersion() + if StrictVersion(ovsVer) < StrictVersion('1.10'): + self.ovsOk = False + showerror(title="Error", + message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.') + + if self.ovsOk: + self.result['openFlowVersions']={'ovsOf10':ovsOf10, + 'ovsOf11':ovsOf11, + 'ovsOf12':ovsOf12, + 'ovsOf13':ovsOf13} + else: + self.result = None + + def getOvsVersion(self): + outp = quietRun("ovs-vsctl show") + r = r'ovs_version: "(.*)"' + m = re.search(r, outp) + if m is None: + print 'Version check failed' + return None + else: + print 'Open vSwitch version is '+m.group(1) + return m.group(1) + + +class CustomDialog(object): + + # TODO: Fix button placement and Title and window focus lock + def __init__(self, master, title): + self.top=Toplevel(master) + + self.bodyFrame = Frame(self.top) + self.bodyFrame.grid(row=0, column=0, sticky='nswe') + self.body(self.bodyFrame) + + #return self.b # initial focus + buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey') + buttonFrame.grid(row=1 , column=0, sticky='nswe') + + okButton = Button(buttonFrame, width=8, text='OK', relief='groove', + bd=4, command=self.okAction) + okButton.grid(row=0, column=0, sticky=E) + + canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove', + bd=4, command=self.cancelAction) + canlceButton.grid(row=0, column=1, sticky=W) + + def body(self, master): + self.rootFrame = master + + def apply(self): + self.top.destroy() + + def cancelAction(self): + self.top.destroy() + + def okAction(self): + self.apply() + self.top.destroy() + +class HostDialog(CustomDialog): + + def __init__(self, master, title, prefDefaults): + + self.prefValues = prefDefaults + self.result = None + + CustomDialog.__init__(self, master, title) + + def body(self, master): + self.rootFrame = master + self.leftfieldFrame = Frame(self.rootFrame) + self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2) + self.rightfieldFrame = Frame(self.rootFrame) + self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2) + + # Field for Hostname + Label(self.leftfieldFrame, text="Hostname:").grid(row=0, sticky=E) + self.hostnameEntry = Entry(self.leftfieldFrame) + self.hostnameEntry.grid(row=0, column=1) + if 'hostname' in self.prefValues: + self.hostnameEntry.insert(0, self.prefValues['hostname']) + + # Field for Switch IP + Label(self.leftfieldFrame, text="IP Address:").grid(row=1, sticky=E) + self.ipEntry = Entry(self.leftfieldFrame) + self.ipEntry.grid(row=1, column=1) + if 'ip' in self.prefValues: + self.ipEntry.insert(0, self.prefValues['ip']) + + # Field for default route + Label(self.leftfieldFrame, text="Default Route:").grid(row=2, sticky=E) + self.routeEntry = Entry(self.leftfieldFrame) + self.routeEntry.grid(row=2, column=1) + if 'defaultRoute' in self.prefValues: + self.routeEntry.insert(0, self.prefValues['defaultRoute']) + + # Field for CPU + Label(self.rightfieldFrame, text="Amount CPU:").grid(row=0, sticky=E) + self.cpuEntry = Entry(self.rightfieldFrame) + self.cpuEntry.grid(row=0, column=1) + if 'cpu' in self.prefValues: + self.cpuEntry.insert(0, str(self.prefValues['cpu'])) + # Selection of Scheduler + if 'sched' in self.prefValues: + sched = self.prefValues['sched'] + else: + sched = 'host' + self.schedVar = StringVar(self.rightfieldFrame) + self.schedOption = OptionMenu(self.rightfieldFrame, self.schedVar, "host", "cfs", "rt") + self.schedOption.grid(row=0, column=2, sticky=W) + self.schedVar.set(sched) + + # Selection of Cores + Label(self.rightfieldFrame, text="Cores:").grid(row=1, sticky=E) + self.coreEntry = Entry(self.rightfieldFrame) + self.coreEntry.grid(row=1, column=1) + if 'cores' in self.prefValues: + self.coreEntry.insert(1, self.prefValues['cores']) + + # External Interfaces + self.externalInterfaces = 0 + Label(self.rootFrame, text="External Interface:").grid(row=1, column=0, sticky=E) + self.b = Button( self.rootFrame, text='Add', command=self.addInterface) + self.b.grid(row=1, column=1) + + self.interfaceFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=1, title='External Interfaces') + self.interfaceFrame.grid(row=2, column=0, sticky='nswe', columnspan=2) + self.tableFrame = self.interfaceFrame.interior + self.tableFrame.addRow(value=['Interface Name'], readonly=True) + + # Add defined interfaces + externalInterfaces = [] + if 'externalInterfaces' in self.prefValues: + externalInterfaces = self.prefValues['externalInterfaces'] + + for externalInterface in externalInterfaces: + self.tableFrame.addRow(value=[externalInterface]) + + # VLAN Interfaces + self.vlanInterfaces = 0 + Label(self.rootFrame, text="VLAN Interface:").grid(row=1, column=2, sticky=E) + self.vlanButton = Button( self.rootFrame, text='Add', command=self.addVlanInterface) + self.vlanButton.grid(row=1, column=3) + + self.vlanFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=2, title='VLAN Interfaces') + self.vlanFrame.grid(row=2, column=2, sticky='nswe', columnspan=2) + self.vlanTableFrame = self.vlanFrame.interior + self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True) + + vlanInterfaces = [] + if 'vlanInterfaces' in self.prefValues: + vlanInterfaces = self.prefValues['vlanInterfaces'] + for vlanInterface in vlanInterfaces: + self.vlanTableFrame.addRow(value=vlanInterface) + + def addVlanInterface( self ): + self.vlanTableFrame.addRow() + + def addInterface( self ): + self.tableFrame.addRow() + + def apply(self): + externalInterfaces = [] + for row in range(self.tableFrame.rows): + if (len(self.tableFrame.get(row, 0)) > 0 and + row > 0): + externalInterfaces.append(self.tableFrame.get(row, 0)) + vlanInterfaces = [] + for row in range(self.vlanTableFrame.rows): + if (len(self.vlanTableFrame.get(row, 0)) > 0 and + len(self.vlanTableFrame.get(row, 1)) > 0 and + row > 0): + vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)]) + + results = {'cpu': self.cpuEntry.get(), + 'cores':self.coreEntry.get(), + 'sched':self.schedVar.get(), + 'hostname':self.hostnameEntry.get(), + 'ip':self.ipEntry.get(), + 'defaultRoute':self.routeEntry.get(), + 'externalInterfaces':externalInterfaces, + 'vlanInterfaces':vlanInterfaces} + self.result = results + +class SwitchDialog(CustomDialog): + + def __init__(self, master, title, prefDefaults): + + self.prefValues = prefDefaults + self.result = None + CustomDialog.__init__(self, master, title) + + def body(self, master): + self.rootFrame = master + + rowCount = 0 + externalInterfaces = [] + if 'externalInterfaces' in self.prefValues: + externalInterfaces = self.prefValues['externalInterfaces'] + + self.fieldFrame = Frame(self.rootFrame) + self.fieldFrame.grid(row=0, column=0, sticky='nswe') + + # Field for Hostname + Label(self.fieldFrame, text="Hostname:").grid(row=rowCount, sticky=E) + self.hostnameEntry = Entry(self.fieldFrame) + self.hostnameEntry.grid(row=rowCount, column=1) + self.hostnameEntry.insert(0, self.prefValues['hostname']) + rowCount+=1 + + # Field for DPID + Label(self.fieldFrame, text="DPID:").grid(row=rowCount, sticky=E) + self.dpidEntry = Entry(self.fieldFrame) + self.dpidEntry.grid(row=rowCount, column=1) + if 'dpid' in self.prefValues: + self.dpidEntry.insert(0, self.prefValues['dpid']) + rowCount+=1 + + # Field for Netflow + Label(self.fieldFrame, text="Enable NetFlow:").grid(row=rowCount, sticky=E) + self.nflow = IntVar() + self.nflowButton = Checkbutton(self.fieldFrame, variable=self.nflow) + self.nflowButton.grid(row=rowCount, column=1, sticky=W) + if 'netflow' in self.prefValues: + if self.prefValues['netflow'] == '0': + self.nflowButton.deselect() + else: + self.nflowButton.select() + else: + self.nflowButton.deselect() + rowCount+=1 + + # Field for sflow + Label(self.fieldFrame, text="Enable sFlow:").grid(row=rowCount, sticky=E) + self.sflow = IntVar() + self.sflowButton = Checkbutton(self.fieldFrame, variable=self.sflow) + self.sflowButton.grid(row=rowCount, column=1, sticky=W) + if 'sflow' in self.prefValues: + if self.prefValues['sflow'] == '0': + self.sflowButton.deselect() + else: + self.sflowButton.select() + else: + self.sflowButton.deselect() + rowCount+=1 + + # Selection of switch type + Label(self.fieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E) + self.switchType = StringVar(self.fieldFrame) + self.switchTypeMenu = OptionMenu(self.fieldFrame, self.switchType, "Default", "Open vSwitch", "Indigo Virtual Switch") + self.switchTypeMenu.grid(row=rowCount, column=1, sticky=W) + if 'switchType' in self.prefValues: + switchTypePref = self.prefValues['switchType'] + if switchTypePref == 'ivs': + self.switchType.set("Indigo Virtual Switch") + elif switchTypePref == 'ovs': + self.switchType.set("Open vSwitch") + else: + self.switchType.set("Default") + else: + self.switchType.set("Default") + rowCount+=1 + + # Field for Switch IP + Label(self.fieldFrame, text="IP Address:").grid(row=rowCount, sticky=E) + self.ipEntry = Entry(self.fieldFrame) + self.ipEntry.grid(row=rowCount, column=1) + if 'switchIP' in self.prefValues: + self.ipEntry.insert(0, self.prefValues['switchIP']) + rowCount+=1 + + # Field for DPCTL port + Label(self.fieldFrame, text="DPCTL port:").grid(row=rowCount, sticky=E) + self.dpctlEntry = Entry(self.fieldFrame) + self.dpctlEntry.grid(row=rowCount, column=1) + if 'dpctl' in self.prefValues: + self.dpctlEntry.insert(0, self.prefValues['dpctl']) + rowCount+=1 + + # External Interfaces + Label(self.fieldFrame, text="External Interface:").grid(row=rowCount, sticky=E) + self.b = Button( self.fieldFrame, text='Add', command=self.addInterface) + self.b.grid(row=rowCount, column=1) + + self.interfaceFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=1, title='External Interfaces') + self.interfaceFrame.grid(row=2, column=0, sticky='nswe') + self.tableFrame = self.interfaceFrame.interior + + # Add defined interfaces + for externalInterface in externalInterfaces: + self.tableFrame.addRow(value=[externalInterface]) + rowCount+=1 + + def addInterface( self ): + self.tableFrame.addRow() + + def defaultDpid( self ,name): + "Derive dpid from switch name, s1 -> 1" + try: + dpid = int( re.findall( r'\d+', name )[ 0 ] ) + dpid = hex( dpid )[ 2: ] + return dpid + except IndexError: + return None + #raise Exception( 'Unable to derive default datapath ID - ' + # 'please either specify a dpid or use a ' + # 'canonical switch name such as s23.' ) + + def apply(self): + externalInterfaces = [] + for row in range(self.tableFrame.rows): + #print 'Interface is ' + self.tableFrame.get(row, 0) + if (len(self.tableFrame.get(row, 0)) > 0): + externalInterfaces.append(self.tableFrame.get(row, 0)) + + dpid = self.dpidEntry.get() + if (self.defaultDpid(self.hostnameEntry.get()) is None + and len(dpid) == 0): + showerror(title="Error", + message= 'Unable to derive default datapath ID - ' + 'please either specify a DPID or use a ' + 'canonical switch name such as s23.' ) + + + results = {'externalInterfaces':externalInterfaces, + 'hostname':self.hostnameEntry.get(), + 'dpid':dpid, + 'sflow':str(self.sflow.get()), + 'netflow':str(self.nflow.get()), + 'dpctl':self.dpctlEntry.get(), + 'switchIP':self.ipEntry.get()} + sw = self.switchType.get() + if sw == 'Indigo Virtual Switch': + results['switchType'] = 'ivs' + if StrictVersion(VERSION) < StrictVersion('2.1'): + self.ovsOk = False + showerror(title="Error", + message='MiniNet version 2.1+ required. You have '+VERSION+'.') + elif sw == 'Open vSwitch': + results['switchType'] = 'ovs' + else: + results['switchType'] = 'default' + self.result = results + + +class VerticalScrolledTable(LabelFrame): + """A pure Tkinter scrollable frame that actually works! + + * Use the 'interior' attribute to place widgets inside the scrollable frame + * Construct and pack/place/grid normally + * This frame only allows vertical scrolling + + """ + def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw): + LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw) + + # create a canvas object and a vertical scrollbar for scrolling it + vscrollbar = Scrollbar(self, orient=VERTICAL) + vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) + canvas = Canvas(self, bd=0, highlightthickness=0, + yscrollcommand=vscrollbar.set) + canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) + vscrollbar.config(command=canvas.yview) + + # reset the view + canvas.xview_moveto(0) + canvas.yview_moveto(0) + + # create a frame inside the canvas which will be scrolled with it + self.interior = interior = TableFrame(canvas, rows=rows, columns=columns) + interior_id = canvas.create_window(0, 0, window=interior, + anchor=NW) + + # track changes to the canvas and frame width and sync them, + # also updating the scrollbar + def _configure_interior(event): + # update the scrollbars to match the size of the inner frame + size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) + canvas.config(scrollregion="0 0 %s %s" % size) + if interior.winfo_reqwidth() != canvas.winfo_width(): + # update the canvas's width to fit the inner frame + canvas.config(width=interior.winfo_reqwidth()) + interior.bind('', _configure_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 MiniEdit( Frame ): "A simple network editor for Mininet." - def __init__( self, parent=None, cheight=200, cwidth=500 ): + def __init__( self, parent=None, cheight=400, cwidth=800 ): + + self.defaultIpBase='10.0.0.0/8' + + self.nflowDefaults = {'nflowTarget':'', + 'nflowTimeout':'600', + 'nflowAddId':'0'} + self.sflowDefaults = {'sflowTarget':'', + 'sflowSampling':'400', + 'sflowHeader':'128', + 'sflowPolling':'30'} + + self.appPrefs={ + "ipBase": self.defaultIpBase, + "startCLI": "0", + "terminalType": 'xterm', + "switchType": 'ovs', + "dpctl": '', + 'sflow':self.sflowDefaults, + 'netflow':self.nflowDefaults, + 'openFlowVersions':{'ovsOf10':'1', + 'ovsOf11':'0', + 'ovsOf12':'0', + 'ovsOf13':'0'} + + } + Frame.__init__( self, parent ) self.action = None self.appName = 'MiniEdit' + self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" ) # Style self.font = ( 'Geneva', 9 ) @@ -47,11 +934,14 @@ 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', 'Link', 'Controller' ) self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' } self.toolbar = self.createToolbar() @@ -67,7 +957,7 @@ class MiniEdit( Frame ): # Initialize node data self.nodeBindings = self.createNodeBindings() - self.nodePrefixes = { 'Switch': 's', 'Host': 'h' } + self.nodePrefixes = { 'Switch': 's', 'Host': 'h' , 'Controller': 'c'} self.widgetToItem = {} self.itemToWidget = {} @@ -83,13 +973,54 @@ 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.switchPopup = Menu(self.top, tearoff=0) + self.switchPopup.add_command(label='Swtich Options', font=self.font) + self.switchPopup.add_separator() + self.switchPopup.add_command(label='Properties', font=self.font, command=self.switchDetails ) + + self.switchRunPopup = Menu(self.top, tearoff=0) + self.switchRunPopup.add_command(label='Swtich Options', font=self.font) + self.switchRunPopup.add_separator() + self.switchRunPopup.add_command(label='List bridge details', font=self.font, command=self.listBridge ) + + self.linkPopup = Menu(self.top, tearoff=0) + self.linkPopup.add_command(label='Link Options', font=self.font) + self.linkPopup.add_separator() + self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails ) + + self.linkRunPopup = Menu(self.top, tearoff=0) + self.linkRunPopup.add_command(label='Link Options', font=self.font) + self.linkRunPopup.add_separator() + self.linkRunPopup.add_command(label='Link Up', font=self.font, command=self.linkUp ) + self.linkRunPopup.add_command(label='Link Down', font=self.font, command=self.linkDown ) + + self.controllerPopup = Menu(self.top, tearoff=0) + self.controllerPopup.add_command(label='Controller Options', font=self.font) + self.controllerPopup.add_separator() + self.controllerPopup.add_command(label='Properties', font=self.font, command=self.controllerDetails ) + + # Event handling initalization self.linkx = self.linky = self.linkItem = None self.lastSelection = None # Model initialization self.links = {} - self.nodeCount = 0 + self.hostOpts = {} + self.switchOpts = {} + self.hostCount = 0 + self.switchCount = 0 + self.controllerCount = 0 self.net = None # Close window gracefully @@ -108,34 +1039,35 @@ 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 ): @@ -241,6 +1173,515 @@ 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'] + self.addNode('Switch', nodeNum, float(x), float(y), name=hostname) + self.switchOpts[hostname] = switch['opts'] + icon = self.findWidgetByName(hostname) + icon.bind('', self.do_switchPopup ) + + # create links to controllers + if (int(loadedTopology['version']) > 1): + controllers = self.switchOpts[hostname]['controllers'] + for controller in controllers: + dest = self.findWidgetByName(controller) + dx, dy = self.canvas.coords( self.widgetToItem[ dest ] ) + self.link = self.canvas.create_line(float(x), + float(y), + dx, + dy, + width=4, + fill='red', + dash=(6, 4, 2, 4), + tag='link' ) + c.itemconfig(self.link, tags=c.gettags(self.link)+('control',)) + self.addLink( icon, dest, linktype='control' ) + self.createControlLinkBindings() + self.link = self.linkWidget = None + else: + dest = self.findWidgetByName('c0') + dx, dy = self.canvas.coords( self.widgetToItem[ dest ] ) + self.link = self.canvas.create_line(float(x), + float(y), + dx, + dy, + width=4, + fill='red', + dash=(6, 4, 2, 4), + tag='link' ) + c.itemconfig(self.link, tags=c.gettags(self.link)+('control',)) + self.addLink( icon, dest, linktype='control' ) + self.createControlLinkBindings() + self.link = self.linkWidget = None + + # Load links + links = loadedTopology['links'] + for link in links: + srcNode = link['src'] + src = self.findWidgetByName(srcNode) + sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) + + destNode = link['dest'] + dest = self.findWidgetByName(destNode) + dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) + + self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, + fill='blue', tag='link' ) + c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) + self.addLink( src, dest, linkopts=link['opts'] ) + self.createDataLinkBindings() + self.link = self.linkWidget = None + + f.close + + def findWidgetByName( self, name ): + for widget in self.widgetToItem: + if name == widget[ 'text' ]: + return widget + + def newTopology( self ): + "New command." + for widget in self.widgetToItem.keys(): + self.deleteItem( self.widgetToItem[ widget ] ) + self.hostCount = 0 + self.switchCount = 0 + self.controllerCount = 0 + self.links = {} + self.hostOpts = {} + self.switchOpts = {} + self.controllers = {} + self.appPrefs["ipBase"]= self.defaultIpBase + + def saveTopology( self ): + "Save command." + myFormats = [ + ('Mininet Topology','*.mn'), + ('All Files','*'), + ] + + savingDictionary = {} + fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...") + if len(fileName ) > 0: + # Save Application preferences + savingDictionary['version'] = '2' + + # Save Switches and Hosts + hostsToSave = [] + switchesToSave = [] + controllersToSave = [] + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) + if 'Switch' in tags: + nodeNum = self.switchOpts[name]['nodeNum'] + nodeToSave = {'number':str(nodeNum), + 'x':str(x1), + 'y':str(y1), + 'opts':self.switchOpts[name] } + switchesToSave.append(nodeToSave) + elif 'Host' in tags: + nodeNum = self.hostOpts[name]['nodeNum'] + nodeToSave = {'number':str(nodeNum), + 'x':str(x1), + 'y':str(y1), + 'opts':self.hostOpts[name] } + hostsToSave.append(nodeToSave) + elif 'Controller' in tags: + nodeToSave = {'x':str(x1), + 'y':str(y1), + 'opts':self.controllers[name] } + controllersToSave.append(nodeToSave) + else: + raise Exception( "Cannot create mystery node: " + name ) + savingDictionary['hosts'] = hostsToSave + savingDictionary['switches'] = switchesToSave + savingDictionary['controllers'] = controllersToSave + + # Save Links + linksToSave = [] + for link in self.links.values(): + src = link['src'] + dst = link['dest'] + linkopts = link['linkOpts'] + + srcName, dstName = src[ 'text' ], dst[ 'text' ] + linkToSave = {'src':srcName, + 'dest':dstName, + 'opts':linkopts} + if link['type'] == 'data': + linksToSave.append(linkToSave) + savingDictionary['links'] = linksToSave + + # Save Application preferences + savingDictionary['application'] = self.appPrefs + + try: + f = open(fileName, 'wb') + #f.write(str(savingDictionary)) + f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': '))) + except Exception as er: + print er + finally: + f.close() + + def exportTopology( self ): + "Export command." + myFormats = [ + ('Mininet Custom Topology','*.py'), + ('All Files','*'), + ] + + fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...") + if len(fileName ) > 0: + #print "Now saving under %s" % fileName + f = open(fileName, 'wb') + + f.write("#!/usr/bin/python\n") + f.write("\n") + f.write("from mininet.net import Mininet\n") + f.write("from mininet.node import Controller, RemoteController, OVSController\n") + f.write("from mininet.node import CPULimitedHost, Host\n") + f.write("from mininet.node import OVSKernelSwitch\n") + if StrictVersion(VERSION) > StrictVersion('2.0'): + f.write("from mininet.node import IVSSwitch\n") + f.write("from mininet.cli import CLI\n") + f.write("from mininet.log import setLogLevel, info\n") + f.write("from mininet.link import TCLink, Intf\n") + + inBandCtrl = False + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + + if 'Controller' in tags: + opts = self.controllers[name] + controllerType = opts['controllerType'] + if controllerType == 'inband': + inBandCtrl = True + + if inBandCtrl == True: + f.write("\n") + f.write("class InbandController( RemoteController ):\n") + f.write("\n") + f.write(" def checkListening( self ):\n") + f.write(" \"Overridden to do nothing.\"\n") + f.write(" return\n") + + f.write("\n") + f.write("def myNetwork():\n") + f.write("\n") + f.write(" net = Mininet( topo=None,\n") + if len(self.appPrefs['dpctl']) > 0: + f.write(" listenPort="+self.appPrefs['dpctl']+",\n") + f.write(" build=False,\n") + f.write(" ipBase='"+self.appPrefs['ipBase']+"',\n") + f.write(" link=TCLink)\n") + f.write("\n") + f.write(" info( '*** Adding controller\\n' )\n") + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + + if 'Controller' in tags: + opts = self.controllers[name] + controllerType = opts['controllerType'] + controllerIP = opts['remoteIP'] + controllerPort = opts['remotePort'] + + + f.write(" "+name+"=net.addController(name='"+name+"',\n") + + if controllerType == 'remote': + f.write(" controller=RemoteController,\n") + f.write(" ip='"+controllerIP+"',\n") + elif controllerType == 'inband': + f.write(" controller=InbandController,\n") + f.write(" ip='"+controllerIP+"',\n") + elif controllerType == 'ovsc': + f.write(" controller=OVSController,\n") + else: + f.write(" controller=Controller,\n") + + f.write(" port="+str(controllerPort)+")\n") + f.write("\n") + + # Save Switches and Hosts + f.write(" info( '*** Add switches\\n')\n") + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + if 'Switch' in tags: + opts = self.switchOpts[name] + nodeNum = opts['nodeNum'] + f.write(" "+name+" = net.addSwitch('"+name+"'") + if opts['switchType'] == 'default': + if self.appPrefs['switchType'] == 'ivs': + f.write(", cls=IVSSwitch") + else: + f.write(", cls=OVSKernelSwitch") + elif opts['switchType'] == 'ivs': + f.write(", cls=IVSSwitch") + else: + f.write(", cls=OVSKernelSwitch") + if 'dpctl' in opts: + f.write(", listenPort="+opts['dpctl']) + if 'dpid' in opts: + f.write(", dpid='"+opts['dpid']+"'") + f.write(")\n") + if ('externalInterfaces' in opts): + for extInterface in opts['externalInterfaces']: + f.write(" Intf( '"+extInterface+"', node="+name+" )\n") + + f.write("\n") + f.write(" info( '*** Add hosts\\n')\n") + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + if 'Host' in tags: + opts = self.hostOpts[name] + ip = None + defaultRoute = None + if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: + defaultRoute = "'via "+opts['defaultRoute']+"'" + else: + defaultRoute = 'None' + if 'ip' in opts and len(opts['ip']) > 0: + ip = opts['ip'] + else: + nodeNum = self.hostOpts[name]['nodeNum'] + ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) + ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) + + if 'cores' in opts or 'cpu' in opts: + f.write(" "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n") + if 'cores' in opts: + f.write(" "+name+".setCPUs(cores='"+opts['cores']+"')\n") + if 'cpu' in opts: + f.write(" "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n") + else: + f.write(" "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n") + if ('externalInterfaces' in opts): + for extInterface in opts['externalInterfaces']: + f.write(" Intf( '"+extInterface+"', node="+name+" )\n") + f.write("\n") + + # Save Links + f.write(" info( '*** Add links\\n')\n") + for key,linkDetail in self.links.iteritems(): + tags = self.canvas.gettags(key) + if 'data' in tags: + optsExist = False + src = linkDetail['src'] + dst = linkDetail['dest'] + linkopts = linkDetail['linkOpts'] + srcName, dstName = src[ 'text' ], dst[ 'text' ] + bw = '' + delay = '' + loss = '' + max_queue_size = '' + linkOpts = "{" + if 'bw' in linkopts: + bw = linkopts['bw'] + linkOpts = linkOpts + "'bw':"+str(bw) + optsExist = True + if 'delay' in linkopts: + delay = linkopts['delay'] + if optsExist: + linkOpts = linkOpts + "," + linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'" + optsExist = True + if 'loss' in linkopts: + if optsExist: + linkOpts = linkOpts + "," + linkOpts = linkOpts + "'loss':"+str(linkopts['loss']) + optsExist = True + if 'max_queue_size' in linkopts: + if optsExist: + linkOpts = linkOpts + "," + linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size']) + optsExist = True + if 'jitter' in linkopts: + if optsExist: + linkOpts = linkOpts + "," + linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'" + optsExist = True + if 'speedup' in linkopts: + if optsExist: + linkOpts = linkOpts + "," + linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup']) + optsExist = True + + linkOpts = linkOpts + "}" + if optsExist: + f.write(" "+srcName+dstName+" = "+linkOpts+"\n") + f.write(" net.addLink("+srcName+", "+dstName) + if optsExist: + f.write(", **"+srcName+dstName) + f.write(")\n") + + f.write("\n") + f.write(" info( '*** Starting network\\n')\n") + f.write(" net.build()\n") + + f.write(" info( '*** Starting controllers\\n')\n") + f.write(" for controller in net.controllers:\n") + f.write(" controller.start()\n") + f.write("\n") + + f.write(" info( '*** Starting switches\\n')\n") + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + if 'Switch' in tags: + opts = self.switchOpts[name] + ctrlList = ",".join(opts['controllers']) + f.write(" net.get('"+name+"').start(["+ctrlList+"])\n") + + f.write("\n") + + f.write(" info( '*** Configuring switches\\n')\n") + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + if 'Switch' in tags: + opts = self.switchOpts[name] + if ('switchIP' in opts): + if (len(opts['switchIP'])>0): + f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + if 'Host' in tags: + opts = self.hostOpts[name] + # Attach vlan interfaces + if ('vlanInterfaces' in opts): + for vlanInterface in opts['vlanInterfaces']: + f.write(" "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n") + f.write(" "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n") + + + f.write("\n") + f.write(" CLI(net)\n") + f.write(" net.stop()\n") + f.write("\n") + f.write("if __name__ == '__main__':\n") + f.write(" setLogLevel( 'info' )\n") + f.write(" myNetwork()\n") + f.write("\n") + + + f.close() + + # Generic canvas handler # # We could have used bindtags, as in nodeIcon, but @@ -319,8 +1760,31 @@ 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 'Host' == node: + self.hostCount += 1 + name = self.nodePrefixes[ node ] + str( self.hostCount ) + self.hostOpts[name] = {'sched':'host'} + self.hostOpts[name]['nodeNum']=self.hostCount + self.hostOpts[name]['hostname']=name + if 'Controller' == node: + name = self.nodePrefixes[ node ] + str( self.controllerCount ) + ctrlr = { 'controllerType': 'ref', + 'hostname': name, + 'remoteIP': '127.0.0.1', + 'remotePort': 6633} + self.controllers[name] = ctrlr + # We want to start controller count at 0 + self.controllerCount += 1 + icon = self.nodeIcon( node, name ) item = self.canvas.create_window( x, y, anchor='c', window=icon, tags=node ) @@ -328,6 +1792,16 @@ class MiniEdit( Frame ): self.itemToWidget[ item ] = icon self.selectItem( item ) icon.links = {} + if 'Switch' == node: + icon.bind('', self.do_switchPopup ) + 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." @@ -362,8 +1836,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(): @@ -427,21 +1900,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 +1914,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" @@ -481,12 +1985,39 @@ class MiniEdit( Frame ): # For now, don't allow hosts to be directly linked stags = self.canvas.gettags( self.widgetToItem[ source ] ) dtags = self.canvas.gettags( target ) - if 'Host' in stags and 'Host' in dtags: + if (('Host' in stags and 'Host' in dtags) or + ('Controller' in stags and 'Host' in dtags) or + ('Controller' in dtags and 'Host' in stags) or + ('Controller' in stags and 'Controller' in dtags)): self.releaseLink( event ) return + + # Set link type + linkType='data' + if 'Controller' in stags or 'Controller' in dtags: + linkType='control' + c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red') + self.createControlLinkBindings() + else: + linkType='data' + self.createDataLinkBindings() + c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,)) + x, y = c.coords( target ) c.coords( self.link, self.linkx, self.linky, x, y ) - self.addLink( source, dest ) + self.addLink( source, dest, linktype=linkType ) + if linkType == 'control': + controllerName = '' + switchName = '' + if 'Controller' in stags: + controllerName = source[ 'text' ] + switchName = dest[ 'text' ] + else: + controllerName = dest[ 'text' ] + switchName = source[ 'text' ] + + self.switchOpts[switchName]['controllers'].append(controllerName) + # We're done self.link = self.linkWidget = None @@ -500,14 +2031,22 @@ 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 +2057,516 @@ 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: + linkDetail['linkOpts'] = linkBox.result + print 'New link details = ' + str(linkBox.result) + + def prefDetails( self ): + prefDefaults = self.appPrefs + prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults) + print 'New Prefs = ' + str(prefBox.result) + if prefBox.result: + self.appPrefs = prefBox.result + + + def controllerDetails( self ): + if ( self.selection is None or + self.net is not None or + self.selection not in self.itemToWidget ): + return + widget = self.itemToWidget[ self.selection ] + name = widget[ 'text' ] + tags = self.canvas.gettags( self.selection ) + oldName = name + if 'Controller' not in tags: + return + + ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name]) + if ctrlrBox.result: + #print 'Controller is ' + ctrlrBox.result[0] + if len(ctrlrBox.result['hostname']) > 0: + name = ctrlrBox.result['hostname'] + widget[ 'text' ] = name + else: + ctrlrBox.result['hostname'] = name + self.controllers[name] = ctrlrBox.result + print 'New controller details for ' + name + ' = ' + str(self.controllers[name]) + # Find references to controller and change name + if oldName != name: + for widget in self.widgetToItem: + switchName = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + if 'Switch' in tags: + switch = self.switchOpts[switchName] + if oldName in switch['controllers']: + switch['controllers'].remove(oldName) + switch['controllers'].append(name) + + + def listBridge( self, _ignore=None ): + if ( self.selection is None or + self.net is None or + self.selection not in self.itemToWidget ): + return + name = self.itemToWidget[ self.selection ][ 'text' ] + tags = self.canvas.gettags( self.selection ) + + if name not in self.net.nameToNode: + return + if 'Switch' in tags: + call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True) + + def ovsShow( self, _ignore=None ): + call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True) + + def rootTerminal( self, _ignore=None ): + call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True) + # Model interface # # Ultimately we will either want to use a topo or # mininet object here, probably. - def addLink( self, source, dest ): + def addLink( self, source, dest, linktype='data', linkopts={} ): "Add link to model." source.links[ dest ] = self.link dest.links[ source ] = self.link - self.links[ self.link ] = ( source, dest ) + self.links[ self.link ] = {'type' :linktype, + 'src':source, + 'dest':dest, + 'linkOpts':linkopts} def deleteLink( self, link ): "Delete link from model." pair = self.links.get( link, None ) if pair is not None: - source, dest = pair + source=pair['src'] + dest=pair['dest'] del source.links[ dest ] del dest.links[ source ] + stags = self.canvas.gettags( self.widgetToItem[ source ] ) + dtags = self.canvas.gettags( self.widgetToItem[ dest ] ) + ltags = self.canvas.gettags( link ) + + if 'control' in ltags: + controllerName = '' + switchName = '' + if 'Controller' in stags: + controllerName = source[ 'text' ] + switchName = dest[ 'text' ] + else: + controllerName = dest[ 'text' ] + switchName = source[ 'text' ] + + if controllerName in self.switchOpts[switchName]['controllers']: + self.switchOpts[switchName]['controllers'].remove(controllerName) + + if link is not None: del self.links[ link ] def deleteNode( self, item ): "Delete node (and its links) from model." + widget = self.itemToWidget[ item ] + tags = self.canvas.gettags(item) + if 'Controller' in tags: + # remove from switch controller lists + for serachwidget in self.widgetToItem: + name = serachwidget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] ) + if 'Switch' in tags: + if widget['text'] in self.switchOpts[name]['controllers']: + self.switchOpts[name]['controllers'].remove(widget['text']) + for link in widget.links.values(): # Delete from view and model self.deleteItem( link ) del self.itemToWidget[ item ] del self.widgetToItem[ widget ] - def build( self ): - "Build network based on our topology." - - net = Mininet( topo=None ) - - # Make controller - net.addController( 'c0' ) + def buildNodes( self, net): # Make nodes + print "Getting Hosts and Switches." for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) - nodeNum = int( name[ 1: ] ) + if 'Switch' in tags: - net.addSwitch( name ) + opts = self.switchOpts[name] + + # Create the correct switch class + switchClass = customOvs + switchParms={} + if 'dpctl' in opts: + switchParms['listenPort']=int(opts['dpctl']) + if 'dpid' in opts: + switchParms['dpid']=opts['dpid'] + if opts['switchType'] == 'default': + if self.appPrefs['switchType'] == 'ivs': + switchClass = IVSSwitch + else: + switchClass = customOvs + elif opts['switchType'] == 'ivs': + switchClass = IVSSwitch + else: + switchClass = customOvs + newSwitch = net.addSwitch( name , cls=switchClass, **switchParms) + #if self.appPrefs['switchType'] == 'ovs': + if switchClass == customOvs: + newSwitch.setOpenFlowVersion(self.appPrefs['openFlowVersions']) + if ('switchIP' in opts): + if (len(opts['switchIP']) > 0): + newSwitch.setSwitchIP(opts['switchIP']) + + # Attach external interfaces + if ('externalInterfaces' in opts): + for extInterface in opts['externalInterfaces']: + if self.checkIntf(extInterface): + Intf( extInterface, node=newSwitch ) + elif 'Host' in tags: - #Generate IP adddress in the 10.0/8 block - ipAddr = ( 10 << 24 ) + nodeNum - net.addHost( name, ip=ipStr( ipAddr ) ) + opts = self.hostOpts[name] + ip = None + defaultRoute = None + if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: + defaultRoute = 'via '+opts['defaultRoute'] + if 'ip' in opts and len(opts['ip']) > 0: + ip = opts['ip'] + else: + nodeNum = self.hostOpts[name]['nodeNum'] + ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) + ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) + + # Create the correct host class + newHost = net.addHost( name, + cls=CPULimitedHost, + ip=ip, + defaultRoute=defaultRoute + ) + + # Set the CPULimitedHost specific options + if 'cores' in opts: + newHost.setCPUs(cores = opts['cores']) + if 'cpu' in opts: + newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched']) + + # Attach external interfaces + if ('externalInterfaces' in opts): + for extInterface in opts['externalInterfaces']: + if self.checkIntf(extInterface): + Intf( extInterface, node=newHost ) + if ('vlanInterfaces' in opts): + if len(opts['vlanInterfaces']) > 0: + print 'Checking that OS is VLAN prepared' + self.pathCheck('vconfig', moduleName='vlan package') + moduleDeps( add='8021q' ) + elif 'Controller' in tags: + opts = self.controllers[name] + + # Get controller info from panel + controllerType = opts['controllerType'] + + # Make controller + print 'Getting controller selection:'+controllerType + controllerIP = opts['remoteIP'] + controllerPort = opts['remotePort'] + if controllerType == 'remote': + net.addController(name=name, + controller=RemoteController, + ip=controllerIP, + port=controllerPort) + elif controllerType == 'inband': + net.addController(name=name, + controller=InbandController, + ip=controllerIP, + port=controllerPort) + elif controllerType == 'ovsc': + net.addController(name=name, + controller=OVSController, + port=controllerPort) + else: + net.addController(name=name, + controller=Controller, + port=controllerPort) + else: raise Exception( "Cannot create mystery node: " + name ) + + def pathCheck( self, *args, **kwargs ): + "Make sure each program in *args can be found in $PATH." + moduleName = kwargs.get( 'moduleName', 'it' ) + for arg in args: + if not quietRun( 'which ' + arg ): + showerror(title="Error", + message= 'Cannot find required executable %s.\n' % arg + + 'Please make sure that %s is installed ' % moduleName + + 'and available in your $PATH.' ) + + def buildLinks( self, net): # Make links - for link in self.links.values(): - ( src, dst ) = link - srcName, dstName = src[ 'text' ], dst[ 'text' ] - src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ] - src.linkTo( dst ) + print "Getting Links." + for key,link in self.links.iteritems(): + tags = self.canvas.gettags(key) + if 'data' in tags: + src=link['src'] + dst=link['dest'] + linkopts=link['linkOpts'] + srcName, dstName = src[ 'text' ], dst[ 'text' ] + src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ] + net.addLink(src, dst, **linkopts) + self.canvas.itemconfig(key, dash=()) + + + def build( self ): + print "Build network based on our topology." + + dpctl = None + if len(self.appPrefs['dpctl']) > 0: + dpctl = int(self.appPrefs['dpctl']) + net = Mininet( topo=None, + listenPort=dpctl, + build=False, + link=TCLink, + ipBase=self.appPrefs['ipBase'] ) + + self.buildNodes(net) + self.buildLinks(net) # Build network (we have to do this separately at the moment ) net.build() return net + + def postStartSetup( self ): + + # Setup host VLAN subinterfaces + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + if 'Host' in tags: + opts = self.hostOpts[name] + # Attach vlan interfaces + if ('vlanInterfaces' in opts): + for vlanInterface in opts['vlanInterfaces']: + print 'adding vlan interface '+vlanInterface[1] + newHost = self.net.get(name) + newHost.cmdPrint('vconfig add '+name+'-eth0 '+vlanInterface[1]) + newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0]) + + # Configure NetFlow + nflowValues = self.appPrefs['netflow'] + if len(nflowValues['nflowTarget']) > 0: + nflowEnabled = False + nflowSwitches = '' + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + + if 'Switch' in tags: + opts = self.switchOpts[name] + if 'netflow' in opts: + if opts['netflow'] == '1': + print name+' has Netflow enabled' + nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF' + nflowEnabled=True + if nflowEnabled: + nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout'] + if nflowValues['nflowAddId'] == '1': + nflowCmd = nflowCmd + ' add_id_to_interface=true' + else: + nflowCmd = nflowCmd + ' add_id_to_interface=false' + print 'cmd = '+nflowCmd+nflowSwitches + call(nflowCmd+nflowSwitches, shell=True) + + else: + print 'No switches with Netflow' + else: + print 'No NetFlow targets specified.' + + # Configure sFlow + sflowValues = self.appPrefs['sflow'] + if len(sflowValues['sflowTarget']) > 0: + sflowEnabled = False + sflowSwitches = '' + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + + if 'Switch' in tags: + opts = self.switchOpts[name] + if 'sflow' in opts: + if opts['sflow'] == '1': + print name+' has sflow enabled' + sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF' + sflowEnabled=True + if sflowEnabled: + sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling'] + print 'cmd = '+sflowCmd+sflowSwitches + call(sflowCmd+sflowSwitches, shell=True) + + else: + print 'No switches with sflow' + else: + print 'No sFlow targets specified.' + + ## NOTE: MAKE SURE THIS IS LAST THING CALLED + # Start the CLI if enabled + if self.appPrefs['startCLI'] == '1': + info( "\n\n NOTE: PLEASE REMEMBER TO EXIT THE CLI BEFORE YOU PRESS THE STOP BUTTON. Not exiting will prevent MiniEdit from quitting and will prevent you from starting the network again during this sessoin.\n\n") + CLI(self.net) + def start( self ): "Start network." if self.net is None: self.net = self.build() - self.net.start() + + # Since I am going to inject per switch controllers. + # I can't call net.start(). I have to replicate what it + # does and add the controller options. + #self.net.start() + info( '**** Starting %s controllers\n' % len( self.net.controllers ) ) + for controller in self.net.controllers: + info( str(controller) + ' ') + controller.start() + info('\n') + info( '**** Starting %s switches\n' % len( self.net.switches ) ) + #for switch in self.net.switches: + # info( switch.name + ' ') + # switch.start( self.net.controllers ) + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + if 'Switch' in tags: + opts = self.switchOpts[name] + switchControllers = [] + for ctrl in opts['controllers']: + switchControllers.append(self.net.get(ctrl)) + info( name + ' ') + # Figure out what controllers will manage this switch + self.net.get(name).start( switchControllers ) + info('\n') + + self.postStartSetup() def stop( self ): "Stop network." @@ -593,6 +2575,60 @@ 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_hostPopup(self, event): + # display the popup menu + if ( self.net is None ): + try: + self.hostPopup.tk_popup(event.x_root, event.y_root, 0) + finally: + # make sure to release the grab (Tk 8.0a1 only) + self.hostPopup.grab_release() + else: + try: + self.hostRunPopup.tk_popup(event.x_root, event.y_root, 0) + finally: + # make sure to release the grab (Tk 8.0a1 only) + self.hostRunPopup.grab_release() + + def do_switchPopup(self, event): + # display the popup menu + if ( self.net is None ): + try: + self.switchPopup.tk_popup(event.x_root, event.y_root, 0) + finally: + # make sure to release the grab (Tk 8.0a1 only) + self.switchPopup.grab_release() + else: + try: + self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0) + finally: + # make sure to release the grab (Tk 8.0a1 only) + self.switchRunPopup.grab_release() + def xterm( self, _ignore=None ): "Make an xterm when a button is pressed." if ( self.selection is None or @@ -602,8 +2638,193 @@ 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(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 +2837,31 @@ def miniEditImages(): 'Select': BitmapImage( file='/usr/include/X11/bitmaps/left_ptr' ), + 'Controller': PhotoImage( data=r""" + R0lGODlhMAAwAPcAAAEBAWfNAYWFhcfHx+3t6/f390lJUaWlpfPz8/Hx72lpaZGRke/v77m5uc0B + AeHh4e/v7WNjY3t7e5eXlyMjI4mJidPT0+3t7f///09PT7Ozs/X19fHx8ZWTk8HBwX9/fwAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAwADAA + Bwj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAEIeMCxo8ePHwVkBGABg8mTKFOmtDByAIYN + MGPCRCCzQIENNzEMGOkBAwIKQIMKpYCgKAIHCDB4GNkAA4OnUJ9++CDhQ1QGFzA0GKkBA4GvYMOK + BYtBA1cNaNOqXcuWq8q3b81m7Cqzbk2bMMu6/Tl0qFEEAZLKxdj1KlSqVA3rnet1rOOwiwmznUzZ + LdzLJgdfpIv3pmebN2Pm1GyRbocNp1PLNMDaAM3Im1/alQk4gO28pCt2RdCBt+/eRg8IP1AUdmmf + f5MrL56bYlcOvaP7Xo6Ag3HdGDho3869u/YE1507t+3AgLz58ujPMwg/sTBUCAzgy49PH0LW5u0x + XFiwvz////5dcJ9bjxVIAHsSdUXAAgs2yOCDDn6FYEQaFGDgYxNCpEFfHHKIX4IDhCjiiCSS+CGF + FlCmogYpcnVABTDGKGOMAlRQYwUHnKjhAjX2aOOPN8LImgAL6PiQBhLMqCSNAThQgQRGOqRBBD1W + aaOVAggnQARRNqRBBxmEKeaYZIrZQZcMKbDiigqM5OabcMYp55x01ilnQAA7 + """), + 'Host': PhotoImage( data=r""" R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m @@ -698,7 +2944,29 @@ def miniEditImages(): """ ) } +def addDictOption( opts, choicesDict, default, name, helpStr=None ): + """Convenience function to add choices dicts to OptionParser. + opts: OptionParser instance + choicesDict: dictionary of valid choices, must include default + default: default choice key + name: long option name + help: string""" + if default not in choicesDict: + raise Exception( 'Invalid default %s for choices dict: %s' % + ( default, name ) ) + if not helpStr: + helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + + '[,param=value...]' ) + opts.add_option( '--' + name, + type='string', + default = default, + help = helpStr ) + if __name__ == '__main__': setLogLevel( 'info' ) app = MiniEdit() + """ import topology if specified """ + app.parseArgs() + app.importTopo() + app.mainloop() From 4b65570ba5cdd242ac3fe8ec07ea2f53bf92b263 Mon Sep 17 00:00:00 2001 From: Gregory Gee Date: Thu, 2 Jan 2014 21:54:56 -0500 Subject: [PATCH 031/140] Add notes about specific release branches. --- INSTALL | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/INSTALL b/INSTALL index f003c08..6d6337f 100644 --- a/INSTALL +++ b/INSTALL @@ -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`. From 55e48112f80feed8f734d66a0539ee991e5f26f8 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 10 Jan 2014 18:06:29 -0800 Subject: [PATCH 032/140] Remove trailing spaces --- util/vm/build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/vm/build.py b/util/vm/build.py index dda4b93..53c22e2 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -871,7 +871,7 @@ def parseArgs(): 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', + parser.add_argument( '-o', '--out', help='output file for test image (vmdk)' ) args = parser.parse_args() if args.depend: @@ -905,7 +905,7 @@ def parseArgs(): exit( 1 ) for image in args.image: bootAndRunTests( image, tests=args.test, pre=args.run, - post=args.post, memory=args.memory, + 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 ): From 75abd94bf5958c76db64f9cef396756e16034306 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 10 Jan 2014 18:07:08 -0800 Subject: [PATCH 033/140] Add #streamOptimized to fix .ovf for VirtualBox 4.3.2 fixes #261 --- util/vm/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/vm/build.py b/util/vm/build.py index 53c22e2..566268e 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -586,7 +586,7 @@ OVFTemplate = """ Virtual disk information + ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"/> The list of logical networks From c90fb34d6d3cd5c7eebd7e556637db40ebf3fdfd Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Jan 2014 23:35:15 -0800 Subject: [PATCH 034/140] If specified, add Branch to build dir name --- util/vm/build.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/vm/build.py b/util/vm/build.py index 566268e..9cd0ba2 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -692,6 +692,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: From b9288efc4a8fe262a9ab9bf89a2784e7a877befe Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 14 Jan 2014 19:42:55 -0800 Subject: [PATCH 035/140] Allow specification of branch to install --- util/vm/install-mininet-vm.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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." - From 5f51abd142a19379c2a9cf83778899d5b96279e7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 14 Jan 2014 20:08:22 -0800 Subject: [PATCH 036/140] Allow -b to specify install or test branch --- util/vm/build.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/util/vm/build.py b/util/vm/build.py index 9cd0ba2..9fb774f 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -510,8 +510,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' ) @@ -531,7 +536,10 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ): 'install-mininet-vm.sh' ) vm.expect( prompt ) log( '* Running VM install script' ) - vm.sendline( 'bash install-mininet-vm.sh' ) + installcmd = 'bash install-mininet-vm.sh' + if Branch: + cmd = cmd + ' ' + Branch + vm.sendline( installcmd ) vm.expect ( 'password for mininet: ' ) vm.sendline( 'mininet' ) log( '* Waiting for script to complete... ' ) @@ -867,8 +875,7 @@ 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', From 317d6482e49cb26b0be2543fee0bc251564a04e9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 14 Jan 2014 20:10:52 -0800 Subject: [PATCH 037/140] Fix typo/minor error. --- util/vm/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/vm/build.py b/util/vm/build.py index 9fb774f..a5f46b3 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -538,7 +538,7 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ): log( '* Running VM install script' ) installcmd = 'bash install-mininet-vm.sh' if Branch: - cmd = cmd + ' ' + Branch + installcmd += ' ' + Branch vm.sendline( installcmd ) vm.expect ( 'password for mininet: ' ) vm.sendline( 'mininet' ) From dfd79bde56ed527103801f08093f36dd88d0a17f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 15 Jan 2014 10:47:06 -0800 Subject: [PATCH 038/140] Add OperatingSystemSection to OVF template should facilitate importing 64-bit image --- util/vm/build.py | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/util/vm/build.py b/util/vm/build.py index a5f46b3..5470544 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -91,6 +91,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 @@ -588,11 +600,11 @@ 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 - @@ -603,8 +615,12 @@ OVFTemplate = """ -A Mininet Virtual Machine (%s) +A Mininet Virtual Machine (%(name)s) mininet-vm + +The kind of installed guest operating system +%(osname)s + Virtual hardware requirements @@ -618,10 +634,10 @@ OVFTemplate = """ byte * 2^20 Memory Size -%dMB of memory +%(mem)dMB of memory 2 4 -%d +%(mem)d 0 @@ -664,16 +680,20 @@ OVFTemplate = """ """ -def generateOVF( name, diskname, disksize, mem=1024 ): +def generateOVF( name, osname, osid, diskname, disksize, mem=1024 ): """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""" 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 ) + xmltext = OVFTemplate % params with open( ovf, 'w+' ) as f: f.write( xmltext ) return ovf @@ -723,13 +743,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' ) From 07e3da08d01d27b39b1bdec84cabef4803454146 Mon Sep 17 00:00:00 2001 From: Andrew Ferguson Date: Sun, 19 Jan 2014 18:33:27 -0500 Subject: [PATCH 039/140] adjust regex for finding mininet's links during cleanup link names of the form "a-b-ethN" were previously interpreted as "b-ethN". this change accepts link names with a dash, and requires N to only contain digits. --- mininet/clean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/clean.py b/mininet/clean.py index 5162bf3..35fbd0f 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -59,7 +59,7 @@ def cleanup(): 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( r"ip link show | egrep -o '([-_[:alnum:]]+-eth[[:digit:]]+)'" ).split( '\n' ) for link in links: if link != '': sh( "ip link del " + link ) From 284547080c457acc4d6005be2a889bfff5aab725 Mon Sep 17 00:00:00 2001 From: Andrew Ferguson Date: Mon, 20 Jan 2014 17:45:30 -0500 Subject: [PATCH 040/140] don't crash when link is TCLink but without options caused by optimized return in link.py#275 in TCIntf.config() --- mininet/node.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 64f143a..b249cd9 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -847,10 +847,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 " From 2e704f996b6332c2094d809a785126dc5e9740cc Mon Sep 17 00:00:00 2001 From: Andrew Ferguson Date: Thu, 23 Jan 2014 16:39:27 -0500 Subject: [PATCH 041/140] print error messages when making interfaces without this, thereis no indication in Mininet that creating an interface has failed. for example, this may happen when the interface name is too long (longer than IFNAMSIZ, which is 16 chars). --- mininet/util.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mininet/util.py b/mininet/util.py index 1b6440b..9948000 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. From 0dd96ebc575a3c6073db89b3a9795b9c565666d1 Mon Sep 17 00:00:00 2001 From: Andrew Ferguson Date: Fri, 24 Jan 2014 16:59:42 -0500 Subject: [PATCH 042/140] userspace switch can be reached on unix port by dpctl --- mininet/node.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index b249cd9..2af68a6 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -827,10 +827,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/' + 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?" From 2286ef4b364d31343b59e66925e28d6768a7f0b7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 24 Jan 2014 15:42:43 -0800 Subject: [PATCH 043/140] Remove GRUB_TERMINAL=serial from /etc/default/grub This is added because we install Ubuntu from a serial console. Fixes #265 --- util/vm/build.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/util/vm/build.py b/util/vm/build.py index 5470544..e973dcf 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -543,9 +543,10 @@ 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' ) installcmd = 'bash install-mininet-vm.sh' @@ -564,6 +565,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' ) From a722a3a110a880fc52f86cd14d0a7fbcdd4bce6f Mon Sep 17 00:00:00 2001 From: lantz Date: Tue, 28 Jan 2014 15:21:48 -0800 Subject: [PATCH 044/140] Update test_simpleperf.py Add spaces. --- examples/test/test_simpleperf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/test/test_simpleperf.py b/examples/test/test_simpleperf.py index 0c57cef..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(0 < 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(0 < loss < 100) + self.assertTrue( 0 < loss < 100 ) if __name__ == '__main__': setLogLevel( 'warning' ) From 29988c8b1d9c1501252f23bbabcabe4110c677ba Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 29 Jan 2014 16:35:30 -0800 Subject: [PATCH 045/140] Canonicalize dpid by removing colons and padding with zeros closes #268 --- mininet/node.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 2af68a6..b197a5b 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -756,7 +756,8 @@ class Switch( Node ): 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 = ( ( '0' * self.dpidLen + dpid.translate( None, ':' ) ) + [ -self.dpidLen: ] if dpid else self.defaultDpid() ) self.opts = opts self.listenPort = listenPort if not self.inNamespace: From 0b5609f5870733b93e9dbcdec5e3b2190ee49191 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 31 Jan 2014 16:59:05 -0800 Subject: [PATCH 046/140] Check (and canonicalize) dpid arguments to Switch() This seems slightly ugly, but it has bitten many people. Closes #268 --- mininet/node.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index b197a5b..b33346c 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -752,28 +752,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 = ( ( '0' * self.dpidLen + dpid.translate( None, ':' ) ) - [ -self.dpidLen: ] 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 + dpid )[ -self.dpidLen : ] def defaultIntf( self ): "Return control interface" From 74c71bc8a4d16d6c2ba8f6447f312bb58a0e4ff7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 3 Feb 2014 17:55:25 -0800 Subject: [PATCH 047/140] Minor tweak of return value. --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index b33346c..ffe38b6 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -777,7 +777,7 @@ class Switch( Node ): 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 + dpid )[ -self.dpidLen : ] + return '0' * ( self.dpidLen - len( dpid ) ) + dpid def defaultIntf( self ): "Return control interface" From f0e55e1096dc11614ffc99b7589c261a87ad610f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 6 Feb 2014 15:30:13 -0800 Subject: [PATCH 048/140] Remove mn links with dots in them (e.g. foo_bar-baz1.2-eth0) --- mininet/clean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/clean.py b/mininet/clean.py index 35fbd0f..6d21fb5 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -59,7 +59,7 @@ def cleanup(): sh( 'ovs-vsctl del-br ' + dp ) info( "*** Removing all links of the pattern foo-ethX\n" ) - links = sh( r"ip link show | egrep -o '([-_[:alnum:]]+-eth[[:digit:]]+)'" ).split( '\n' ) + links = sh( r"ip link show | egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).split( '\n' ) for link in links: if link != '': sh( "ip link del " + link ) From 1fda4865c87d818bca1c2068283408720810917c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 6 Feb 2014 15:51:24 -0800 Subject: [PATCH 049/140] Try batch deleting OVS instances. --- mininet/clean.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mininet/clean.py b/mininet/clean.py index 6d21fb5..a5cee44 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -54,6 +54,9 @@ def cleanup(): info( "*** Removing OVS datapaths" ) dps = sh("ovs-vsctl --timeout=1 list-br").split( '\n' ) + 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").split( '\n' ) for dp in dps: if dp: sh( 'ovs-vsctl del-br ' + dp ) From 9cf9b7b2233469f90ec3bb5384396fa5c7bb778d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 6 Feb 2014 17:50:09 -0800 Subject: [PATCH 050/140] Clean things up slightly. --- mininet/clean.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/mininet/clean.py b/mininet/clean.py index a5cee44..675c7d7 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -47,24 +47,26 @@ 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' ) - sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp for dp in dps if dp ) ) + 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").split( '\n' ) + 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 '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).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( "*** Cleanup complete.\n" ) From 06115a0456e1e1852c3e3f1c580ec88444cd52ee Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 6 Feb 2014 17:56:32 -0800 Subject: [PATCH 051/140] Add support for batch shutdown of OVS switches. This saves about 10 seconds for a 200 switch network. --- mininet/net.py | 3 +++ mininet/node.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/mininet/net.py b/mininet/net.py index 91e3542..2b3ce04 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -408,6 +408,9 @@ class Mininet( object ): info( '*** Stopping %i terms\n' % len( self.terms ) ) self.stopXterms() info( '*** Stopping %i switches\n' % len( self.switches ) ) + swclass = type( self.switches[ 0 ] ) + if False and self.switches and hasattr( swclass, 'batchShutdown' ): + swclass.batchShutdown( self.switches ) for switch in self.switches: info( switch.name + ' ' ) switch.stop() diff --git a/mininet/node.py b/mininet/node.py index ffe38b6..46158a1 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -985,6 +985,13 @@ class OVSSwitch( Switch ): '"service openvswitch-switch start".\n' ) exit( 1 ) + @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 if type(s) == cls ) ) + def dpctl( self, *args ): "Run ovs-ofctl command" return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] ) From 3e2eb71316ce3437f369bd5d006e256ace194fdd Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 17 Feb 2014 00:23:10 -0800 Subject: [PATCH 052/140] Fix default of in-band control for OVS. fixes #279 --- mininet/node.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 46158a1..51f01c1 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -956,14 +956,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 ): @@ -1056,6 +1059,9 @@ class OVSSwitch( Switch ): for c in controllers ] ) if self.listenPort: clist += ' ptcp:%s' % self.listenPort + if not self.inband: + self.cmd( 'ovs-vsctl set bridge', self, + 'other-config:disable-in-band=true' ) self.cmd( 'ovs-vsctl set-controller', self, clist ) # Reconnect quickly to controllers (1s vs. 15s max_backoff) for uuid in self.controllerUUIDs(): From 59cbec3fc9c31c0b78648f6fe0da60cb1bf13210 Mon Sep 17 00:00:00 2001 From: Gregory Gee Date: Sun, 23 Feb 2014 18:30:04 -0800 Subject: [PATCH 053/140] Update to 2.1.0.8 --- examples/miniedit.py | 414 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 372 insertions(+), 42 deletions(-) diff --git a/examples/miniedit.py b/examples/miniedit.py index 8b348be..3adb770 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -10,9 +10,10 @@ Bob Lantz, April 2010 Gregory Gee, July 2013 Controller icon from http://semlabs.co.uk/ +OpenFlow icon from https://www.opennetworking.org/ """ -MINIEDIT_VERSION = '2.1.0.7.1' +MINIEDIT_VERSION = '2.1.0.8' from optparse import OptionParser from Tkinter import * @@ -39,9 +40,9 @@ from mininet.util import ipStr, netParse, ipAdd, quietRun from mininet.util import buildTopo from mininet.term import makeTerm, cleanUpScreens from mininet.node import Controller, RemoteController, NOX, OVSController -from mininet.node import CPULimitedHost, Host -from mininet.node import OVSKernelSwitch, OVSSwitch -from mininet.link import TCLink, Intf +from mininet.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 @@ -71,10 +72,49 @@ class InbandController( RemoteController ): "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, **params ) + OVSSwitch.__init__( self, name, failMode=failMode, datapath=datapath, **params ) self.switchIP = None def getSwitchIP(self): @@ -160,11 +200,15 @@ class PrefsDialog(tkSimpleDialog.Dialog): # Selection of switch type Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E) self.switchType = StringVar(self.leftfieldFrame) - self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch", "Indigo Virtual Switch") + self.switchTypeMenu = 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") @@ -299,6 +343,10 @@ class PrefsDialog(tkSimpleDialog.Dialog): 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' @@ -561,12 +609,16 @@ class SwitchDialog(CustomDialog): # Selection of switch type Label(self.fieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E) self.switchType = StringVar(self.fieldFrame) - self.switchTypeMenu = OptionMenu(self.fieldFrame, self.switchType, "Default", "Open vSwitch", "Indigo Virtual Switch") + self.switchTypeMenu = 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: @@ -650,6 +702,10 @@ class SwitchDialog(CustomDialog): 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: @@ -881,11 +937,48 @@ class ControllerDialog(tkSimpleDialog.Dialog): 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=400, cwidth=800 ): + def __init__( self, parent=None, cheight=600, cwidth=1000 ): self.defaultIpBase='10.0.0.0/8' @@ -941,7 +1034,7 @@ class MiniEdit( Frame ): self.images = miniEditImages() self.buttons = {} self.active = None - self.tools = ( 'Select', 'Host', 'Switch', 'Link', 'Controller' ) + self.tools = ( 'Select', 'Host', 'Switch', 'LegacySwitch', 'LegacyRouter', 'NetLink', 'Controller' ) self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' } self.toolbar = self.createToolbar() @@ -957,7 +1050,7 @@ class MiniEdit( Frame ): # Initialize node data self.nodeBindings = self.createNodeBindings() - self.nodePrefixes = { 'Switch': 's', 'Host': 'h' , 'Controller': 'c'} + self.nodePrefixes = { 'LegacyRouter': 'r', 'LegacySwitch': 's', 'Switch': 's', 'Host': 'h' , 'Controller': 'c'} self.widgetToItem = {} self.itemToWidget = {} @@ -983,13 +1076,18 @@ class MiniEdit( Frame ): 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='Swtich Options', font=self.font) + 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='Swtich Options', font=self.font) + 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 ) @@ -1132,6 +1230,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." @@ -1143,6 +1251,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 @@ -1283,10 +1392,19 @@ class MiniEdit( Frame ): switch['opts']['nodeNum'] = int(nodeNum) x = switch['x'] y = switch['y'] - self.addNode('Switch', nodeNum, float(x), float(y), name=hostname) + 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'] - icon = self.findWidgetByName(hostname) - icon.bind('', self.do_switchPopup ) # create links to controllers if (int(loadedTopology['version']) > 1): @@ -1381,7 +1499,7 @@ class MiniEdit( Frame ): name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) - if 'Switch' in tags: + if 'Switch' in tags or 'LegacySwitch' in tags or 'LegacyRouter' in tags: nodeNum = self.switchOpts[name]['nodeNum'] nodeToSave = {'number':str(nodeNum), 'x':str(x1), @@ -1449,8 +1567,8 @@ class MiniEdit( Frame ): f.write("\n") f.write("from mininet.net import Mininet\n") f.write("from mininet.node import Controller, RemoteController, OVSController\n") - f.write("from mininet.node import CPULimitedHost, Host\n") - f.write("from mininet.node import OVSKernelSwitch\n") + f.write("from mininet.node import CPULimitedHost, Host, Node\n") + f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n") if StrictVersion(VERSION) > StrictVersion('2.0'): f.write("from mininet.node import IVSSwitch\n") f.write("from mininet.cli import CLI\n") @@ -1458,6 +1576,7 @@ class MiniEdit( Frame ): 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 ] ) @@ -1483,8 +1602,7 @@ class MiniEdit( Frame ): if len(self.appPrefs['dpctl']) > 0: f.write(" listenPort="+self.appPrefs['dpctl']+",\n") f.write(" build=False,\n") - f.write(" ipBase='"+self.appPrefs['ipBase']+"',\n") - f.write(" link=TCLink)\n") + f.write(" ipBase='"+self.appPrefs['ipBase']+"')\n") f.write("\n") f.write(" info( '*** Adding controller\\n' )\n") for widget in self.widgetToItem: @@ -1519,6 +1637,11 @@ class MiniEdit( Frame ): 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'] @@ -1526,10 +1649,18 @@ class MiniEdit( Frame ): 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: @@ -1625,7 +1756,7 @@ class MiniEdit( Frame ): f.write(" "+srcName+dstName+" = "+linkOpts+"\n") f.write(" net.addLink("+srcName+", "+dstName) if optsExist: - f.write(", **"+srcName+dstName) + f.write(", link=TCLink , **"+srcName+dstName) f.write(")\n") f.write("\n") @@ -1641,7 +1772,7 @@ class MiniEdit( Frame ): for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) - if 'Switch' in tags: + if 'Switch' in tags or 'LegacySwitch' in tags: opts = self.switchOpts[name] ctrlList = ",".join(opts['controllers']) f.write(" net.get('"+name+"').start(["+ctrlList+"])\n") @@ -1654,9 +1785,31 @@ class MiniEdit( Frame ): tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Switch' in tags: opts = self.switchOpts[name] - if ('switchIP' in opts): - if (len(opts['switchIP'])>0): - f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") + 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 ] ) @@ -1769,6 +1922,21 @@ class MiniEdit( Frame ): 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 ) @@ -1794,6 +1962,10 @@ class MiniEdit( Frame ): 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: @@ -1807,11 +1979,19 @@ class MiniEdit( Frame ): "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 @@ -1821,7 +2001,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 ) @@ -1858,7 +2038,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 ) @@ -1866,14 +2046,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 @@ -1980,16 +2160,20 @@ 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) or - ('Controller' 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.releaseLink( event ) + self.releaseNetLink( event ) return # Set link type @@ -2175,7 +2359,7 @@ class MiniEdit( Frame ): dest = linkDetail['dest'] linkopts = linkDetail['linkOpts'] linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts) - if linkBox.result: + if linkBox.result is not None: linkDetail['linkOpts'] = linkBox.result print 'New link details = ' + str(linkBox.result) @@ -2231,7 +2415,7 @@ class MiniEdit( Frame ): if name not in self.net.nameToNode: return - if 'Switch' in tags: + 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 ): @@ -2309,6 +2493,7 @@ class MiniEdit( Frame ): for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + #print name+' has '+str(tags) if 'Switch' in tags: opts = self.switchOpts[name] @@ -2323,14 +2508,27 @@ class MiniEdit( Frame ): 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 self.appPrefs['switchType'] == 'ovs': + 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): @@ -2343,6 +2541,10 @@ class MiniEdit( Frame ): 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: opts = self.hostOpts[name] ip = None @@ -2357,8 +2559,11 @@ class MiniEdit( Frame ): 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=CPULimitedHost, + cls=hostCls, ip=ip, defaultRoute=defaultRoute ) @@ -2432,7 +2637,10 @@ class MiniEdit( Frame ): linkopts=link['linkOpts'] srcName, dstName = src[ 'text' ], dst[ 'text' ] src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ] - net.addLink(src, dst, **linkopts) + if linkopts: + net.addLink(src, dst, cls=TCLink, **linkopts) + else: + net.addLink(src, dst) self.canvas.itemconfig(key, dash=()) @@ -2445,7 +2653,6 @@ class MiniEdit( Frame ): net = Mininet( topo=None, listenPort=dpctl, build=False, - link=TCLink, ipBase=self.appPrefs['ipBase'] ) self.buildNodes(net) @@ -2564,6 +2771,9 @@ class MiniEdit( Frame ): 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() @@ -2599,6 +2809,15 @@ class MiniEdit( Frame ): # 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 ): @@ -2614,6 +2833,15 @@ class MiniEdit( Frame ): # 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 ): @@ -2837,6 +3065,108 @@ 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 @@ -2890,7 +3220,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 @@ -2917,7 +3247,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 From 2dee3413cf1e3d999970e464ba4839cdc615ded0 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 25 Feb 2014 16:49:33 -0800 Subject: [PATCH 054/140] Remove Ubuntu 10 and Debian Lenny and add Debian Wheezy support --- util/install.sh | 152 +++++++----------------------------------------- 1 file changed, 21 insertions(+), 131 deletions(-) diff --git a/util/install.sh b/util/install.sh index 10a6e2e..6aa6960 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 @@ -108,31 +97,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 +141,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 @@ -301,110 +265,36 @@ function wireshark { # 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 { From 4b7b23cf208ae7173cdb839e9a23dbae4fdb0b79 Mon Sep 17 00:00:00 2001 From: Gregory Gee Date: Tue, 25 Feb 2014 23:15:03 -0500 Subject: [PATCH 055/140] Change the installation to a separate option as recommended with -V. This argument expect a parameter which is the version number of OVS you wish to install. install.sh -V 2.0.0 What is nice that I tested, is that you can keep running the above to change which OVS version you want to test with. Also, if a new version of OVS is released, just run the above again specifying the new version number. The version number must match the version number in the tarball file name like openvswitch-2.0.0.tar.gz. In this case, the version specified must be 2.0.0. --- util/install.sh | 65 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/util/install.sh b/util/install.sh index 6aa6960..f295803 100755 --- a/util/install.sh +++ b/util/install.sh @@ -264,6 +264,64 @@ 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 function ovs { @@ -596,7 +654,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 @@ -621,6 +679,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 @@ -634,7 +693,7 @@ if [ $# -eq 0 ] then all else - while getopts 'abcdefhikmnprs:tvwx03' OPTION + while getopts 'abcdefhikmnprs:tvV:wx03' OPTION do case $OPTION in a) all;; @@ -659,6 +718,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;; From 7c29c2ebf21da9fb05c2db1d3adf471d73f9a6d9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 28 Feb 2014 14:56:08 -0800 Subject: [PATCH 056/140] Fix IVS build --- util/install.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/util/install.sh b/util/install.sh index 6aa6960..e02e245 100755 --- a/util/install.sh +++ b/util/install.sh @@ -85,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 @@ -328,7 +326,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 From 5e60ee266b3163ef5cfefad910da0251863b27ad Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 28 Feb 2014 18:41:41 -0800 Subject: [PATCH 057/140] assertTrue( a > b ) -> assertGreater( a, b ) for better messages --- mininet/test/test_hifi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py index c9d288b..20ee031 100755 --- a/mininet/test/test_hifi.py +++ b/mininet/test/test_hifi.py @@ -53,8 +53,8 @@ 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 ) def testCPULimits( self ): "Verify topology creation with CPU limits set for both schedulers." @@ -120,7 +120,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." From a0bc10028976611dc9aa8e1df757a6ebb96103bd Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 26 Feb 2014 06:13:18 -0800 Subject: [PATCH 058/140] Enable batch shutdown for OVS. --- mininet/net.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 2b3ce04..08dc61f 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -408,9 +408,10 @@ class Mininet( object ): info( '*** Stopping %i terms\n' % len( self.terms ) ) self.stopXterms() info( '*** Stopping %i switches\n' % len( self.switches ) ) - swclass = type( self.switches[ 0 ] ) - if False and self.switches and hasattr( swclass, 'batchShutdown' ): - swclass.batchShutdown( self.switches ) + if self.switches: + swclass = type( self.switches[ 0 ] ) + if hasattr( swclass, 'batchShutdown' ): + swclass.batchShutdown( self.switches ) for switch in self.switches: info( switch.name + ' ' ) switch.stop() From 2e19ceb0aacd4afa5e547a1f6251c019ce6ad8c1 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 26 Feb 2014 06:13:53 -0800 Subject: [PATCH 059/140] Use a single ovs-vsctl command for speed/atomicity --- mininet/node.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 51f01c1..ec9e7b7 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1044,33 +1044,38 @@ 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 ) + for intf in self.intfList() if not intf.IP() ) + clist = ' '.join( 'tcp:%s:%d' % ( c.IP(), c.port ) + for c in controllers ) if self.listenPort: clist += ' ptcp:%s' % self.listenPort + # Construct big ovs-vsctl command + 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 ) ) if not self.inband: - self.cmd( 'ovs-vsctl set bridge', self, - 'other-config:disable-in-band=true' ) - self.cmd( 'ovs-vsctl set-controller', self, clist ) + 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." From e62715870a87a16d97b88abab8d027574c6a8ea9 Mon Sep 17 00:00:00 2001 From: Gregory Gee Date: Fri, 28 Feb 2014 22:23:27 -0500 Subject: [PATCH 060/140] Update to 2.1.0.8.1. Include a fix to work around Mininet version number checking with non-numeric characters. --- examples/miniedit.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/miniedit.py b/examples/miniedit.py index 3adb770..472c0bc 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -13,7 +13,7 @@ Controller icon from http://semlabs.co.uk/ OpenFlow icon from https://www.opennetworking.org/ """ -MINIEDIT_VERSION = '2.1.0.8' +MINIEDIT_VERSION = '2.1.0.8.1' from optparse import OptionParser from Tkinter import * @@ -49,7 +49,8 @@ from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo print 'MiniEdit running against MiniNet '+VERSION -if StrictVersion(VERSION) > StrictVersion('2.0'): +MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION) +if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): from mininet.node import IVSSwitch TOPODEF = 'none' @@ -339,7 +340,7 @@ class PrefsDialog(tkSimpleDialog.Dialog): 'startCLI':startCLI} if sw == 'Indigo Virtual Switch': self.result['switchType'] = 'ivs' - if StrictVersion(VERSION) < StrictVersion('2.1'): + if StrictVersion(MININET_VERSION) < StrictVersion('2.1'): self.ovsOk = False showerror(title="Error", message='MiniNet version 2.1+ required. You have '+VERSION+'.') @@ -698,7 +699,7 @@ class SwitchDialog(CustomDialog): sw = self.switchType.get() if sw == 'Indigo Virtual Switch': results['switchType'] = 'ivs' - if StrictVersion(VERSION) < StrictVersion('2.1'): + if StrictVersion(MININET_VERSION) < StrictVersion('2.1'): self.ovsOk = False showerror(title="Error", message='MiniNet version 2.1+ required. You have '+VERSION+'.') @@ -1569,7 +1570,7 @@ class MiniEdit( Frame ): 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(VERSION) > StrictVersion('2.0'): + 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") @@ -2867,7 +2868,7 @@ class MiniEdit( Frame ): if name not in self.net.nameToNode: return term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] ) - if StrictVersion(VERSION) > StrictVersion('2.0'): + if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): self.net.terms += term else: self.net.terms.append(term) From d82900d3a81c313315c26af9f6d5095bc850ef1e Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 6 Mar 2014 17:52:41 -0800 Subject: [PATCH 061/140] Don't look through all interfaces if you don't need to. --- mininet/util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mininet/util.py b/mininet/util.py index 9948000..b03e945 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -188,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' ) From a905be226055b22fee9f4c33a8434bb2a0c348a7 Mon Sep 17 00:00:00 2001 From: Rich Lane Date: Sat, 15 Mar 2014 10:46:07 -0700 Subject: [PATCH 062/140] persistent command history Saves readline history to ~/.mininet_history. --- mininet/cli.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mininet/cli.py b/mininet/cli.py index 08638c8..b2398d2 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' ) + + # Setup 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 From 0fe73a6780a7d8c240429bfb8a8bc3c7055807ba Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 16 Mar 2014 05:19:51 -0700 Subject: [PATCH 063/140] build=False is unnecessary if topo=None, which is the default --- examples/controllers2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ) From a7eb557680f47c985e2102b967fd7746859d9b67 Mon Sep 17 00:00:00 2001 From: Rich Lane Date: Tue, 18 Mar 2014 11:04:14 -0700 Subject: [PATCH 064/140] IVSSwitch: wait for ivs to terminate before tearing down node This was a problem when running IVS in a container. IVS would begin the process of closing controller connections on receiving SIGTERM, but often mininet would have continued on and removed the control network interface from the container before it could send the FIN. The controller wouldn't know the connection had been closed until it timed out much later. --- mininet/node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mininet/node.py b/mininet/node.py index 51f01c1..ac3c64e 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1124,6 +1124,7 @@ class IVSSwitch(Switch): def stop( self ): "Terminate IVS switch." self.cmd( 'kill %ivs' ) + self.cmd( 'wait' ) self.deleteIntfs() def attach( self, intf ): From d9376439b10e2a144909a5b5fe5931f6ba28d505 Mon Sep 17 00:00:00 2001 From: Adrian Date: Wed, 26 Mar 2014 13:37:32 +0100 Subject: [PATCH 065/140] Ping: packet loss should be a float --- mininet/net.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/net.py b/mininet/net.py index 55bb1c8..ae1634c 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -504,7 +504,7 @@ class Mininet( object ): output( ( '%s ' % dest.name ) if received else 'X ' ) output( '\n' ) if packets > 0: - ploss = 100 * lost / packets + ploss = float(100) * lost / packets received = packets - lost output( "*** Results: %i%% dropped (%d/%d received)\n" % ( ploss, received, packets ) ) From 92a28881b16ab6037b0c41b78f3af9c411ef966e Mon Sep 17 00:00:00 2001 From: lantz Date: Tue, 1 Apr 2014 11:46:52 -0700 Subject: [PATCH 066/140] Update net.py --- mininet/net.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/net.py b/mininet/net.py index 36b4ee2..774545c 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -507,7 +507,7 @@ class Mininet( object ): output( ( '%s ' % dest.name ) if received else 'X ' ) output( '\n' ) if packets > 0: - ploss = float(100) * lost / packets + ploss = 100.0 * lost / packets received = packets - lost output( "*** Results: %i%% dropped (%d/%d received)\n" % ( ploss, received, packets ) ) From c3bf407adfe7b390773cb709999751f90cb66959 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 23 Apr 2014 14:28:09 -0700 Subject: [PATCH 067/140] Added Ubuntu 14.04. --- util/vm/build.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/util/vm/build.py b/util/vm/build.py index e973dcf..3e0daa7 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -83,6 +83,12 @@ 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', } From 4d1a9cdc1dc95595c8390f348d3e0d55c6261756 Mon Sep 17 00:00:00 2001 From: Jon Hall Date: Fri, 25 Apr 2014 04:37:11 +0000 Subject: [PATCH 068/140] Add a timeout parameter to the pingAll command --- mininet/cli.py | 4 ++-- mininet/net.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mininet/cli.py b/mininet/cli.py index b2398d2..b432f7c 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -165,9 +165,9 @@ 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." diff --git a/mininet/net.py b/mininet/net.py index 774545c..e7003c3 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -578,10 +578,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. From 5cb4a5424dc609148f3ba3f635e2eba4fd3f0657 Mon Sep 17 00:00:00 2001 From: Gregory Gee Date: Sat, 3 May 2014 16:21:53 -0400 Subject: [PATCH 069/140] Add ability for for OVS switch start connect to controllers using protocols other than TCP. net.addController( 'c0', protocol='ssl' ) This now allows OVS to connect to a controller using SSL. The default of protocol is 'tcp' as it currently is. --- mininet/node.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 6f765fc..27d1e16 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1048,7 +1048,7 @@ class OVSSwitch( Switch ): # Interfaces and controllers intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) for intf in self.intfList() if not intf.IP() ) - clist = ' '.join( 'tcp:%s:%d' % ( c.IP(), c.port ) + clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port ) for c in controllers ) if self.listenPort: clist += ' ptcp:%s' % self.listenPort @@ -1154,12 +1154,13 @@ class Controller( Node ): def __init__( self, name, inNamespace=False, command='controller', cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1", - port=6633, **params ): + port=6633, protocol='tcp', **params ): self.command = command self.cargs = cargs self.cdir = cdir self.ip = ip self.port = port + self.protocol = protocol Node.__init__( self, name, inNamespace=inNamespace, ip=ip, **params ) self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary From 93cd5583eb9618ca38ba450bfa999975b1a3c77a Mon Sep 17 00:00:00 2001 From: Rich Lane Date: Thu, 22 May 2014 12:18:08 -0700 Subject: [PATCH 070/140] IVSSwitch: support batch shutdown Not a single command like the OVS one, but it still greatly decreases the shutdown time. This does assume that stop() is called after batchShutdown(), which is true in the current mininet code. --- mininet/node.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mininet/node.py b/mininet/node.py index 27d1e16..fdc855e 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1106,6 +1106,13 @@ 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: + if type(switch) == cls: + switch.cmd( 'kill %ivs' ) + def start( self, controllers ): "Start up a new IVS switch" args = ['ivs'] From 876e66e5553fe1c93ef4ad4d3c9cbff5bb3735ee Mon Sep 17 00:00:00 2001 From: Rich Lane Date: Thu, 22 May 2014 12:36:07 -0700 Subject: [PATCH 071/140] net: allow batch shutdown of multiple types of switches Each switch class will be called to shutdown its own instances. --- mininet/net.py | 7 +++---- mininet/node.py | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 1c932f9..8edaee3 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -91,7 +91,7 @@ import re import select import signal 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 @@ -408,10 +408,9 @@ class Mininet( object ): info( '*** Stopping %i terms\n' % len( self.terms ) ) self.stopXterms() info( '*** Stopping %i switches\n' % len( self.switches ) ) - if self.switches: - swclass = type( self.switches[ 0 ] ) + for swclass, switches in groupby( sorted( self.switches, key=type ), type ): if hasattr( swclass, 'batchShutdown' ): - swclass.batchShutdown( self.switches ) + swclass.batchShutdown( switches ) for switch in self.switches: info( switch.name + ' ' ) switch.stop() diff --git a/mininet/node.py b/mininet/node.py index fdc855e..54bfd23 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -993,7 +993,7 @@ class OVSSwitch( Switch ): "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 if type(s) == cls ) ) + for s in switches ) ) def dpctl( self, *args ): "Run ovs-ofctl command" @@ -1110,8 +1110,7 @@ class IVSSwitch(Switch): def batchShutdown( cls, switches ): "Kill each IVS switch, to be waited on later in stop()" for switch in switches: - if type(switch) == cls: - switch.cmd( 'kill %ivs' ) + switch.cmd( 'kill %ivs' ) def start( self, controllers ): "Start up a new IVS switch" From 163a66c64cb6cf084bd16a594954cac19b314171 Mon Sep 17 00:00:00 2001 From: Rich Lane Date: Thu, 22 May 2014 13:06:31 -0700 Subject: [PATCH 072/140] IVSSwitch: add an option to control the --verbose flag --- mininet/node.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 27d1e16..a718ad2 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1090,8 +1090,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 ): @@ -1111,7 +1112,8 @@ class IVSSwitch(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] ) From f1e42ba5fa8d5a34d95abcf7683577757b6dee67 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 28 May 2014 11:50:13 -0700 Subject: [PATCH 073/140] adding ovs version detection to fix port numbering bug --- mininet/node.py | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 27d1e16..4366362 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -57,6 +57,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. @@ -987,6 +989,15 @@ 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 ] + if cls.isOldOVS(): + print "using old version of ovs so startup will be slower" + + @classmethod + def isOldOVS( cls ): + return ( StrictVersion( cls.OVSVersion ) < + StrictVersion( '1.10' ) ) @classmethod def batchShutdown( cls, switches ): @@ -1044,21 +1055,34 @@ class OVSSwitch( Switch ): self.cmd( 'ifconfig lo up' ) # Annoyingly, --if-exists option seems not to work self.cmd( 'ovs-vsctl del-br', self ) - int( self.dpid, 16 ) # DPID must be a hex string # Interfaces and controllers - intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) + intfs = ' '.join( '-- add-port %s %s -- set Interface %s ofport_request=%s ' % ( self, intf, intf, 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 - # Construct big ovs-vsctl command - cmd = ( 'ovs-vsctl add-br %s ' % self + - '-- set Bridge %s ' % self + + # configure old version ov ovs + if self.isOldOVS(): + 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 ) + - intfs + - '-- set-controller %s %s ' % (self, clist ) ) + '-- set-controller %s %s ' % (self, clist )) + + int( self.dpid, 16 ) # DPID must be a hex string + # Construct big ovs-vsctl command + if not self.isOldOVS(): + print "using a newer ovs version so startup will be faster" + 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 ) ) if not self.inband: cmd += ( '-- set bridge %s ' 'other-config:disable-in-band=true ' % self ) @@ -1274,4 +1298,3 @@ class RemoteController( Controller ): if 'Connected' not in listening: warn( "Unable to contact the remote controller" " at %s:%d\n" % ( self.ip, self.port ) ) - From ba43451bd637ac530e2118b9d0045dff74f0b633 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 28 May 2014 12:42:48 -0700 Subject: [PATCH 074/140] rearranged code for elegance --- mininet/node.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 4366362..0630aae 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -991,8 +991,6 @@ class OVSSwitch( Switch ): exit( 1 ) info = quietRun( 'ovs-vsctl --version' ) cls.OVSVersion = findall( '\d+\.\d+', info )[ 0 ] - if cls.isOldOVS(): - print "using old version of ovs so startup will be slower" @classmethod def isOldOVS( cls ): @@ -1055,6 +1053,7 @@ class OVSSwitch( Switch ): self.cmd( 'ifconfig lo up' ) # Annoyingly, --if-exists option seems not to work self.cmd( 'ovs-vsctl del-br', self ) + int( self.dpid, 16 ) # DPID must be a hex string # Interfaces and controllers intfs = ' '.join( '-- add-port %s %s -- set Interface %s ofport_request=%s ' % ( self, intf, intf, self.ports[intf] ) for intf in self.intfList() if not intf.IP() ) @@ -1062,8 +1061,18 @@ class OVSSwitch( Switch ): for c in controllers ) if self.listenPort: clist += ' ptcp:%s' % self.listenPort - # configure old version ov ovs - if self.isOldOVS(): + # Construct big ovs-vsctl command for new versions of OVS + if not self.isOldOVS(): + print "\nusing a newer ovs version" + 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: + print "\nusing an older ovs version" self.cmd( 'ovs-vsctl add-br', self ) for intf in self.intfList(): if not intf.IP(): @@ -1072,17 +1081,6 @@ class OVSSwitch( Switch ): 'other_config:datapath-id=%s ' % self.dpid + '-- set-fail-mode %s %s ' % ( self, self.failMode ) + '-- set-controller %s %s ' % (self, clist )) - - int( self.dpid, 16 ) # DPID must be a hex string - # Construct big ovs-vsctl command - if not self.isOldOVS(): - print "using a newer ovs version so startup will be faster" - 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 ) ) if not self.inband: cmd += ( '-- set bridge %s ' 'other-config:disable-in-band=true ' % self ) From 32d3c2bc7994aa7428ca06a33beedffaf1b0f70a Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 28 May 2014 12:56:24 -0700 Subject: [PATCH 075/140] removing debugging messages --- mininet/node.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 0630aae..02d16b8 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1063,7 +1063,6 @@ class OVSSwitch( Switch ): clist += ' ptcp:%s' % self.listenPort # Construct big ovs-vsctl command for new versions of OVS if not self.isOldOVS(): - print "\nusing a newer ovs version" cmd = ( 'ovs-vsctl add-br %s ' % self + '-- set Bridge %s ' % self + 'other_config:datapath-id=%s ' % self.dpid + @@ -1072,7 +1071,6 @@ class OVSSwitch( Switch ): '-- set-controller %s %s ' % (self, clist ) ) # Construct ovs-vsctl commands for old versions of OVS else: - print "\nusing an older ovs version" self.cmd( 'ovs-vsctl add-br', self ) for intf in self.intfList(): if not intf.IP(): From 4579b303e6dba3f6d21646f6bb36e761770cbf77 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 28 May 2014 13:46:55 -0700 Subject: [PATCH 076/140] conforming to mininet python style --- mininet/node.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 02d16b8..e5e8cb1 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1055,7 +1055,9 @@ class OVSSwitch( Switch ): self.cmd( 'ovs-vsctl del-br', self ) int( self.dpid, 16 ) # DPID must be a hex string # Interfaces and controllers - intfs = ' '.join( '-- add-port %s %s -- set Interface %s ofport_request=%s ' % ( self, intf, intf, self.ports[intf] ) + 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 ) @@ -1068,17 +1070,17 @@ class OVSSwitch( Switch ): 'other_config:datapath-id=%s ' % self.dpid + '-- set-fail-mode %s %s ' % ( self, self.failMode ) + intfs + - '-- set-controller %s %s ' % (self, clist ) ) + '-- 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) + 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 )) + '-- set-controller %s %s ' % ( self, clist ) ) if not self.inband: cmd += ( '-- set bridge %s ' 'other-config:disable-in-band=true ' % self ) From 586a9bb631a8499b02c64a2cb16824f7fa30f865 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 28 May 2014 17:46:19 -0700 Subject: [PATCH 077/140] adding example to test functionality of port numbering --- examples/numberedports.py | 75 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100755 examples/numberedports.py diff --git a/examples/numberedports.py b/examples/numberedports.py new file mode 100755 index 0000000..510fec1 --- /dev/null +++ b/examples/numberedports.py @@ -0,0 +1,75 @@ +#!/usr/bin/python + +""" +Create a network with 5 hosts, numbered 1-4 and 9. +""" + +from mininet.net import Mininet +from mininet.node import Controller +from mininet.cli import CLI +from mininet.log import setLogLevel, info +#from mininet.topo import Topo +from mininet.node import Node + +def validatePort( self, intf ): + "Validate intf's OF port number" + ofport = int( self.cmd( 'ovs-vsctl get Interface', intf, + 'ofport' ) ) + if ofport != self.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' ) + net.addLink( h1, s1 ) + net.addLink( h2, s1 ) + net.addLink( h3, s1 ) + net.addLink( h4, s1 ) + net.addLink( h5, s1, port1 = 1, port2 = 9 ) + + root = Node( 'root', inNamespace=False ) + info( '*** Starting network\n' ) + net.start() + #info( s1.intfs, "\n" ) + # print the interfaces, their port numbers, and the port requests + info( '\n*** printing and validating the ports running on each interface\n' ) + for intfs in s1.intfList(): + if not intfs.name == "lo": + info( intfs, ': ', root.cmd( 'ovs-vsctl get Interface', intfs, 'ofport' ) ) + info ( 'Validating ', intfs, '... ' ) + if validatePort( s1, intfs ): + info( 'Validated.\n' ) + print '\n' + + #info( root.cmd( 'ovs-vsctl list interface | grep -A 2 s1 ' ) ) + net.pingAll() + print '\n' + + info( '*** Stopping network' ) + net.stop() + +if __name__ == '__main__': + setLogLevel( 'info' ) + net() + From 8b215af8183bdea8ee6294bc193c4900068c7af1 Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Wed, 28 May 2014 22:22:20 -0700 Subject: [PATCH 078/140] slight refactoring of util/vm/build.py to make it more extensible --- util/vm/build.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/util/vm/build.py b/util/vm/build.py index 3e0daa7..af39e05 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -781,6 +781,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: @@ -811,8 +814,8 @@ def getMininetVersion( vm ): return version -def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt, - memory=1024, outputFile=None ): +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 @@ -840,11 +843,9 @@ 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' ) @@ -950,9 +951,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, - outputFile=args.out ) + 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() From 3641723193dee42661da528d9e3ab02dc23d47f5 Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 29 May 2014 11:11:12 -0700 Subject: [PATCH 079/140] explaining test --- examples/numberedports.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/numberedports.py b/examples/numberedports.py index 510fec1..6603b3b 100755 --- a/examples/numberedports.py +++ b/examples/numberedports.py @@ -2,13 +2,14 @@ """ 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.cli import CLI from mininet.log import setLogLevel, info -#from mininet.topo import Topo from mininet.node import Node def validatePort( self, intf ): @@ -42,17 +43,18 @@ def net(): 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 ) + 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() - #info( s1.intfs, "\n" ) - # print the interfaces, their port numbers, and the port requests + + # 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": @@ -62,7 +64,7 @@ def net(): info( 'Validated.\n' ) print '\n' - #info( root.cmd( 'ovs-vsctl list interface | grep -A 2 s1 ' ) ) + # test the network with pingall net.pingAll() print '\n' From 50f5080912cfb08154c74fcc8d2898d59f6d2cc8 Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 29 May 2014 11:52:56 -0700 Subject: [PATCH 080/140] corrected code --- examples/numberedports.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/numberedports.py b/examples/numberedports.py index 6603b3b..ba56998 100755 --- a/examples/numberedports.py +++ b/examples/numberedports.py @@ -8,17 +8,16 @@ and that the ovs ports match the mininet ports. from mininet.net import Mininet from mininet.node import Controller -from mininet.cli import CLI from mininet.log import setLogLevel, info from mininet.node import Node -def validatePort( self, intf ): +def validatePort( switch, intf ): "Validate intf's OF port number" - ofport = int( self.cmd( 'ovs-vsctl get Interface', intf, + ofport = int( switch.cmd( 'ovs-vsctl get Interface', intf, 'ofport' ) ) - if ofport != self.ports[ intf ]: + if ofport != switch.ports[ intf ]: warn( 'WARNING: ofport for', intf, 'is actually', ofport, - '\n' ) + '\n' ) return 0 else: return 1 @@ -58,8 +57,9 @@ def net(): info( '\n*** printing and validating the ports running on each interface\n' ) for intfs in s1.intfList(): if not intfs.name == "lo": - info( intfs, ': ', root.cmd( 'ovs-vsctl get Interface', intfs, 'ofport' ) ) - info ( 'Validating ', intfs, '... ' ) + 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' From 87b6021428f7251209358b3102a528fd987e77ea Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 29 May 2014 17:26:40 -0700 Subject: [PATCH 081/140] restructured code and added a test for the numberedports.py example --- examples/README.md | 5 +++ examples/test/test_numberedports.py | 51 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100755 examples/test/test_numberedports.py 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/test/test_numberedports.py b/examples/test/test_numberedports.py new file mode 100755 index 0000000..f92e482 --- /dev/null +++ b/examples/test/test_numberedports.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +""" +Test for numberedports.py +""" + +import unittest +import pexpect +from collections import defaultdict + + +class testNumberedports( unittest.TestCase ): + + 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: + self.assertNotEqual( 0, count_intfs ) + break + +if __name__ == '__main__': + unittest.main() From e49c9d260018b973378c2d6379cd2af2c54c28da Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Fri, 6 Jun 2014 21:21:13 -0700 Subject: [PATCH 082/140] build.py: making ovf generation more generic --- util/vm/build.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/util/vm/build.py b/util/vm/build.py index af39e05..be809ec 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -158,7 +158,7 @@ def depend(): run( 'sudo apt-get -y update' ) run( 'sudo apt-get install -y' ' kvm cloud-utils genisoimage qemu-kvm qemu-utils' - ' e2fsprogs dnsmasq' + ' e2fsprogs dnsmasq curl' ' python-setuptools mtools zip' ) run( 'sudo easy_install pexpect' ) @@ -457,16 +457,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...' ) @@ -628,9 +628,9 @@ OVFTemplate = """ The nat network - -A Mininet Virtual Machine (%(name)s) -mininet-vm + +%(vminfo)s (%(name)s) +%(vmname)s The kind of installed guest operating system %(osname)s @@ -640,10 +640,10 @@ OVFTemplate = """ hertz * 10^6 Number of Virtual CPUs -1 virtual CPU(s) +%(cpus)s virtual CPU(s) 1 3 -1 +%(cpus)s byte * 2^20 @@ -694,19 +694,23 @@ OVFTemplate = """ """ -def generateOVF( name, osname, osid, 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 ] params = dict( osname=osname, osid=osid, diskname=diskname, filesize=filesize, disksize=disksize, name=name, - mem=mem ) + mem=mem, cpus=cpus, vmname=vmname, vminfo=vminfo ) xmltext = OVFTemplate % params with open( ovf, 'w+' ) as f: f.write( xmltext ) From 9109233886f88fdfd6b87dd8f368328c838a7098 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 10 Jun 2014 11:44:03 -0700 Subject: [PATCH 083/140] Added support for mount namespaces in bind.py. Also moved it to the node class as a host type. --- examples/bind.py | 164 +---------------------------------------------- mininet/node.py | 17 +++++ 2 files changed, 20 insertions(+), 161 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index 2c317cf..af02254 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -7,191 +7,33 @@ This creates hosts with private directories as desired. """ 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 ) + host = partial( HostWithPrivateDirs, + privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) net.start() info( 'Private Directories:', privateDirs, '\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/mininet/node.py b/mininet/node.py index e5e8cb1..a9852b2 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -725,6 +725,23 @@ class CPULimitedHost( Host ): mountCgroups() cls.inited = True +class HostWithPrivateDirs( Host ): + "Host with private directories" + + def __init__(self, *args, **kwargs ): + """privateDirs: list of private directories""" + + self.privateDirs = kwargs.pop( 'privateDirs', [] ) + Host.__init__( self, *args, **kwargs ) + self.mountPrivateDirs() + + def mountPrivateDirs( self ): + "Mount tmpfs for each private directory" + for dir_ in self.privateDirs: + self.cmd( 'mkdir -p ' + dir_ ) + self.cmd( 'mount -n -t tmpfs tmpfs %s' % dir_ ) + + # Some important things to note: # From b3055067ace901a89c63a9c03eda7cbaafe07c83 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 10 Jun 2014 18:38:10 -0700 Subject: [PATCH 084/140] fixed netParse bug that caused mininet crash when no ip prefix was specified --- mininet/util.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mininet/util.py b/mininet/util.py index b03e945..3dfa04c 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -281,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 ): @@ -290,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 ): From de41192ea7dd1db8477e16326c8215b45952fbe7 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 10 Jun 2014 21:50:40 -0700 Subject: [PATCH 085/140] imported warn from mininet.log --- examples/numberedports.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/numberedports.py b/examples/numberedports.py index ba56998..8220550 100755 --- a/examples/numberedports.py +++ b/examples/numberedports.py @@ -8,7 +8,7 @@ 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 +from mininet.log import setLogLevel, info, warn from mininet.node import Node def validatePort( switch, intf ): @@ -16,8 +16,7 @@ def validatePort( switch, intf ): ofport = int( switch.cmd( 'ovs-vsctl get Interface', intf, 'ofport' ) ) if ofport != switch.ports[ intf ]: - warn( 'WARNING: ofport for', intf, 'is actually', ofport, - '\n' ) + warn( 'WARNING: ofport for', intf, 'is actually', ofport, '\n' ) return 0 else: return 1 From 29e5bee34ea38028bae5afb3577f679afd7a793e Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 10 Jun 2014 22:15:49 -0700 Subject: [PATCH 086/140] fixed issue with AssertTrue and skip first test if using old OVS version --- examples/test/test_numberedports.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/test/test_numberedports.py b/examples/test/test_numberedports.py index f92e482..b565d3e 100755 --- a/examples/test/test_numberedports.py +++ b/examples/test/test_numberedports.py @@ -7,10 +7,11 @@ 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' ) @@ -28,7 +29,7 @@ class testNumberedports( unittest.TestCase ): elif index == 2: self.assertNotEqual( 0, count ) break - self.assertTrue( correct_ports ) + self.assertTrue( correct_ports ) def testNumbering( self ): """verify that all of the port numbers are printed correctly and consistent with their interface""" @@ -44,8 +45,8 @@ class testNumberedports( unittest.TestCase ): ofport = p.match.group( 2 ) self.assertEqual( intfport, ofport ) elif index == 1: - self.assertNotEqual( 0, count_intfs ) break + self.assertNotEqual( 0, count_intfs ) if __name__ == '__main__': unittest.main() From 0d39f11034b2d28cf68c5cc7a0d888fa0ea4ef18 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Mon, 16 Jun 2014 17:39:24 -0700 Subject: [PATCH 087/140] added code to kill stale mininet processes --- mininet/clean.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mininet/clean.py b/mininet/clean.py index 675c7d7..9b0d6aa 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -69,4 +69,18 @@ def cleanup(): 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" ) From 4e76439c796cb74efa8559c084dec32cfbec5ad9 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Mon, 16 Jun 2014 23:33:38 -0700 Subject: [PATCH 088/140] added support in iperf for different result formats. also added upper bounds for hifi tests --- mininet/net.py | 4 +++- mininet/test/test_hifi.py | 14 +++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 1c932f9..4e98c60 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -617,7 +617,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='M' ): """Run iperf between two hosts. hosts: list of hosts; if None, uses opposite hosts l4Type: string, one of [ TCP, UDP ] @@ -640,6 +640,8 @@ class Mininet( object ): bwArgs = '-b ' + udpBw + ' ' elif l4Type != 'TCP': raise Exception( 'Unexpected l4 type: %s' % l4Type ) + if not format == 'M': + iperfArgs += '-f %s ' %format server.sendCmd( iperfArgs + '-s', printPid=True ) servout = '' while server.lastPid is None: diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py index 20ee031..1ded385 100755 --- a/mininet/test/test_hifi.py +++ b/mininet/test/test_hifi.py @@ -55,6 +55,8 @@ class testOptionsTopoCommon( object ): """ self.assertGreaterEqual( float(measured), float(expected) * tolerance_frac ) + self.assertLess( float( measured ), + float(expected) + (1-tolerance_frac) * float( expected ) ) def testCPULimits( self ): "Verify topology creation with CPU limits set for both schedulers." @@ -69,18 +71,19 @@ class testOptionsTopoCommon( object ): results = mn.runCpuLimitTest( cpu=CPU_FRACTION ) mn.stop() for cpu in results: - self.assertWithinTolerance( cpu, CPU_FRACTION, CPU_TOLERANCE ) + #divide cpu by 100 to convert from percentage to fraction + self.assertWithinTolerance( cpu/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 ) @@ -101,10 +104,11 @@ class testOptionsTopoCommon( object ): self.assertEqual( sent, received ) # 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, + # Multiply delay by 8 to cover there & back on two links, for both the icmp packets and the arp packets + self.assertWithinTolerance( rttval, DELAY_MS * 8.0, DELAY_TOLERANCE) + def testLinkLoss( self ): "Verify that we see packet drops with a high configured loss rate." LOSS_PERCENT = 99 From 6a81b6dfb38297a50e20dfb2e29d57be4e572ee2 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Thu, 19 Jun 2014 15:08:26 -0700 Subject: [PATCH 089/140] added persistence option to HostWithPrivateDirs. also attached mount namespaces when mnexec -a is specified --- examples/bind.py | 4 +++- mininet/node.py | 20 +++++++++++++++++--- mnexec.c | 15 ++++++++++++++- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index af02254..c2e7902 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -20,7 +20,9 @@ from functools import partial def testHostWithPrivateDirs(): "Test bind mounts" topo = SingleSwitchTopo( 10 ) - privateDirs = [ '/var/log', '/var/run' ] + privateDirs = [ ( '/var/log', '/onos/%(name)s/var/log' ), + ( '/var/run', '/ovx/%(name)s/var/run' ), + '/mn' ] host = partial( HostWithPrivateDirs, privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) diff --git a/mininet/node.py b/mininet/node.py index a9852b2..9d5516c 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -728,14 +728,28 @@ class CPULimitedHost( Host ): class HostWithPrivateDirs( Host ): "Host with private directories" - def __init__(self, *args, **kwargs ): + def __init__(self, name, *args, **kwargs ): """privateDirs: list of private directories""" - + self.name = name self.privateDirs = kwargs.pop( 'privateDirs', [] ) - Host.__init__( self, *args, **kwargs ) + Host.__init__( self, name, *args, **kwargs ) self.mountPrivateDirs() def mountPrivateDirs( self ): + "mount the directories that have specified mountpoints" + for directory in self.privateDirs: + if isinstance( directory, tuple ): + 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: + self.cmd( 'mkdir -p %s' %directory ) + self.cmd( 'mount -n -t tmpfs tmpfs %s' %directory ) + + + def mountTempDirs( self ): "Mount tmpfs for each private directory" for dir_ in self.privateDirs: self.cmd( 'mkdir -p ' + dir_ ) diff --git a/mnexec.c b/mnexec.c index fee3d25..8f6830e 100644 --- a/mnexec.c +++ b/mnexec.c @@ -133,6 +133,7 @@ int main(int argc, char *argv[]) perror("mount"); return 1; } + break; case 'p': /* print pid */ @@ -140,7 +141,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); @@ -152,6 +153,18 @@ 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 */ From 3c3344e1f5df164eb87a8e4a1a6060a1dd0f6e68 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Fri, 20 Jun 2014 23:31:45 -0700 Subject: [PATCH 090/140] imported check_output --- mininet/clean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/clean.py b/mininet/clean.py index 9b0d6aa..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 From 752c2d6e7cbe1b8bde132677cec317b169266dd6 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Fri, 20 Jun 2014 23:54:18 -0700 Subject: [PATCH 091/140] mountprivatedirs is no longer needed --- examples/bind.py | 8 +++++++- mininet/node.py | 7 ------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index c2e7902..22eb3a2 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -27,7 +27,13 @@ def testHostWithPrivateDirs(): privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) net.start() - info( 'Private Directories:', privateDirs, '\n' ) + info( 'private Directories: [ ' ) + for directory in privateDirs: + if isinstance( directory, tuple ): + info( '%s, ' %directory[0] ) + else: + info( '%s, ' %directory ) + info( ']\n' ) CLI( net ) net.stop() diff --git a/mininet/node.py b/mininet/node.py index 9d5516c..a2deb44 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -749,13 +749,6 @@ class HostWithPrivateDirs( Host ): self.cmd( 'mount -n -t tmpfs tmpfs %s' %directory ) - def mountTempDirs( self ): - "Mount tmpfs for each private directory" - for dir_ in self.privateDirs: - self.cmd( 'mkdir -p ' + dir_ ) - self.cmd( 'mount -n -t tmpfs tmpfs %s' % dir_ ) - - # Some important things to note: # From af2f67d98c337ff5b8055b52d55d2153b68ce3e0 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Thu, 26 Jun 2014 09:29:06 -0700 Subject: [PATCH 092/140] added documentation for HostWithPrivateDirs --- examples/bind.py | 65 +++++++++++++++++++++++++++++++++++++++--------- mininet/node.py | 13 +++++++--- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index 22eb3a2..9eb027e 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -1,9 +1,38 @@ #!/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 runs TWO mininet instances, one after the other. +The first mounts persistent private directories on /var/log and +/var/run. The second mounts temporary private directories on the same +directories. """ from mininet.net import Mininet @@ -15,33 +44,45 @@ from mininet.log import setLogLevel, info, debug from functools import partial -# Sample usage +# Persistent directory sample usage -def testHostWithPrivateDirs(): +def testPersistentPrivateDirs(): "Test bind mounts" topo = SingleSwitchTopo( 10 ) - privateDirs = [ ( '/var/log', '/onos/%(name)s/var/log' ), - ( '/var/run', '/ovx/%(name)s/var/run' ), - '/mn' ] + privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ), + ( '/var/run', '/tmp/%(name)s/var/run' ) ] host = partial( HostWithPrivateDirs, privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) net.start() - info( 'private Directories: [ ' ) + info( 'Private Directories: [ ' ) for directory in privateDirs: if isinstance( directory, tuple ): - info( '%s, ' %directory[0] ) + info( '%s, ' %directory[ 0 ] ) else: info( '%s, ' %directory ) info( ']\n' ) CLI( net ) net.stop() +# Temporary directory sample usage + +def testTempPrivateDirs(): + "test bind mounts with temporary directories" + topo = SingleSwitchTopo( 10 ) + privateDirs = [ '/var/log', '/var/run' ] + host = partial( HostWithPrivateDirs, + privateDirs=privateDirs ) + net = Mininet( topo=topo, host=host ) + net.start() + info( 'Private Directories: ', privateDirs, '\n' ) + CLI( net ) + net.stop() if __name__ == '__main__': setLogLevel( 'info' ) - testHostWithPrivateDirs() - info( 'Done.\n') - + testPersistentPrivateDirs() + testTempPrivateDirs() + info( 'Done.\n' ) diff --git a/mininet/node.py b/mininet/node.py index a2deb44..bdf7642 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 @@ -728,23 +733,25 @@ class CPULimitedHost( Host ): class HostWithPrivateDirs( Host ): "Host with private directories" - def __init__(self, name, *args, **kwargs ): - """privateDirs: list of 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 the directories that have specified mountpoints" + "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 ) From 00803bcd7f3e05c9caec0ace6333b02595101710 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 27 Jun 2014 13:05:10 -0700 Subject: [PATCH 093/140] Whitespace changes in OVSSwitch. --- mininet/node.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 422ea15..783c949 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1076,16 +1076,16 @@ class OVSSwitch( Switch ): 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 ) ) + 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 + 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: From 549f1ebc8f514fc7b8c98aa90a63494aa708fd8c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 27 Jun 2014 16:41:54 -0700 Subject: [PATCH 094/140] Attach a pty to each node's bash process This should enable node commands that are expecting a tty to behave better. --- mininet/cli.py | 15 ++++++++----- mininet/node.py | 58 +++++++++++++++++++++++++++---------------------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/mininet/cli.py b/mininet/cli.py index b432f7c..42a4d3d 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -55,7 +55,7 @@ class CLI( Cmd ): Cmd.__init__( self ) info( '*** Starting CLI:\n' ) - # Setup history if readline is available + # Set up history if readline is available try: import readline except ImportError: @@ -77,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: @@ -352,8 +352,7 @@ class CLI( Cmd ): 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 ) @@ -361,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 ) @@ -379,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 ): @@ -391,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/node.py b/mininet/node.py index 783c949..ea0e369 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -45,6 +45,7 @@ Future enhancements: """ import os +import pty import re import signal import select @@ -118,16 +119,21 @@ 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 + cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ), '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 ) @@ -141,6 +147,14 @@ class Node( object ): self.lastPid = None self.readbuf = '' self.waiting = False + # Wait for prompt + while True: + data = self.read( 1024 ) + if chr( 127 ) in data: + break + self.pollOut.poll() + self.waiting = False + self.cmd( 'stty -echo' ) def cleanup( self ): "Help python collect its garbage." @@ -205,7 +219,7 @@ class Node( object ): args: command and arguments, or string printPid: print command's PID?""" assert not self.waiting - printPid = kwargs.get( 'printPid', True ) + printPid = kwargs.get( 'printPid', False ) # Allow sendCmd( [ list ] ) if len( args ) == 1 and type( args[ 0 ] ) is list: cmd = args[ 0 ] @@ -219,28 +233,17 @@ 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 ): - cmd = 'mnexec -p ' + cmd + if printPid and not isShellBuiltin( cmd ): + 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.""" @@ -248,7 +251,7 @@ class Node( object ): data = self.read( 1024 ) # Look for PID marker = chr( 1 ) + r'\d+\n' - if chr( 1 ) in data: + if findPid and chr( 1 ) in data: markers = re.findall( marker, data ) if markers: self.lastPid = int( markers[ 0 ][ 1: ] ) @@ -317,7 +320,10 @@ class Node( object ): # Shell requires a string, not a list! if defaults.get( 'shell', False ): cmd = ' '.join( cmd ) - return Popen( cmd, **defaults ) + old = signal.signal( signal.SIGINT, signal.SIG_IGN ) + popen = Popen( cmd, **defaults ) + signal.signal( signal.SIGINT, old ) + return popen def pexec( self, *args, **kwargs ): """Execute a command using popen From 82e0e9f38f7d7d8c9582ac668e9b38429d823b47 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 1 Jul 2014 01:49:10 -0700 Subject: [PATCH 095/140] Avoid overhead of another process (env) in startShell() --- mininet/node.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index ea0e369..d977c61 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -124,8 +124,9 @@ class Node( object ): opts += 'n' # bash -m: enable job control, i: force interactive # -s: pass $* to shell, and make process easy to find in ps - cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ), 'bash', - '--norc', '-mis', 'mininet:' + self.name ] + # 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 From 40a4a25dd8585789ed7d75fcbb0ef3bd526e7928 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 1 Jul 2014 14:51:07 -0700 Subject: [PATCH 096/140] use a single mininet instance in bindpy --- examples/bind.py | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index 9eb027e..287c97a 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -29,10 +29,9 @@ have a temporary private directory mounted on "/var/log". [ ( '/var/run', '/tmp/%(name)s/var/run' ), '/var/log' ] -This example runs TWO mininet instances, one after the other. -The first mounts persistent private directories on /var/log and -/var/run. The second mounts temporary private directories on the same -directories. +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 @@ -44,45 +43,30 @@ from mininet.log import setLogLevel, info, debug from functools import partial -# Persistent directory sample usage +# Sample usage -def testPersistentPrivateDirs(): +def testHostWithPrivateDirs(): "Test bind mounts" topo = SingleSwitchTopo( 10 ) privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ), - ( '/var/run', '/tmp/%(name)s/var/run' ) ] + ( '/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: [ ' ) + directories = [] for directory in privateDirs: - if isinstance( directory, tuple ): - info( '%s, ' %directory[ 0 ] ) - else: - info( '%s, ' %directory ) - info( ']\n' ) - CLI( net ) - net.stop() - -# Temporary directory sample usage - -def testTempPrivateDirs(): - "test bind mounts with temporary directories" - topo = SingleSwitchTopo( 10 ) - privateDirs = [ '/var/log', '/var/run' ] - host = partial( HostWithPrivateDirs, - privateDirs=privateDirs ) - net = Mininet( topo=topo, host=host ) - net.start() - info( 'Private Directories: ', privateDirs, '\n' ) + directories.append( directory[ 0 ] + if isinstance( directory, tuple ) + else directory ) + info( 'Private Directories:', directories, '\n' ) CLI( net ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) - testPersistentPrivateDirs() - testTempPrivateDirs() - info( 'Done.\n' ) + testHostWithPrivateDirs() + info( 'Done.\n') From 342b743b136ab0918f5764b45a2a4137e18621df Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 1 Jul 2014 17:07:14 -0700 Subject: [PATCH 097/140] set staticArp in testLinkDelay --- mininet/test/test_hifi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py index 1ded385..107138f 100755 --- a/mininet/test/test_hifi.py +++ b/mininet/test/test_hifi.py @@ -94,7 +94,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 @@ -104,8 +104,8 @@ class testOptionsTopoCommon( object ): self.assertEqual( sent, received ) # pylint: enable-msg=W0612 for rttval in [rttmin, rttavg, rttmax]: - # Multiply delay by 8 to cover there & back on two links, for both the icmp packets and the arp packets - self.assertWithinTolerance( rttval, DELAY_MS * 8.0, + # Multiply delay by 4 to cover there & back on two links + self.assertWithinTolerance( rttval, DELAY_MS * 4.0, DELAY_TOLERANCE) From 355696f3dd8798b8a0367fc26104c9b66af2700e Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 1 Jul 2014 17:55:14 -0700 Subject: [PATCH 098/140] Don't set self.waiting twice --- mininet/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index d977c61..848ba9e 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -147,7 +147,6 @@ class Node( object ): self.lastCmd = None self.lastPid = None self.readbuf = '' - self.waiting = False # Wait for prompt while True: data = self.read( 1024 ) From 771850b9cfc9b5c8b790edcd152bae3b4e85ea92 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 1 Jul 2014 18:04:44 -0700 Subject: [PATCH 099/140] Possibly faster check for sentinel. --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 848ba9e..50f92e6 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -150,7 +150,7 @@ class Node( object ): # Wait for prompt while True: data = self.read( 1024 ) - if chr( 127 ) in data: + if data[ -1 ] == chr( 127 ): break self.pollOut.poll() self.waiting = False From 9c3ecfe338e361cbeaec18689c5632c372873794 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Wed, 2 Jul 2014 10:53:41 -0700 Subject: [PATCH 100/140] conforming to mininet style --- mininet/node.py | 13 +++++++------ mnexec.c | 3 --- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index bdf7642..72c782a 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -745,15 +745,16 @@ class HostWithPrivateDirs( Host ): for directory in self.privateDirs: if isinstance( directory, tuple ): # mount given private directory - privateDir = directory[ 1 ] %self.__dict__ + 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 ) ) + 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 ) + self.cmd( 'mkdir -p %s' % directory ) + self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory ) diff --git a/mnexec.c b/mnexec.c index 8f6830e..c7103d4 100644 --- a/mnexec.c +++ b/mnexec.c @@ -133,7 +133,6 @@ int main(int argc, char *argv[]) perror("mount"); return 1; } - break; case 'p': /* print pid */ @@ -163,8 +162,6 @@ int main(int argc, char *argv[]) perror("setns"); return 1; } - - break; case 'g': /* Attach to cgroup */ From 0e733c77543b16a67d77465b416fdd77cb509807 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Wed, 2 Jul 2014 13:07:57 -0700 Subject: [PATCH 101/140] fixed default iperf formatting behavior --- mininet/net.py | 6 +++--- mininet/test/test_hifi.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 4e98c60..3b3b1ff 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -617,7 +617,7 @@ class Mininet( object ): # XXX This should be cleaned up - def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format='M' ): + 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 ] @@ -640,8 +640,8 @@ class Mininet( object ): bwArgs = '-b ' + udpBw + ' ' elif l4Type != 'TCP': raise Exception( 'Unexpected l4 type: %s' % l4Type ) - if not format == 'M': - iperfArgs += '-f %s ' %format + if format: + iperfArgs += '-f %s ' % format server.sendCmd( iperfArgs + '-s', printPid=True ) servout = '' while server.lastPid is None: diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py index 107138f..3f89b87 100755 --- a/mininet/test/test_hifi.py +++ b/mininet/test/test_hifi.py @@ -105,7 +105,7 @@ 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) From 16ddf6560a68438fd0b35730520926fbe8c1b1fd Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 7 Jul 2014 21:51:07 -0700 Subject: [PATCH 102/140] Fix findPid since pty uses \r\n as line ending --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 50f92e6..9db22b4 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -250,7 +250,7 @@ class Node( object ): self.waitReadable( timeoutms ) data = self.read( 1024 ) # Look for PID - marker = chr( 1 ) + r'\d+\n' + marker = chr( 1 ) + r'\d+' if findPid and chr( 1 ) in data: markers = re.findall( marker, data ) if markers: From c49b216c1f5f7899440fbd17f4ed3d53abce4cbc Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 7 Jul 2014 21:55:34 -0700 Subject: [PATCH 103/140] Set default printPid back to True --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 9db22b4..9e50319 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -219,7 +219,7 @@ class Node( object ): args: command and arguments, or string printPid: print command's PID?""" assert not self.waiting - printPid = kwargs.get( 'printPid', False ) + printPid = kwargs.get( 'printPid', True ) # Allow sendCmd( [ list ] ) if len( args ) == 1 and type( args[ 0 ] ) is list: cmd = args[ 0 ] From e9013d761fe8f04607b53e42df496d8b6f3f3455 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 7 Jul 2014 21:58:30 -0700 Subject: [PATCH 104/140] Fix pid regex to eat \r\n --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 9e50319..258ce84 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -250,7 +250,7 @@ class Node( object ): self.waitReadable( timeoutms ) data = self.read( 1024 ) # Look for PID - marker = chr( 1 ) + r'\d+' + marker = chr( 1 ) + r'\d+\r\n' if findPid and chr( 1 ) in data: markers = re.findall( marker, data ) if markers: From 3b24bd7abd840479eeaedc63a4ee46c33853014f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 8 Jul 2014 02:22:02 -0700 Subject: [PATCH 105/140] Restore non-mnexec pid detection for background commands --- mininet/node.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 258ce84..7bb80ba 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -234,7 +234,11 @@ class Node( object ): cmd = 'echo -n' self.lastCmd = cmd if printPid and not isShellBuiltin( cmd ): - cmd = 'mnexec -p ' + 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 From 3131c90344766c30d2516ebab5792785296a7104 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 8 Jul 2014 16:53:45 -0700 Subject: [PATCH 106/140] rolled back to iperf format option, and changed 'cpu' variable to 'pct' --- mininet/test/test_hifi.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py index 3f89b87..8f8a1ce 100755 --- a/mininet/test/test_hifi.py +++ b/mininet/test/test_hifi.py @@ -55,8 +55,9 @@ class testOptionsTopoCommon( object ): """ self.assertGreaterEqual( float(measured), float(expected) * tolerance_frac ) - self.assertLess( float( measured ), - float(expected) + (1-tolerance_frac) * float( expected ) ) + self.assertLessEqual( float( measured ), + float(expected) + (1-tolerance_frac) + * float( expected ) ) def testCPULimits( self ): "Verify topology creation with CPU limits set for both schedulers." @@ -70,9 +71,9 @@ class testOptionsTopoCommon( object ): mn.start() results = mn.runCpuLimitTest( cpu=CPU_FRACTION ) mn.stop() - for cpu in results: + for pct in results: #divide cpu by 100 to convert from percentage to fraction - self.assertWithinTolerance( cpu/100, CPU_FRACTION, CPU_TOLERANCE ) + self.assertWithinTolerance( pct/100, CPU_FRACTION, CPU_TOLERANCE ) def testLinkBandwidth( self ): "Verify that link bandwidths are accurate within a bound." From 93ddd926621f1ced84299bdf763471af71761f6a Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 8 Jul 2014 16:55:50 -0700 Subject: [PATCH 107/140] Revert "fixed default iperf formatting behavior" This reverts commit 0e733c77543b16a67d77465b416fdd77cb509807. --- mininet/net.py | 6 +++--- mininet/test/test_hifi.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 3b3b1ff..4e98c60 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -617,7 +617,7 @@ class Mininet( object ): # XXX This should be cleaned up - def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ): + def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format='M' ): """Run iperf between two hosts. hosts: list of hosts; if None, uses opposite hosts l4Type: string, one of [ TCP, UDP ] @@ -640,8 +640,8 @@ class Mininet( object ): bwArgs = '-b ' + udpBw + ' ' elif l4Type != 'TCP': raise Exception( 'Unexpected l4 type: %s' % l4Type ) - if format: - iperfArgs += '-f %s ' % format + if not format == 'M': + iperfArgs += '-f %s ' %format server.sendCmd( iperfArgs + '-s', printPid=True ) servout = '' while server.lastPid is None: diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py index 8f8a1ce..c888e29 100755 --- a/mininet/test/test_hifi.py +++ b/mininet/test/test_hifi.py @@ -106,7 +106,7 @@ 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) From a1acfa89c2cc90c0125d18374c8779edd6af2311 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 8 Jul 2014 17:23:35 -0700 Subject: [PATCH 108/140] set default iperf formatting to none --- mininet/net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 4e98c60..744d4c9 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -617,7 +617,7 @@ class Mininet( object ): # XXX This should be cleaned up - def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format='M' ): + 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 ] @@ -640,7 +640,7 @@ class Mininet( object ): bwArgs = '-b ' + udpBw + ' ' elif l4Type != 'TCP': raise Exception( 'Unexpected l4 type: %s' % l4Type ) - if not format == 'M': + if format: iperfArgs += '-f %s ' %format server.sendCmd( iperfArgs + '-s', printPid=True ) servout = '' From 191df1cb731743dc938b5fac7268e358b0de69b0 Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Wed, 9 Jul 2014 17:47:25 -0700 Subject: [PATCH 109/140] Adding listen socket to UserSwitch when there is no listenPort set --- mininet/node.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 96107f9..568d986 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -856,6 +856,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 @@ -868,7 +870,7 @@ class UserSwitch( Switch ): "Run dpctl command" listenAddr = None if not self.listenPort: - listenAddr = 'unix:/tmp/' + self.name + listenAddr = 'unix:/tmp/%s.listen' % self.name else: listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort return self.cmd( 'dpctl ' + ' '.join( args ) + From 84ea8d7f901ed10e49b0cd8fcdeeebde0f6274a7 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Wed, 2 Jul 2014 15:22:19 -0700 Subject: [PATCH 110/140] added waitConnected attribute to mininet class --- bin/mn | 2 ++ mininet/net.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/bin/mn b/bin/mn index 71c76ec..a86d758 100755 --- a/bin/mn +++ b/bin/mn @@ -261,12 +261,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/mininet/net.py b/mininet/net.py index 8edaee3..f068bdc 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -94,7 +94,7 @@ from time import sleep from itertools import chain, groupby from mininet.cli import CLI -from mininet.log import info, error, debug, output +from mininet.log import info, error, debug, output, warn from mininet.node import Host, OVSKernelSwitch, Controller from mininet.link import Link, Intf from mininet.util import quietRun, fixLimits, numCores, ensureRoot @@ -112,7 +112,7 @@ class Mininet( object ): 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 @@ -163,6 +163,33 @@ class Mininet( object ): if topo and build: self.build() + if waitConnected: + self.waitConnected() + + def waitConnected( self ): + """wait for each switch to connect to a controller, + up to 5 seconds + returns: True if all switches are connected""" + info( '***waiting for switches to connect\n' ) + time = 0 + while time < 5: + connected = True + for switch in self.switches: + if not switch.connected(): + connected = False + sleep( .1 ) + time += .1 + break + if connected: + break + if time >= 5: + warn( 'Timed out after %d seconds\n' % time ) + for switch in self.switches: + if not switch.connected(): + warn( 'Warning: %s is not connected to a controller\n' + % switch.name ) + return connected + def addHost( self, name, cls=None, **params ): """Add host. name: name of host to add From 8e2443ada408bcfbd98bfa2193e5b903990b0df5 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 8 Jul 2014 13:08:48 -0700 Subject: [PATCH 111/140] improved waitConnected algorithm and set default wait time to wait forever --- mininet/net.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index f068bdc..d2e9b59 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -90,6 +90,7 @@ import os import re import select import signal +import copy from time import sleep from itertools import chain, groupby @@ -166,23 +167,25 @@ class Mininet( object ): if waitConnected: self.waitConnected() - def waitConnected( self ): + def waitConnected( self, timeout=None ): """wait for each switch to connect to a controller, up to 5 seconds returns: True if all switches are connected""" info( '***waiting for switches to connect\n' ) time = 0 - while time < 5: + remaining = copy.copy( self.switches ) + while time < timeout or timeout == None: connected = True - for switch in self.switches: + for switch in remaining: if not switch.connected(): connected = False - sleep( .1 ) - time += .1 - break + sleep( .5 ) + time += .5 + else: + remaining.remove( switch ) if connected: break - if time >= 5: + if time >= timeout and not timeout == None: warn( 'Timed out after %d seconds\n' % time ) for switch in self.switches: if not switch.connected(): From 6845fd833975554dd11f724e7aba540a5a94d50e Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 8 Jul 2014 14:40:59 -0700 Subject: [PATCH 112/140] added documentation for waitConnected timeout --- mininet/net.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mininet/net.py b/mininet/net.py index d2e9b59..e86a07d 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -170,6 +170,7 @@ class Mininet( object ): def waitConnected( self, timeout=None ): """wait for each switch to connect to a controller, up to 5 seconds + timeout: max time to wait for switches to connect. returns: True if all switches are connected""" info( '***waiting for switches to connect\n' ) time = 0 From 4797b42005f0503c12f4ceebac3137bfe8aac901 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 8 Jul 2014 17:29:37 -0700 Subject: [PATCH 113/140] conforming to style, and fixing documentation --- mininet/net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index e86a07d..784f20e 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -170,7 +170,7 @@ class Mininet( object ): def waitConnected( self, timeout=None ): """wait for each switch to connect to a controller, up to 5 seconds - timeout: max time to wait for switches to connect. + timeout: time to wait, or None to wait indefinitely returns: True if all switches are connected""" info( '***waiting for switches to connect\n' ) time = 0 @@ -186,7 +186,7 @@ class Mininet( object ): remaining.remove( switch ) if connected: break - if time >= timeout and not timeout == None: + if time >= timeout and timeout is not None: warn( 'Timed out after %d seconds\n' % time ) for switch in self.switches: if not switch.connected(): From 73f477be9dcbf0c02540d5afcb0b60c99da7e52b Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 8 Jul 2014 18:19:11 -0700 Subject: [PATCH 114/140] added waitConnect to linearbandwidth example. --- examples/linearbandwidth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/linearbandwidth.py b/examples/linearbandwidth.py index 3fd06c7..8e866a7 100755 --- a/examples/linearbandwidth.py +++ b/examples/linearbandwidth.py @@ -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, waitConnected=True ) net.start() print "*** testing basic connectivity" for n in lengths: From c23c992f144146822fbf641d23f7d787ababbe59 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 8 Jul 2014 19:36:42 -0700 Subject: [PATCH 115/140] fixed waitConnected performance and moved waitConnected call to mn.start --- mininet/net.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 784f20e..0a31c68 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -149,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 = [] @@ -164,8 +165,6 @@ class Mininet( object ): if topo and build: self.build() - if waitConnected: - self.waitConnected() def waitConnected( self, timeout=None ): """wait for each switch to connect to a controller, @@ -180,12 +179,12 @@ class Mininet( object ): for switch in remaining: if not switch.connected(): connected = False - sleep( .5 ) - time += .5 else: remaining.remove( switch ) if connected: break + sleep( .5 ) + time += .5 if time >= timeout and timeout is not None: warn( 'Timed out after %d seconds\n' % time ) for switch in self.switches: @@ -432,6 +431,8 @@ 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" From 3a52ad2f530242cd368f5fb900ff254b7d4e8b00 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Wed, 9 Jul 2014 19:24:22 -0700 Subject: [PATCH 116/140] fixed linearbandwidth and waitconnected --- examples/linearbandwidth.py | 4 ++-- mininet/net.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/linearbandwidth.py b/examples/linearbandwidth.py index 8e866a7..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, waitConnected=True ) + net = Mininet( topo=topo, switch=Switch, controller=Controller, waitConnected=True ) net.start() print "*** testing basic connectivity" for n in lengths: diff --git a/mininet/net.py b/mininet/net.py index 0a31c68..a5b9a34 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -432,7 +432,7 @@ class Mininet( object ): switch.start( self.controllers ) info( '\n' ) if self.waitConn: - self.waitConnected() + self.waitConnected( ) def stop( self ): "Stop the controller(s), switches and hosts" From 1324ae62621972a226c8629e96856de53c470346 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 9 Jul 2014 21:20:39 -0700 Subject: [PATCH 117/140] Add build() method to simplify Topo() usage --- mininet/topo.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index f9c421f..de5ba8a 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. From 3878c000fe5da0e972cceb629990420be9a1e699 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 Jul 2014 00:38:18 -0700 Subject: [PATCH 118/140] Add nodelib.py, a library of new node types --- mininet/nodelib.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 mininet/nodelib.py diff --git a/mininet/nodelib.py b/mininet/nodelib.py new file mode 100644 index 0000000..6c27f28 --- /dev/null +++ b/mininet/nodelib.py @@ -0,0 +1,45 @@ +""" +Package: nodelib + +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 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 ) + From 38addf2e24840a10fcee88308fe9a41ee0b4c559 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 Jul 2014 01:28:40 -0700 Subject: [PATCH 119/140] Add alias + switch: { 'ovs': OVSSwitch, 'lxbr': LinuxBridge } --- bin/mn | 10 +++++++--- mininet/nodelib.py | 5 ++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/bin/mn b/bin/mn index 71c76ec..567e213 100755 --- a/bin/mn +++ b/bin/mn @@ -25,8 +25,9 @@ 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, 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 @@ -44,9 +45,12 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), 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, diff --git a/mininet/nodelib.py b/mininet/nodelib.py index 6c27f28..df6154c 100644 --- a/mininet/nodelib.py +++ b/mininet/nodelib.py @@ -1,9 +1,7 @@ """ -Package: nodelib - Node Library for Mininet -This contains additional node types which you may find to be useful +This contains additional Node types which you may find to be useful """ from mininet.net import Mininet @@ -11,6 +9,7 @@ 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)" From 5a9c74be03ba61d6764cd70fffb673ed4abc2ba8 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Thu, 10 Jul 2014 11:07:50 -0700 Subject: [PATCH 120/140] fixed last commit --- mininet/net.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/net.py b/mininet/net.py index a5b9a34..0a31c68 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -432,7 +432,7 @@ class Mininet( object ): switch.start( self.controllers ) info( '\n' ) if self.waitConn: - self.waitConnected( ) + self.waitConnected() def stop( self ): "Stop the controller(s), switches and hosts" From b7a112cbec372954e88999607d8bdf8cabd27066 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Thu, 10 Jul 2014 11:24:58 -0700 Subject: [PATCH 121/140] Shutting down controller first --- mininet/net.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 8edaee3..e970bbd 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -404,6 +404,11 @@ class Mininet( object ): 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() @@ -419,11 +424,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 ): From 13d25b410920845f75c5bd1ff1b5f69f28cb0eee Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 Jul 2014 12:44:49 -0700 Subject: [PATCH 122/140] Minor message changes --- mininet/net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index be0d287..281980a 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -171,7 +171,7 @@ class Mininet( object ): up to 5 seconds timeout: time to wait, or None to wait indefinitely returns: True if all switches are connected""" - info( '***waiting for switches to connect\n' ) + info( '*** Waiting for switches to connect\n' ) time = 0 remaining = copy.copy( self.switches ) while time < timeout or timeout == None: @@ -678,7 +678,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 ) From 4794871a9a47ff2baf74c5955c916ab3ce7b8be1 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 Jul 2014 13:39:17 -0700 Subject: [PATCH 123/140] Change algorithm slightly and print progress --- mininet/net.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 281980a..84416d2 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -166,32 +166,35 @@ class Mininet( object ): self.build() - def waitConnected( self, timeout=None ): + 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 = copy.copy( self.switches ) - while time < timeout or timeout == None: - connected = True + while True: for switch in remaining: - if not switch.connected(): - connected = False - else: + if switch.connected(): + info( '%s ' % switch ) remaining.remove( switch ) - if connected: + if not remaining: + info( '\n' ) + return True + if time > timeout and timeout is not None: break - sleep( .5 ) - time += .5 - if time >= timeout and timeout is not None: - warn( 'Timed out after %d seconds\n' % time ) - for switch in self.switches: - if not switch.connected(): - warn( 'Warning: %s is not connected to a controller\n' - % switch.name ) - return connected + 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. From 72fd120dc85b86f4f1477337223fac85a5cb2171 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Fri, 11 Jul 2014 19:04:20 -0700 Subject: [PATCH 124/140] added default controller class --- bin/mn | 5 +++-- mininet/net.py | 7 ++++--- mininet/node.py | 9 +++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/bin/mn b/bin/mn index 71c76ec..3d01a58 100755 --- a/bin/mn +++ b/bin/mn @@ -25,7 +25,7 @@ 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, OVSKernelSwitch, OVSLegacyKernelSwitch, IVSSwitch ) from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo @@ -53,11 +53,12 @@ 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' diff --git a/mininet/net.py b/mininet/net.py index 8edaee3..d285abc 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -95,7 +95,7 @@ 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 +from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller from mininet.link import Link, Intf from mininet.util import quietRun, fixLimits, numCores, ensureRoot from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd @@ -213,16 +213,17 @@ 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 name = controller_new.name # pylint: enable=E1103 else: + # bookmark 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 diff --git a/mininet/node.py b/mininet/node.py index 568d986..609b6e5 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1278,6 +1278,15 @@ class Controller( Node ): self.__class__.__name__, self.name, self.IP(), self.port, self.pid ) +class DefaultController( Controller ): + "find any controller that is available and run it" + def __init__( self, name, **kwargs ): + "search for any installed controller" + controllers = [ 'controller', 'ovs-controller', 'test-controller' ] # , 'pox', 'ryu' ] # test-controller is the important part + for c in controllers: + if quietRun( "which " + c ): + Controller.__init__( self, name, controller=c, **kwargs ) + break class OVSController( Controller ): "Open vSwitch controller" From 796b281bf1955022b4bac8e16b976e0e0ee277ea Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Mon, 14 Jul 2014 13:09:41 -0700 Subject: [PATCH 125/140] fixed command parameter --- mininet/node.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 609b6e5..2680265 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1282,10 +1282,11 @@ class DefaultController( Controller ): "find any controller that is available and run it" def __init__( self, name, **kwargs ): "search for any installed controller" - controllers = [ 'controller', 'ovs-controller', 'test-controller' ] # , 'pox', 'ryu' ] # test-controller is the important part + controllers = [ 'controller', 'ovs-controller', + 'test-controller' ] for c in controllers: if quietRun( "which " + c ): - Controller.__init__( self, name, controller=c, **kwargs ) + Controller.__init__( self, name, command=c, **kwargs ) break class OVSController( Controller ): From a19cc915373c22bddb2e73360a6e165cf18e5955 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Mon, 14 Jul 2014 14:09:39 -0700 Subject: [PATCH 126/140] set DefaultController as the mininet class default --- mininet/net.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/net.py b/mininet/net.py index d285abc..cfd8d08 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -108,7 +108,7 @@ 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, From ea97dea902709448d469c719b366c28377cbf197 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Mon, 14 Jul 2014 14:42:00 -0700 Subject: [PATCH 127/140] adding waitConnected to linear5 test --- mininet/test/test_nets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ) From 708b184397af9ae8d6787f42a678f899030fdbfb Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 15 Jul 2014 01:24:19 -0700 Subject: [PATCH 128/140] Don't remove items from a list we're iterating over --- mininet/net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index f521fbe..353fc57 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -174,9 +174,9 @@ class Mininet( object ): returns: True if all switches are connected""" info( '*** Waiting for switches to connect\n' ) time = 0 - remaining = copy.copy( self.switches ) + remaining = list( self.switches ) while True: - for switch in remaining: + for switch in tuple( remaining ): if switch.connected(): info( '%s ' % switch ) remaining.remove( switch ) From 2935000485dad214f929601bb84b7601c0c9b7f8 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 15 Jul 2014 04:26:31 -0700 Subject: [PATCH 129/140] Add utopic/Ubuntu 14.10 --- util/vm/build.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/util/vm/build.py b/util/vm/build.py index be809ec..ad454f0 100755 --- a/util/vm/build.py +++ b/util/vm/build.py @@ -89,6 +89,12 @@ isoURLs = { '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', } From ece509d5795ee9494f69fbe9448acec690bbb98a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 15 Jul 2014 05:34:32 -0700 Subject: [PATCH 130/140] add connected() to LinuxBridge --- mininet/nodelib.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mininet/nodelib.py b/mininet/nodelib.py index df6154c..2eb8046 100644 --- a/mininet/nodelib.py +++ b/mininet/nodelib.py @@ -26,6 +26,13 @@ class LinuxBridge( Switch ): 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 ) From b5962e8ee982b5b2a81c1548378ff30ef986ba02 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 15 Jul 2014 05:44:13 -0700 Subject: [PATCH 131/140] Added TorusTopo, a 2D torus topology --- bin/mn | 5 +++-- mininet/topolib.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/bin/mn b/bin/mn index 79941f0..67c7173 100755 --- a/bin/mn +++ b/bin/mn @@ -30,7 +30,7 @@ from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, 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 @@ -41,7 +41,8 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), 'linear': LinearTopo, 'reversed': SingleSwitchReversedTopo, 'single': SingleSwitchTopo, - 'tree': TreeTopo } + 'tree': TreeTopo, + 'torus': TorusTopo } SWITCHDEF = 'ovsk' SWITCHES = { 'user': UserSwitch, diff --git a/mininet/topolib.py b/mininet/topolib.py index 63ba36d..4c15e91 100644 --- a/mininet/topolib.py +++ b/mininet/topolib.py @@ -34,3 +34,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 __init__( self, x, y, *args, **kwargs ): + Topo.__init__( self, *args, **kwargs ) + if x < 3 or y < 3: + raise Exception( 'Please use 3x3 or greater for compatibility ' + 'with Mininet 2.1.0' ) + 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 ) + + + From 1b2c7a3193fcfac8c1ce464cd5a80b95215661ac Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 15 Jul 2014 05:58:10 -0700 Subject: [PATCH 132/140] Clean up standard topologies to use build --- mininet/topo.py | 80 +++++++++++++++++----------------------------- mininet/topolib.py | 7 ++-- 2 files changed, 33 insertions(+), 54 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index de5ba8a..cc458fd 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -201,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 4c15e91..c739cff 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 @@ -42,8 +41,8 @@ class TorusTopo( Topo ): 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 __init__( self, x, y, *args, **kwargs ): - Topo.__init__( self, *args, **kwargs ) + + def build( self, x, y ): if x < 3 or y < 3: raise Exception( 'Please use 3x3 or greater for compatibility ' 'with Mininet 2.1.0' ) From b7268856d7b74dc6022bad120b27749f32e9253a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 15 Jul 2014 08:28:05 -0700 Subject: [PATCH 133/140] Tolerate passing controller *objects* into Mininet() --- mininet/net.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 353fc57..b7b89f7 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -246,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 issubclass( name.__class__, Controller ): controller_new = name # Pylint thinks controller is a str() # pylint: disable=E1103 @@ -357,7 +357,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 issubclass( cls.__class__, Controller ): + self.addController( cls ) + else: + self.addController( 'c%d' % i, cls ) info( '*** Adding hosts:\n' ) for hostName in topo.hosts(): From 2a08dec648e7c66d23ed5bb81d673e07ba68cec3 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 15 Jul 2014 08:50:30 -0700 Subject: [PATCH 134/140] Hack to avoid failing version check... ;-/ --- mininet/topolib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/topolib.py b/mininet/topolib.py index 4c15e91..8e3b3a4 100644 --- a/mininet/topolib.py +++ b/mininet/topolib.py @@ -46,7 +46,7 @@ class TorusTopo( Topo ): Topo.__init__( self, *args, **kwargs ) if x < 3 or y < 3: raise Exception( 'Please use 3x3 or greater for compatibility ' - 'with Mininet 2.1.0' ) + 'with 2.1' ) hosts, switches, dpid = {}, {}, 0 # Create and wire interior for i in range( 0, x ): From 5ac3cde2bdeec58156f2d34a5ea4de710662b77e Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 15 Jul 2014 15:48:18 -0700 Subject: [PATCH 135/140] restructured defaultController into a function --- bin/mn | 2 +- mininet/node.py | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/bin/mn b/bin/mn index 3d01a58..04761da 100755 --- a/bin/mn +++ b/bin/mn @@ -25,7 +25,7 @@ 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, DefaultController, UserSwitch, OVSKernelSwitch, + NOX, DefaultController, RemoteController, UserSwitch, OVSKernelSwitch, OVSLegacyKernelSwitch, IVSSwitch ) from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo diff --git a/mininet/node.py b/mininet/node.py index 2680265..41b2d45 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1277,23 +1277,19 @@ class Controller( Node ): return '<%s %s: %s:%s pid=%s> ' % ( self.__class__.__name__, self.name, self.IP(), self.port, self.pid ) - -class DefaultController( Controller ): - "find any controller that is available and run it" - def __init__( self, name, **kwargs ): - "search for any installed controller" - controllers = [ 'controller', 'ovs-controller', - 'test-controller' ] - for c in controllers: - if quietRun( "which " + c ): - Controller.__init__( self, name, command=c, **kwargs ) - break + @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." @@ -1348,3 +1344,10 @@ class RemoteController( Controller ): if 'Connected' not in listening: warn( "Unable to contact the remote controller" " at %s:%d\n" % ( self.ip, self.port ) ) + + +def DefaultController( name, order=[ Controller, OVSController ], **kwargs ): + "find a default controller for mininet" + for controller in order: + if controller.isAvailable(): + return controller( name, **kwargs ) From 00d1963484af0b0252e7afd5b014a91598f6a95f Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 15 Jul 2014 18:21:56 -0700 Subject: [PATCH 136/140] revised comment on defaultController function --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 41b2d45..d9052fa 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1347,7 +1347,7 @@ class RemoteController( Controller ): def DefaultController( name, order=[ Controller, OVSController ], **kwargs ): - "find a default controller for mininet" + "find any controller that is available and run it" for controller in order: if controller.isAvailable(): return controller( name, **kwargs ) From 779ea5f0ad2905acd977c2bdcd4e35915d4491d8 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 15 Jul 2014 19:39:50 -0700 Subject: [PATCH 137/140] removed bookmark --- mininet/net.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mininet/net.py b/mininet/net.py index cfd8d08..afe8096 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -220,7 +220,6 @@ class Mininet( object ): name = controller_new.name # pylint: enable=E1103 else: - # bookmark controller_new = controller( name, **params ) # Add new controller to net if controller_new: # allow controller-less setups From e183e6999736d481452b2f41cfd4e94831e56a1f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 16 Jul 2014 09:57:48 -0700 Subject: [PATCH 138/140] Check for Controller type using isinstance() --- mininet/net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index b7b89f7..e4228b9 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -246,7 +246,7 @@ class Mininet( object ): if not controller: controller = self.controller # Construct new controller if one is not given - if issubclass( name.__class__, Controller ): + if isinstance( name, Controller ): controller_new = name # Pylint thinks controller is a str() # pylint: disable=E1103 @@ -358,7 +358,7 @@ class Mininet( object ): classes = [ classes ] for i, cls in enumerate( classes ): # Allow Controller objects because nobody understands currying - if issubclass( cls.__class__, Controller ): + if isinstance( cls, Controller ): self.addController( cls ) else: self.addController( 'c%d' % i, cls ) From 628e84068ed91921fd881f8627bc2983c0c2c6a2 Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Thu, 31 Jul 2014 18:27:57 -0700 Subject: [PATCH 139/140] Reverting the disable signals change --- mininet/node.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 3950d95..d5b3036 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -329,10 +329,7 @@ class Node( object ): # Shell requires a string, not a list! if defaults.get( 'shell', False ): cmd = ' '.join( cmd ) - old = signal.signal( signal.SIGINT, signal.SIG_IGN ) - popen = Popen( cmd, **defaults ) - signal.signal( signal.SIGINT, old ) - return popen + return Popen( cmd, **defaults ) def pexec( self, *args, **kwargs ): """Execute a command using popen From 0983ed29bb5545c7f1118448d41d7fd4fff3cba1 Mon Sep 17 00:00:00 2001 From: "lip.z" Date: Sat, 2 Aug 2014 20:18:06 +0800 Subject: [PATCH 140/140] fix Singleton.__call__ error fix Singleton.__call__ error --- mininet/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/log.py b/mininet/log.py index 09a664a..5f96569 100644 --- a/mininet/log.py +++ b/mininet/log.py @@ -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 ):