From 60b5864e1d092630eb128fc69f562402f04aecce Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 6 Jul 2011 14:09:46 -0700 Subject: [PATCH 001/250] Change to not fail if OS not detected, and to print detected OS. --- util/install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 33e5d89..f8fea7c 100755 --- a/util/install.sh +++ b/util/install.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + # Mininet install script for Ubuntu (and Debian Lenny) # Brandon Heller (brandonh@stanford.edu) @@ -16,6 +17,7 @@ KERNEL_LOC=http://www.openflow.org/downloads/mininet DIST=Unknown RELEASE=Unknown CODENAME=Unknown + test -e /etc/debian_version && DIST="Debian" grep Ubuntu /etc/lsb-release &> /dev/null && DIST="Ubuntu" if [ "$DIST" = "Ubuntu" ] || [ "$DIST" = "Debian" ]; then @@ -46,7 +48,6 @@ else exit 1 fi - # Kernel Deb pkg to be removed: KERNEL_IMAGE_OLD=linux-image-2.6.26-2-686 From b80f4aeb8578cc1556b957fbe18cea860a1bf904 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Thu, 20 Oct 2011 18:04:50 -0700 Subject: [PATCH 002/250] install.sh: Copy Wireshark dissector to global plugin dir --- util/install.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index f8fea7c..c85518d 100755 --- a/util/install.sh +++ b/util/install.sh @@ -153,12 +153,20 @@ function of { make sudo make install + # The OpenFlow wireshark plugin does not install to the correct dir. + # The correct way would be to fix the install script. + # For now, just copy it to the global WS plugin dir. + # Tested on Ubuntu 11.04. + if [ -e /var/packet-openflow.so ]; then + cp /var/packet-openflow.so /usr/lib/wireshark/libwireshark0/plugins + fi + # Copy coloring rules: OF is white-on-blue: mkdir -p ~/.wireshark cp ~/mininet/util/colorfilters ~/.wireshark # Remove avahi-daemon, which may cause unwanted discovery packets to be - # sent during tests, near link status changes: + # sent during tests, near link status changes: sudo apt-get remove -y avahi-daemon # Disable IPv6. Add to /etc/modprobe.d/blacklist: From daa576c47ab75396d3b87e72e4b78dfd63350425 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 5 Feb 2012 19:36:25 -0800 Subject: [PATCH 003/250] Add errRun to run a command with stderr, stdout, return code and monitoring. --- mininet/util.py | 57 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/mininet/util.py b/mininet/util.py index 75065ea..112269b 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -2,10 +2,10 @@ from time import sleep from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE -import select +from select import poll, POLLIN +from os import read from subprocess import call, check_call, Popen, PIPE, STDOUT - -from mininet.log import error +from mininet.log import output, error # Command execution support @@ -22,7 +22,7 @@ def checkRun( cmd ): # pylint doesn't understand explicit type checking # pylint: disable-msg=E1103 -def quietRun( *cmd ): +def oldQuietRun( *cmd ): """Run a command, routing stderr to stdout, and return the output. cmd: list of command params""" if len( cmd ) == 1: @@ -34,7 +34,7 @@ def quietRun( *cmd ): # select(), which can't handle # high file descriptor numbers! poll() can, however. output = '' - readable = select.poll() + readable = poll() readable.register( popen.stdout ) while True: while readable.poll(): @@ -47,6 +47,53 @@ def quietRun( *cmd ): break return output +# This is a bit complicated, but it enables us to +# monitor commount output as it is happening + +def errRun( *cmd, **kwargs ): + """Run a command and return stdout, stderr and return code + cmd: string or list of command and args + stderr: STDOUT to merge stderr with stdout + shell: run command using shell + echo: monitor output to console""" + # Allow passing in a list or a string + if len( cmd ) == 1: + cmd = cmd[ 0 ] + if isinstance( cmd, str ): + cmd = cmd.split( ' ' ) + # By default we separate stderr, don't run in a shell, and don't echo + stderr = kwargs.get( 'stderr', PIPE ) + shell = kwargs.get( 'shell', False ) + echo = kwargs.get( 'echo', False ) + popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell ) + # We use poll() because select() doesn't work with large fd numbers + out, err = '', '' + poller = poll() + poller.register( popen.stdout, POLLIN ) + fdtofile = { popen.stdout.fileno(): popen.stdout } + if popen.stderr: + fdtofile[ popen.stderr.fileno() ] = popen.stderr + poller.register( popen.stderr, POLLIN ) + while True: + readable = poller.poll() + for fd, event in readable: + f = fdtofile[ fd ] + data = f.read( 1024 ) + if echo: + output( data ) + if f == popen.stdout: + out += data + elif f == popen.stderr: + err += data + returncode = popen.poll() + if returncode is not None: + break + return out, err, returncode + +def quietRun( cmd, **kwargs ): + "Run a command and return merged stdout and stderr" + return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ] + # pylint: enable-msg=E1103 # pylint: disable-msg=E1101,W0612 From 8a7d42db0bb47d7712ddcb7adcfee7fbb75c6e89 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 5 Feb 2012 19:37:10 -0800 Subject: [PATCH 004/250] Update OVS switch to use ovs-vsctl rather than deprecated ovs-openflowd. --- mininet/node.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 8286466..70381ec 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -49,7 +49,7 @@ from subprocess import Popen, PIPE, STDOUT from time import sleep from mininet.log import info, error, debug -from mininet.util import quietRun, makeIntfPair, moveIntf, isShellBuiltin +from mininet.util import quietRun, errRun, makeIntfPair, moveIntf, isShellBuiltin from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN SWITCH_PORT_BASE = 1 # For OF > 0.9, switch ports start at 1 rather than zero @@ -558,8 +558,8 @@ class KernelSwitch( Switch ): self.deleteIntfs() -class OVSKernelSwitch( Switch ): - """Open VSwitch kernel-space switch. +class OVSLegacyKernelSwitch( Switch ): + """Open VSwitch legacy kernel-space switch using ovs-openflowd. Currently only works in the root namespace.""" def __init__( self, name, dp=None, **kwargs ): @@ -617,6 +617,61 @@ class OVSKernelSwitch( Switch ): self.deleteIntfs() +class OVSSwitch( Switch ): + "Open vSwitch switch. Depends on ovs-vsctl." + + def __init__( self, name, dp=None, **kwargs ): + """Init. + name: name for switch + dp: netlink id (0, 1, 2, ...) + defaultMAC: default MAC as unsigned int; random value if None""" + Switch.__init__( self, name, **kwargs ) + self.dp = 'dp%i' % dp + + @staticmethod + def setup(): + "Make sure Open vSwitch is installed and working" + pathCheck( 'ovs-vsctl', + moduleName='Open vSwitch (openvswitch.org)') + moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) + out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' ) + if exitcode: + error( out + err + + 'ovs-vsctl exited with code %d\n' % exitcode + + '*** Error connecting to ovs-db with ovs-vsctl\n' + 'Make sure that Open vSwitch is installed, ' + 'that ovsdb-server is running, and that\n' + '"ovs-vsctl show" works correctly.\n' + 'You may wish to try "service openvswitch-switch start".\n' ) + exit( 1 ) + + def start( self, controllers ): + "Start up a new OVS OpenFlow switch using ovs-vsctl" + # Annoyingly, --if-exists option seems not to work + self.cmd( 'ovs-vsctl del-br ', self.dp ) + self.cmd( 'ovs-vsctl add-br', self.dp ) + self.cmd( 'ovs-vsctl set-fail-mode', self.dp, 'secure' ) + # Add ports + ports = sorted( self.ports.values() ) + intfs = [ self.intfs[ port ] for port in ports ] + # XXX: Ugly check - we should probably fix this! + if ports and ( len( ports ) != ports[ -1 ] + 1 - self.portBase ): + raise Exception( 'only contiguous, one-indexed port ranges ' + 'supported: %s' % self.intfs ) + for intf in intfs: + self.cmd( 'ovs-vsctl add-port', self.dp, intf ) + self.cmd( 'ifconfig', intf, 'up' ) + # Add controllers + clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) for c in controllers ] ) + self.cmd( 'ovs-vsctl set-controller', self.dp, clist ) + + def stop( self ): + "Terminate OVS switch." + self.cmd( 'ovs-vsctl del-br', self.dp ) + +OVSKernelSwitch = OVSSwitch + + class Controller( Node ): """A Controller is a Node that is running (or has execed?) an OpenFlow controller.""" From 08773f8fe92326cbdb1de722ed5eec95b25b84c9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 10 Feb 2012 13:57:33 -0800 Subject: [PATCH 005/250] Script to build .deb packages for Open vSwitch. --- util/build-ovs-packages.sh | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100755 util/build-ovs-packages.sh diff --git a/util/build-ovs-packages.sh b/util/build-ovs-packages.sh new file mode 100755 index 0000000..3236e22 --- /dev/null +++ b/util/build-ovs-packages.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Attempt to build debian packages for OVS + +set -e # exit on error +set -u # exit on undefined variable + +kvers=`uname -r` +ksrc=/lib/modules/$kvers/build +ovs=openvswitch-1.4.0 +ovstgz=$ovs.tar.gz +ovsurl=http://openvswitch.org/releases/$ovstgz + +install='sudo apt-get install -y' + +echo "*** Installing debian/ubuntu build system" + $install build-essential devscripts ubuntu-dev-tools debhelper dh-make + $install diff patch cdbs quilt gnupg fakeroot lintian pbuilder piuparts + $install module-assistant + +echo "*** Installing OVS dependencies" + $install pkg-config gcc make python-dev libssl-dev libtool + $install dkms ipsec-tools + +echo "*** Installing headers for $kvers" + $install linux-headers-$kvers + +echo "*** Retrieving OVS source" + wget -c $ovsurl + tar xzf $ovstgz + cd $ovs + +echo "*** Patching OVS source" + # Not sure why this fails, but off it goes! + sed -i -e 's/dh_strip/# dh_strip/' debian/rules + # And this fails on 10.04 + if [ `lsb_release -rs` = "10.04" ]; then + echo "*** Patching debian/rules to remove dh_python2" + sed -i -e 's/dh_python2/dh_pysupport/' debian/rules + echo "*** Not building ovsdbmonitor since it's too hard on 10.04" + mv debian/ovsdbmonitor.install debian/ovsdbmonitor.install.backup + sed -i -e 's/ovsdbmonitor.install/ovsdbmonitor.install.backup/' Makefile.in + fi + +echo "*** Building OVS user packages" + opts=--with-linux=/lib/modules/`uname -r`/build + fakeroot make -f debian/rules DATAPATH_CONFIGURE_OPTS=$opts binary + +echo "*** Building OVS datapath kernel module package" + # Still looking for the "right" way to do this... + sudo mkdir -p /usr/src/linux + ln -sf _debian/openvswitch.tar.gz . + sudo make -f debian/rules.modules KSRC=$ksrc KVERS=$kvers binary-modules + +echo "*** User packages:" + ls -l ~/*openvswitch*deb + +echo "*** Kernel packages:" + ls -l /usr/src/*openvswitch*deb + +echo "*** Done (hopefully)" + + + + + + + + + + From 3cd2e1a6aa92ee2eea67f26e7b7e6441d9e00e65 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 10 Feb 2012 16:16:16 -0800 Subject: [PATCH 006/250] Add $install + various cleanup. --- util/install.sh | 106 ++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/util/install.sh b/util/install.sh index c85518d..1792909 100755 --- a/util/install.sh +++ b/util/install.sh @@ -17,34 +17,37 @@ KERNEL_LOC=http://www.openflow.org/downloads/mininet DIST=Unknown RELEASE=Unknown CODENAME=Unknown +ARCH=`uname -m` test -e /etc/debian_version && DIST="Debian" grep Ubuntu /etc/lsb-release &> /dev/null && DIST="Ubuntu" if [ "$DIST" = "Ubuntu" ] || [ "$DIST" = "Debian" ]; then - sudo apt-get install -y lsb-release + install='sudo apt-get -y install' + remove='sudo apt-get -y remove' + $install -y lsb-release fi if which lsb_release &> /dev/null; then DIST=`lsb_release -is` RELEASE=`lsb_release -rs` CODENAME=`lsb_release -cs` fi -echo "Detected Linux distribution: $DIST $RELEASE $CODENAME" +echo "Detected Linux distribution: $DIST $RELEASE $CODENAME $ARCH" # Kernel params -if [ "$DIST" = "Debian" ]; 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" = "Ubuntu" ]; then +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 else - echo "Install.sh currently only supports Ubuntu and Debian." + echo "Install.sh currently only supports Ubuntu and Debian Lenny i386." exit 1 fi @@ -61,7 +64,10 @@ OVS_KMODS=($OVS_BUILD/datapath/linux/{openvswitch_mod.ko,brcompat_mod.ko}) function kernel { echo "Install Mininet-compatible kernel if necessary" sudo apt-get update - if [ "$DIST" = "Debian" ]; then + if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then + $install linux-image-$KERNEL_NAME + fi + 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 @@ -84,16 +90,13 @@ function kernel { # /boot/grub/menu.lst to set the default to the entry corresponding to the # kernel you just installed. fi - if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then - sudo apt-get -y install linux-image-$KERNEL_NAME - fi } function kernel_clean { echo "Cleaning kernel..." # To save disk space, remove previous kernel - sudo apt-get -y remove $KERNEL_IMAGE_OLD + $remove $KERNEL_IMAGE_OLD # Also remove downloaded packages: rm -f ~/linux-headers-* ~/linux-image-* @@ -102,7 +105,7 @@ function kernel_clean { # Install Mininet deps function mn_deps { echo "Installing Mininet dependencies" - sudo aptitude install -y gcc make screen psmisc xterm ssh iperf iproute \ + $install gcc make screen psmisc xterm ssh iperf iproute \ python-setuptools python-networkx if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then @@ -130,11 +133,10 @@ function mn_deps { # http://www.openflowswitch.org/wk/index.php/Debian_Install # ... modified to use Debian Lenny rather than unstable. function of { - echo "Installing OpenFlow and its tools..." - + echo "Installing OpenFlow and OpenFlow WireShark dissector..." cd ~/ - sudo apt-get install -y git-core automake m4 pkg-config libtool \ - make libc6-dev autoconf autotools-dev gcc + $install git-core autoconf automake autotools-dev pkg-config \ + make gcc libtool libc6-dev git clone git://openflowswitch.org/openflow.git cd ~/openflow @@ -148,7 +150,7 @@ function of { sudo make install # Install dissector: - sudo apt-get install -y wireshark libgtk2.0-dev + $install wireshark libgtk2.0-dev cd ~/openflow/utilities/wireshark_dissectors/openflow make sudo make install @@ -167,7 +169,7 @@ function of { # Remove avahi-daemon, which may cause unwanted discovery packets to be # sent during tests, near link status changes: - sudo apt-get remove -y avahi-daemon + $remove avahi-daemon # Disable IPv6. Add to /etc/modprobe.d/blacklist: if [ "$DIST" = "Ubuntu" ]; then @@ -183,23 +185,33 @@ function of { function ovs { echo "Installing Open vSwitch..." - if [ "$DIST" = "Debian" ] && [ "$CODENAME" == "lenny" ]; then - sudo aptitude -y install pkg-config gcc make git-core python-dev libssl-dev - # 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 + if [ "$DIST" = "Ubuntu" ]; then + if [ `echo "$RELEASE >= 11.10" | bc` = 1 ]; then + # Use upstream OVS packages + $install openvswitch-switch openvswitch-controller + return fi - if [ "$DIST" = "Ubuntu" ]; then - sudo apt-get -y install $KERNEL_HEADERS - fi + $install $KERNEL_HEADERS + $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 ~/ - git clone git://openvswitch.org/openvswitch + git clone git://openvswitch.org/openvswitch $OVS_SRC cd $OVS_SRC git checkout $OVS_RELEASE ./boot.sh @@ -208,14 +220,12 @@ function ovs { echo "Creating build sdirectory $BUILDDIR" sudo mkdir -p $BUILDDIR fi - opts="--with-linux=$BUILDDIR" - mkdir -p $OVS_BUILD - cd $OVS_BUILD + opts="--with-linux=$BUILDDIR" + mkdir -p $OVS_BUILD + cd $OVS_BUILD ../configure $opts make sudo make install - # openflowd is deprecated, but for now copy it in - sudo cp tests/test-openflowd /usr/local/bin/ovs-openflowd } # Install NOX with tutorial files @@ -223,17 +233,17 @@ function nox { echo "Installing NOX w/tutorial files..." # Install NOX deps: - sudo apt-get -y install autoconf automake g++ libtool python python-twisted \ + $install autoconf automake g++ libtool python python-twisted \ swig libssl-dev make if [ "$DIST" = "Debian" ]; then - sudo apt-get -y install libboost1.35-dev + $install libboost1.35-dev elif [ "$DIST" = "Ubuntu" ]; then - sudo apt-get -y install python-dev libboost-dev - sudo apt-get -y install libboost-filesystem-dev - sudo apt-get -y install libboost-test-dev + $install python-dev libboost-dev + $install libboost-filesystem-dev + $install libboost-test-dev fi # Install NOX optional deps: - sudo apt-get install -y libsqlite3-dev python-simplejson + $install libsqlite3-dev python-simplejson # Fetch NOX destiny cd ~/ @@ -266,7 +276,7 @@ function oftest { echo "Installing oftest..." # Install deps: - sudo apt-get install -y tcpdump python-scapy + $install tcpdump python-scapy # Install oftest: cd ~/ @@ -280,7 +290,7 @@ function oftest { function cbench { echo "Installing cbench..." - sudo apt-get install -y libsnmp-dev libpcap-dev + $install libsnmp-dev libpcap-dev cd ~/ git clone git://openflow.org/oflops.git cd oflops @@ -299,13 +309,13 @@ function other { # Install tcpdump and tshark, cmd-line packet dump tools. Also install gitk, # a graphical git history viewer. - sudo apt-get install -y tcpdump tshark gitk + $install tcpdump tshark gitk # Install common text editors - sudo apt-get install -y vim nano emacs + $install vim nano emacs # Install NTP - sudo apt-get install -y ntp + $install ntp # Set git to colorize everything. git config --global color.diff auto From ae5ac257dd07630e872dc00c531d3982231f6472 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 10 Feb 2012 17:40:19 -0800 Subject: [PATCH 007/250] Build tar archive of relevant OVS packages in correct order. --- util/build-ovs-packages.sh | 41 +++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/util/build-ovs-packages.sh b/util/build-ovs-packages.sh index 3236e22..18634e8 100755 --- a/util/build-ovs-packages.sh +++ b/util/build-ovs-packages.sh @@ -7,7 +7,14 @@ set -u # exit on undefined variable kvers=`uname -r` ksrc=/lib/modules/$kvers/build -ovs=openvswitch-1.4.0 +dist=`lsb_release -is | tr [A-Z] [a-z]` +release=`lsb_release -rs` +arch=`uname -m` +if [ "$arch" = "i686" ]; then arch=i386; fi +if [ "$arch" = "x86_64" ]; then arch=amd64; fi + +overs=1.4.0 +ovs=openvswitch-$overs ovstgz=$ovs.tar.gz ovsurl=http://openvswitch.org/releases/$ovstgz @@ -52,20 +59,26 @@ echo "*** Building OVS datapath kernel module package" ln -sf _debian/openvswitch.tar.gz . sudo make -f debian/rules.modules KSRC=$ksrc KVERS=$kvers binary-modules -echo "*** User packages:" - ls -l ~/*openvswitch*deb +echo "*** Built the following packages:" + cd ~ + ls -l *deb -echo "*** Kernel packages:" - ls -l /usr/src/*openvswitch*deb +archive=$ovs-core-$dist-$release-$arch.tar +ovsbase='common switch brcompat controller' +echo "*** Packing up dkml pki $ovsbase .debs into:" +echo " $archive" + dppkg=openvswitch-datapath-dkms_$overs*all.deb + pkipkg=openvswitch-pki_$overs*all.deb + pkgs="$dppkg $pkipkg" + for component in $ovsbase; do + deb=(openvswitch-${component}_$overs*$arch.deb) + pkgs="$pkgs $deb" + done + rm -rf $archive + tar cf $archive $pkgs + +echo "*** Contents of archive $archive:" + tar tf $archive echo "*** Done (hopefully)" - - - - - - - - - From 0e3cb791b57bd9fbfdcf44fc2990ea49a6608711 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 10 Feb 2012 23:43:30 -0800 Subject: [PATCH 008/250] Add gross depends for ovsdbmonitor. --- util/build-ovs-packages.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/util/build-ovs-packages.sh b/util/build-ovs-packages.sh index 18634e8..6f1b02a 100755 --- a/util/build-ovs-packages.sh +++ b/util/build-ovs-packages.sh @@ -40,13 +40,17 @@ echo "*** Retrieving OVS source" echo "*** Patching OVS source" # Not sure why this fails, but off it goes! sed -i -e 's/dh_strip/# dh_strip/' debian/rules - # And this fails on 10.04 - if [ `lsb_release -rs` = "10.04" ]; then + if [ "$release" = "10.04" ]; then + # Lucid doesn't seem to have all the packages for ovsdbmonitor echo "*** Patching debian/rules to remove dh_python2" sed -i -e 's/dh_python2/dh_pysupport/' debian/rules echo "*** Not building ovsdbmonitor since it's too hard on 10.04" mv debian/ovsdbmonitor.install debian/ovsdbmonitor.install.backup sed -i -e 's/ovsdbmonitor.install/ovsdbmonitor.install.backup/' Makefile.in + else + # Install a bag of hurt for ovsdbmonitor + $install python-pyside.qtcore pyqt4-dev-tools python-twisted python-twisted-bin \ + python-twisted-core python-twisted-conch python-anyjson python-zope.interface fi echo "*** Building OVS user packages" From 2a4cbe2f575c9ec4176ef8ef4d365259873e187e Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 01:29:01 -0800 Subject: [PATCH 009/250] Fix OVS 1.4.0 switch and controller package build/remove/install. --- util/build-ovs-packages.sh | 20 ++++++---- util/install.sh | 75 +++++++++++++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/util/build-ovs-packages.sh b/util/build-ovs-packages.sh index 6f1b02a..23c2757 100755 --- a/util/build-ovs-packages.sh +++ b/util/build-ovs-packages.sh @@ -10,6 +10,7 @@ ksrc=/lib/modules/$kvers/build dist=`lsb_release -is | tr [A-Z] [a-z]` release=`lsb_release -rs` arch=`uname -m` +buildsuffix=-1 if [ "$arch" = "i686" ]; then arch=i386; fi if [ "$arch" = "x86_64" ]; then arch=amd64; fi @@ -52,6 +53,8 @@ echo "*** Patching OVS source" $install python-pyside.qtcore pyqt4-dev-tools python-twisted python-twisted-bin \ python-twisted-core python-twisted-conch python-anyjson python-zope.interface fi + # init script was written to assume that commands complete + sed -i -e 's/^set -e/#set -e/' debian/openvswitch-controller.init echo "*** Building OVS user packages" opts=--with-linux=/lib/modules/`uname -r`/build @@ -67,15 +70,18 @@ echo "*** Built the following packages:" cd ~ ls -l *deb -archive=$ovs-core-$dist-$release-$arch.tar -ovsbase='common switch brcompat controller' -echo "*** Packing up dkml pki $ovsbase .debs into:" +archive=ovs-$overs-core-$dist-$release-$arch$buildsuffix.tar +ovsbase='common pki switch brcompat controller datapath-dkms' +echo "*** Packing up $ovsbase .debs into:" echo " $archive" - dppkg=openvswitch-datapath-dkms_$overs*all.deb - pkipkg=openvswitch-pki_$overs*all.deb - pkgs="$dppkg $pkipkg" + pkgs="" for component in $ovsbase; do - deb=(openvswitch-${component}_$overs*$arch.deb) + if echo $component | egrep 'dkms|pki'; then + # Architecture-independent packages + deb=(openvswitch-${component}_$overs*all.deb) + else + deb=(openvswitch-${component}_$overs*$arch.deb) + fi pkgs="$pkgs $deb" done rm -rf $archive diff --git a/util/install.sh b/util/install.sh index 1792909..9fd0927 100755 --- a/util/install.sh +++ b/util/install.sh @@ -18,13 +18,18 @@ DIST=Unknown RELEASE=Unknown CODENAME=Unknown ARCH=`uname -m` +if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; fi +if [ "$ARCH" = "686" ]; then ARCH="i386"; fi test -e /etc/debian_version && DIST="Debian" grep Ubuntu /etc/lsb-release &> /dev/null && DIST="Ubuntu" if [ "$DIST" = "Ubuntu" ] || [ "$DIST" = "Debian" ]; then install='sudo apt-get -y install' remove='sudo apt-get -y remove' - $install -y lsb-release + pkginst='sudo dpkg -i' + if ! which lsb_release &> /dev/null; then + $install -y lsb-release + fi fi if which lsb_release &> /dev/null; then DIST=`lsb_release -is` @@ -51,13 +56,20 @@ else exit 1 fi +# More distribution info +DIST_LC=`echo $DIST | tr [A-Z] [a-z]` # as lower case + # Kernel Deb pkg to be removed: KERNEL_IMAGE_OLD=linux-image-2.6.26-2-686 DRIVERS_DIR=/lib/modules/${KERNEL_NAME}/kernel/drivers/net -OVS_RELEASE=v1.2.2 +OVS_RELEASE=1.4.0 +OVS_PACKAGE_LOC=https://github.com/downloads/mininet/mininet +OVS_BUILDSUFFIX=-1 +OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar OVS_SRC=~/openvswitch +OVS_TAG=v$OVS_RELEASE OVS_BUILD=$OVS_SRC/build-$KERNEL_NAME OVS_KMODS=($OVS_BUILD/datapath/linux/{openvswitch_mod.ko,brcompat_mod.ko}) @@ -66,14 +78,13 @@ function kernel { sudo apt-get update if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then $install linux-image-$KERNEL_NAME - fi 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: - sudo dpkg -i $KERNEL_IMAGE $KERNEL_HEADERS + $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. @@ -185,11 +196,32 @@ function of { function ovs { echo "Installing Open vSwitch..." - if [ "$DIST" = "Ubuntu" ]; then - if [ `echo "$RELEASE >= 11.10" | bc` = 1 ]; then - # Use upstream OVS packages - $install openvswitch-switch openvswitch-controller + # First see if we have packages + # XXX wget -c seems to fail from github/amazon s3 + if wget $OVS_PACKAGE_LOC/$OVS_PACKAGE_NAME; then + # Install dkms dependencies + $install patch dkms fakeroot + tar xf $OVS_PACKAGE_NAME + orig=`tar tf $OVS_PACKAGE_NAME` + # Now install packages in reasonable dependency order + order='common pki openvswitch-switch brcompat controller dkms' + pkgs="" + for p in $order; do + pkg=`echo "$orig" | grep $p` + pkgs="$pkgs $pkg" + done + echo PKGS $pkgs + $pkginst $pkgs + sudo service openvswitch-controller stop + echo "Done (hopefully) installing packages" + return + fi + + # Otherwise try distribution's OVS packages + if [ "$DIST" = "Ubuntu" ] && [ `echo "$RELEASE >= 11.10" | bc` = 1 ]; then + if $install openvswitch-switch openvswitch-controller; then return + fi fi $install $KERNEL_HEADERS @@ -213,7 +245,7 @@ function ovs { cd ~/ git clone git://openvswitch.org/openvswitch $OVS_SRC cd $OVS_SRC - git checkout $OVS_RELEASE + git checkout $OVS_TAG ./boot.sh BUILDDIR=/lib/modules/${KERNEL_NAME}/build if [ ! -e $BUILDDIR ]; then @@ -228,6 +260,27 @@ function ovs { sudo make install } +function remove_ovs { + pkgs=`dpkg-query -l | grep openvswitch | awk '{ print $2;}'` + echo "Removing existing Open vSwitch packages:" + echo $pkgs + if ! $remove $pkgs; then + echo "Not all packages removed correctly" + fi + # For some reason this doesn't happen + if scripts=`ls /etc/init.d/*openvswitch* 2>/dev/null`; then + echo $scripts + for s in $scripts; do + s=$(basename $s) + echo SCRIPT $s + sudo service $s stop + sudo rm -f /etc/init.d/$s + sudo update-rc.d -f $s remove + done + fi + echo "Done removing OVS" +} + # Install NOX with tutorial files function nox { echo "Installing NOX w/tutorial files..." @@ -407,6 +460,7 @@ function usage { printf -- ' -k: install new (K)ernel\n' >&2 printf -- ' -m: install Open vSwitch kernel (M)odule\n' >&2 printf -- ' -n: install mini(N)et dependencies + core files\n' >&2 + printf -- ' -r: remove existing Open vSwitch packages\n' >&2 printf -- ' -t: install o(T)her stuff\n' >&2 printf -- ' -v: install open (V)switch\n' >&2 printf -- ' -x: install NO(X) OpenFlow controller\n' >&2 @@ -419,7 +473,7 @@ if [ $# -eq 0 ] then all else - while getopts 'abcdfhkmntvx' OPTION + while getopts 'abcdfhkmnrtvx' OPTION do case $OPTION in a) all;; @@ -431,6 +485,7 @@ else k) kernel;; m) modprobe;; n) mn_deps;; + r) remove_ovs;; t) other;; v) ovs;; x) nox;; From fb25ee020005fc19fa26874489379dd4143d3aee Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 01:36:02 -0800 Subject: [PATCH 010/250] Disable automatic openvswitch-controller startup. --- util/install.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/install.sh b/util/install.sh index 9fd0927..484bff3 100755 --- a/util/install.sh +++ b/util/install.sh @@ -212,7 +212,10 @@ function ovs { done echo PKGS $pkgs $pkginst $pkgs + # Switch can run on its own, but + # Mininet should control the controller sudo service openvswitch-controller stop + sudo update-rc.d openvswitch-controller disable echo "Done (hopefully) installing packages" return fi From 738ae1f3fa57adc7d666ecbf391c98984802931c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 11:05:00 -0800 Subject: [PATCH 011/250] Make sure bc is installed. --- util/install.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 484bff3..ae66859 100755 --- a/util/install.sh +++ b/util/install.sh @@ -27,9 +27,13 @@ if [ "$DIST" = "Ubuntu" ] || [ "$DIST" = "Debian" ]; then install='sudo apt-get -y install' remove='sudo apt-get -y remove' pkginst='sudo dpkg -i' + # Prereqs for this script if ! which lsb_release &> /dev/null; then - $install -y lsb-release + $install lsb-release fi + if ! which bc &> /dev/null; then + $install bc + fi fi if which lsb_release &> /dev/null; then DIST=`lsb_release -is` From 46cffb3bcf10ddb52bb8febf791f75c86ab89eea Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 11:10:11 -0800 Subject: [PATCH 012/250] Fixed arch detection - should be i686 rather than just 686 --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index ae66859..ab8d739 100755 --- a/util/install.sh +++ b/util/install.sh @@ -19,7 +19,7 @@ RELEASE=Unknown CODENAME=Unknown ARCH=`uname -m` if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; fi -if [ "$ARCH" = "686" ]; then ARCH="i386"; fi +if [ "$ARCH" = "i686" ]; then ARCH="i386"; fi test -e /etc/debian_version && DIST="Debian" grep Ubuntu /etc/lsb-release &> /dev/null && DIST="Ubuntu" From 8183cb626291ea8751cbfb636eb5413887812fd6 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 11:23:39 -0800 Subject: [PATCH 013/250] Handle libwireshark0/libwireshark1 --- util/install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index ab8d739..b909af5 100755 --- a/util/install.sh +++ b/util/install.sh @@ -175,7 +175,8 @@ function of { # For now, just copy it to the global WS plugin dir. # Tested on Ubuntu 11.04. if [ -e /var/packet-openflow.so ]; then - cp /var/packet-openflow.so /usr/lib/wireshark/libwireshark0/plugins + WS_DIR=`ls -d /usr/lib/wireshark/libwireshark* | head -1` + cp /var/packet-openflow.so $WS_DIR/plugins/ fi # Copy coloring rules: OF is white-on-blue: From 9d275262e2e22089edcb730dbab7db1d0aa1bef3 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 11:26:11 -0800 Subject: [PATCH 014/250] sudo cp for wireshark plugin --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index b909af5..8e2b683 100755 --- a/util/install.sh +++ b/util/install.sh @@ -176,7 +176,7 @@ function of { # Tested on Ubuntu 11.04. if [ -e /var/packet-openflow.so ]; then WS_DIR=`ls -d /usr/lib/wireshark/libwireshark* | head -1` - cp /var/packet-openflow.so $WS_DIR/plugins/ + sudo cp /var/packet-openflow.so $WS_DIR/plugins/ fi # Copy coloring rules: OF is white-on-blue: From a24705d7844e3e3f62d1758a41086aa58d02c8e7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 11:31:53 -0800 Subject: [PATCH 015/250] More controller-stopping madness. --- util/install.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/util/install.sh b/util/install.sh index 8e2b683..29df896 100755 --- a/util/install.sh +++ b/util/install.sh @@ -219,8 +219,12 @@ function ovs { $pkginst $pkgs # Switch can run on its own, but # Mininet should control the controller - sudo service openvswitch-controller stop - sudo update-rc.d openvswitch-controller disable + 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 echo "Done (hopefully) installing packages" return fi From e5b54a314311c443c3f5323eba192ab7af98ae66 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 11:35:17 -0800 Subject: [PATCH 016/250] Only install module manually if we built OVS from source. --- util/install.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util/install.sh b/util/install.sh index 29df896..35b82e6 100755 --- a/util/install.sh +++ b/util/install.sh @@ -270,6 +270,8 @@ function ovs { ../configure $opts make sudo make install + + modprobe } function remove_ovs { @@ -416,7 +418,6 @@ function all { mn_deps of ovs - modprobe nox oftest cbench @@ -470,7 +471,7 @@ function usage { printf -- ' -f: install open(F)low\n' >&2 printf -- ' -h: print this (H)elp message\n' >&2 printf -- ' -k: install new (K)ernel\n' >&2 - printf -- ' -m: install Open vSwitch kernel (M)odule\n' >&2 + printf -- ' -m: install Open vSwitch kernel (M)odule from source dir\n' >&2 printf -- ' -n: install mini(N)et dependencies + core files\n' >&2 printf -- ' -r: remove existing Open vSwitch packages\n' >&2 printf -- ' -t: install o(T)her stuff\n' >&2 From 2b26161000d3e3ea738a2e316d7d29d0f25c65c3 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 17:13:53 -0800 Subject: [PATCH 017/250] More OVS install fixes. --- util/install.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/util/install.sh b/util/install.sh index 35b82e6..a8e0d2a 100755 --- a/util/install.sh +++ b/util/install.sh @@ -209,14 +209,12 @@ function ovs { tar xf $OVS_PACKAGE_NAME orig=`tar tf $OVS_PACKAGE_NAME` # Now install packages in reasonable dependency order - order='common pki openvswitch-switch brcompat controller dkms' + order='dkms common pki openvswitch-switch brcompat controller' pkgs="" for p in $order; do pkg=`echo "$orig" | grep $p` - pkgs="$pkgs $pkg" + $pkginst $pkg done - echo PKGS $pkgs - $pkginst $pkgs # Switch can run on its own, but # Mininet should control the controller if [ -e /etc/init.d/openvswitch-controller ]; then @@ -275,7 +273,7 @@ function ovs { } function remove_ovs { - pkgs=`dpkg-query -l | grep openvswitch | awk '{ print $2;}'` + pkgs=`dpkg --get-selections | grep openvswitch | awk '{ print $1;}'` echo "Removing existing Open vSwitch packages:" echo $pkgs if ! $remove $pkgs; then From 7eb869af85ed3541c63b2bd1c0b0208da13bc10a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 17:27:32 -0800 Subject: [PATCH 018/250] Force config files to be installed even if removed/edited. ;-/ --- util/install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index a8e0d2a..56046c5 100755 --- a/util/install.sh +++ b/util/install.sh @@ -213,7 +213,8 @@ function ovs { pkgs="" for p in $order; do pkg=`echo "$orig" | grep $p` - $pkginst $pkg + # Annoyingly, things seem to be missing without this flag + $pkginst --force-confmiss $pkg done # Switch can run on its own, but # Mininet should control the controller From 899620808446c4ec94c5c5e301d5ce922e39e303 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 18:19:24 -0800 Subject: [PATCH 019/250] dkms needs kernel headers. --- util/install.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 56046c5..392d3c6 100755 --- a/util/install.sh +++ b/util/install.sh @@ -201,6 +201,9 @@ function of { function ovs { echo "Installing Open vSwitch..." + # Required for module build/dkms install + $install $KERNEL_HEADERS + # First see if we have packages # XXX wget -c seems to fail from github/amazon s3 if wget $OVS_PACKAGE_LOC/$OVS_PACKAGE_NAME; then @@ -235,7 +238,6 @@ function ovs { fi fi - $install $KERNEL_HEADERS $install pkg-config gcc make python-dev libssl-dev libtool if [ "$DIST" = "Debian" ]; then From 148a3f5735e0a8af991aa5023e6067fbbe607267 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 18:30:49 -0800 Subject: [PATCH 020/250] Still dealing with install directory issues... --- util/install.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/install.sh b/util/install.sh index 392d3c6..1d60411 100755 --- a/util/install.sh +++ b/util/install.sh @@ -194,6 +194,7 @@ function of { BLACKLIST=/etc/modprobe.d/blacklist fi sudo sh -c "echo 'blacklist net-pf-10\nblacklist ipv6' >> $BLACKLIST" + cd ~ } # Install Open vSwitch @@ -206,6 +207,7 @@ function ovs { # 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; then # Install dkms dependencies $install patch dkms fakeroot @@ -228,6 +230,7 @@ function ovs { sudo update-rc.d openvswitch-controller disable fi echo "Done (hopefully) installing packages" + cd ~ return fi From 1c0b54e52a7115149400025e3ae9837eccb606ff Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 18:47:22 -0800 Subject: [PATCH 021/250] Update OVS build suffix. --- util/build-ovs-packages.sh | 2 +- util/install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/util/build-ovs-packages.sh b/util/build-ovs-packages.sh index 23c2757..6a14659 100755 --- a/util/build-ovs-packages.sh +++ b/util/build-ovs-packages.sh @@ -10,7 +10,7 @@ ksrc=/lib/modules/$kvers/build dist=`lsb_release -is | tr [A-Z] [a-z]` release=`lsb_release -rs` arch=`uname -m` -buildsuffix=-1 +buildsuffix=-2 if [ "$arch" = "i686" ]; then arch=i386; fi if [ "$arch" = "x86_64" ]; then arch=amd64; fi diff --git a/util/install.sh b/util/install.sh index 1d60411..42e2f51 100755 --- a/util/install.sh +++ b/util/install.sh @@ -70,7 +70,7 @@ DRIVERS_DIR=/lib/modules/${KERNEL_NAME}/kernel/drivers/net OVS_RELEASE=1.4.0 OVS_PACKAGE_LOC=https://github.com/downloads/mininet/mininet -OVS_BUILDSUFFIX=-1 +OVS_BUILDSUFFIX=-2 OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar OVS_SRC=~/openvswitch OVS_TAG=v$OVS_RELEASE From 7a0ee56c807c854b739d3d0ed0843323ff567324 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 18:52:07 -0800 Subject: [PATCH 022/250] openvswitch-switch needs python-argparse --- util/install.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util/install.sh b/util/install.sh index 42e2f51..00ecc7d 100755 --- a/util/install.sh +++ b/util/install.sh @@ -209,8 +209,7 @@ function ovs { # XXX wget -c seems to fail from github/amazon s3 cd /tmp if wget $OVS_PACKAGE_LOC/$OVS_PACKAGE_NAME; then - # Install dkms dependencies - $install patch dkms fakeroot + $install patch dkms fakeroot python-argparse tar xf $OVS_PACKAGE_NAME orig=`tar tf $OVS_PACKAGE_NAME` # Now install packages in reasonable dependency order From 65d46518c06810d2dedb9a4d104e0441b5f04e2f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 20:33:59 -0800 Subject: [PATCH 023/250] Don't crash if we can't uninstall kernel. --- util/install.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/util/install.sh b/util/install.sh index 00ecc7d..79943ab 100755 --- a/util/install.sh +++ b/util/install.sh @@ -64,7 +64,7 @@ fi DIST_LC=`echo $DIST | tr [A-Z] [a-z]` # as lower case # Kernel Deb pkg to be removed: -KERNEL_IMAGE_OLD=linux-image-2.6.26-2-686 +KERNEL_IMAGE_OLD=linux-image-2.6.26-33-generic DRIVERS_DIR=/lib/modules/${KERNEL_NAME}/kernel/drivers/net @@ -111,7 +111,9 @@ function kernel_clean { echo "Cleaning kernel..." # To save disk space, remove previous kernel - $remove $KERNEL_IMAGE_OLD + if ! $remove $KERNEL_IMAGE_OLD; then + echo $KERNEL_IMAGE_OLD not installed. + endif # Also remove downloaded packages: rm -f ~/linux-headers-* ~/linux-image-* From de5d31184ff7f18d9706cf987913d25a8497b564 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 13 Feb 2012 20:34:59 -0800 Subject: [PATCH 024/250] Ugh, typo. --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 79943ab..ae45548 100755 --- a/util/install.sh +++ b/util/install.sh @@ -113,7 +113,7 @@ function kernel_clean { # To save disk space, remove previous kernel if ! $remove $KERNEL_IMAGE_OLD; then echo $KERNEL_IMAGE_OLD not installed. - endif + fi # Also remove downloaded packages: rm -f ~/linux-headers-* ~/linux-image-* From 7a106d9b0dcf5cf63c5bd06831e9d5502c2c415d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 14 Feb 2012 15:25:22 -0800 Subject: [PATCH 025/250] Script for installing mininet + tutorial into new VM. --- util/vm/install-mininet-vm.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 util/vm/install-mininet-vm.sh diff --git a/util/vm/install-mininet-vm.sh b/util/vm/install-mininet-vm.sh new file mode 100644 index 0000000..db7ae65 --- /dev/null +++ b/util/vm/install-mininet-vm.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# This script is intended to install Mininet into +# a brand-new Ubuntu (10.04 or 11.10) virtual machine, +# to create a fully usable "tutorial" VM. + +set -e +sudo sh -c 'cat >> /etc/sudoers' < Date: Tue, 14 Feb 2012 16:35:12 -0800 Subject: [PATCH 026/250] Added setting NOX_CORE_DIR in .bashrc - this should not be necessary. --- util/vm/install-mininet-vm.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/vm/install-mininet-vm.sh b/util/vm/install-mininet-vm.sh index db7ae65..9347307 100644 --- a/util/vm/install-mininet-vm.sh +++ b/util/vm/install-mininet-vm.sh @@ -24,6 +24,9 @@ cd mininet git checkout -b 1.4 origin/devel/ovs-1.4-compat cd time mininet/util/install.sh +if ! grep NOX_CORE_DIR .bashrc; then + echo "export NOX_CORE_DIR=~/noxcore/build/src/" >> .bashrc +fi echo < Date: Fri, 2 Mar 2012 15:39:05 -0800 Subject: [PATCH 027/250] Added errcheck target which only checks for errors. --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index f3e26e0..cc65d2e 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,11 @@ codecheck: $(PYSRC) pylint --rcfile=.pylint $(PYSRC) pep8 --repeat --ignore=$(P8IGN) $(PYSRC) +errcheck: $(PYSRC) + -echo "Running check for errors only" + pyflakes $(PYSRC) + pylint -E --rcfile=.pylint $(PYSRC) + test: $(MININET) $(TEST) -echo "Running tests" mininet/test/test_nets.py From 134a75ef387c77641d2ac2b7de473437733308a1 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 2 Mar 2012 15:43:07 -0800 Subject: [PATCH 028/250] Fix pylint complaint and add natural sort key function. --- mininet/util.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mininet/util.py b/mininet/util.py index 112269b..da83490 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -3,9 +3,9 @@ from time import sleep from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE from select import poll, POLLIN -from os import read from subprocess import call, check_call, Popen, PIPE, STDOUT from mininet.log import output, error +import re # Command execution support @@ -33,7 +33,7 @@ def oldQuietRun( *cmd ): # We can't use Popen.communicate() because it uses # select(), which can't handle # high file descriptor numbers! poll() can, however. - output = '' + out = '' readable = poll() readable.register( popen.stdout ) while True: @@ -41,11 +41,11 @@ def oldQuietRun( *cmd ): data = popen.stdout.read( 1024 ) if len( data ) == 0: break - output += data + out += data popen.poll() if popen.returncode != None: break - return output + return out # This is a bit complicated, but it enables us to # monitor commount output as it is happening @@ -254,3 +254,9 @@ def fixLimits(): "Fix ridiculously small resource limits." setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) ) setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) ) + +def natural( text ): + "To sort sanely/alphabetically: sorted( l, key=natural )" + def num( s ): + return int( s ) if s.isdigit() else text + return [ num( s ) for s in re.split( r'(\d+)', text ) ] From 6f446f6e5521ee0519f35a531914977f2568b0c8 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 2 Mar 2012 15:45:10 -0800 Subject: [PATCH 029/250] Make pylint happy. --- examples/consoles.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/consoles.py b/examples/consoles.py index 607163d..5729454 100755 --- a/examples/consoles.py +++ b/examples/consoles.py @@ -107,8 +107,10 @@ class Console( Frame ): self.text.insert( 'end', text ) self.text.mark_set( 'insert', 'end' ) self.text.see( 'insert' ) + outputHook = lambda x,y: True # make pylint happy if self.outputHook: - self.outputHook( self, text ) + outputHook = self.outputHook + outputHook( self, text ) def handleKey( self, event ): "If it's an interactive command, send it to the node." From a6bcad8f48aabae68287178c8dd54cff634a4982 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 2 Mar 2012 15:45:21 -0800 Subject: [PATCH 030/250] Intf and Link classes. Latter support bandwidth limits using tc. --- mininet/link.py | 275 ++++++++++++++++++++++++++++++++++++++++++++++++ mininet/net.py | 79 ++++++++------ mininet/node.py | 196 +++++++++++++--------------------- mininet/topo.py | 62 ++++++++--- 4 files changed, 448 insertions(+), 164 deletions(-) create mode 100644 mininet/link.py diff --git a/mininet/link.py b/mininet/link.py new file mode 100644 index 0000000..c7c7aa2 --- /dev/null +++ b/mininet/link.py @@ -0,0 +1,275 @@ +""" + +link.py: interface and link abstractions for mininet + +It seems useful to bundle functionality for interfaces into a single +class. + +Also it seems useful to enable the possibility of multiple flavors of +links, including: + +- simple veth pairs +- tunneled links +- patchable links (which can be disconnected and reconnected via a patchbay) +- link simulators (e.g. wireless) + +Basic division of labor: + + Nodes: know how to execute commands + Intfs: know how to configure themselves + Links: know how to connect nodes together + +""" + +from mininet.log import info, error, debug +from mininet.util import makeIntfPair +from time import sleep +import re + +class BasicIntf( object ): + + "Basic interface object that can configure itself." + + def __init__( self, node, name=None, link=None, **kwargs ): + """node: owning node (where this intf most likely lives) + name: interface name (e.g. h1-eth0) + link: parent link if any + other arguments are used to configure link parameters""" + self.node = node + self.name = name + self.link = link + self.mac, self.ip = None, None + self.config( **kwargs ) + + def cmd( self, *args, **kwargs ): + self.node.cmd( *args, **kwargs ) + + def ifconfig( self, *args ): + "Configure ourselves using ifconfig" + return self.cmd( 'ifconfig', self.name, *args ) + + def setIP( self, ipstr ): + """Set our IP address""" + # This is a sign that we should perhaps rethink our prefix + # mechanism + self.ip, self.prefixLen = ipstr.split( '/' ) + return self.ifconfig( ipstr, 'up' ) + + def setMAC( self, macstr ): + """Set the MAC address for an interface. + macstr: MAC address as string""" + self.mac = macstr + return ( self.ifconfig( 'down' ) + + self.ifconfig( 'hw', 'ether', macstr ) + + self.ifconfig( 'up' ) ) + + _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' ) + _macMatchRegex = re.compile( r'..:..:..:..:..:..' ) + + def updateIP( self ): + "Return updated IP address based on ifconfig" + ifconfig = self.ifconfig() + ips = self._ipMatchRegex.findall( ifconfig ) + self.ip = ips[ 0 ] if ips else None + return self.ip + + def updateMAC( self, intf ): + "Return updated MAC address based on ifconfig" + ifconfig = self.ifconfig() + macs = self._macMatchRegex.findall( ifconfig ) + self.mac = macs[ 0 ] if macs else None + return self.mac + + def IP( self ): + "Return IP address" + return self.ip + + def MAC( self ): + "Return MAC address" + return self.mac + + def isUp( self, set=False ): + "Return whether interface is up" + return "UP" in self.ifconfig() + + + # Map of config params to config methods + # Perhaps this could be more graceful, but it + # is flexible + configMap = { 'mac': 'setMAC', + 'ip': 'setIP', + 'ifconfig': 'ifconfig' } + + def config( self, **params ): + "Configure interface based on parameters" + self.__dict__.update(**params) + for name, value in params.iteritems(): + method = self.configMap.get( name, None ) + if method: + if type( value ) is str: + value = value.split( ',' ) + method( value ) + + def delete( self ): + "Delete interface" + self.cmd( 'ip link del ' + self.name ) + # Does it help to sleep to let things run? + sleep( 0.001 ) + + def __str__( self ): + return self.name + + +class TCIntf( BasicIntf ): + "Interface customized by tc (traffic control) utility" + + def config( self, bw=None, delay=None, loss=0, disable_gro=True, + speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False, + enable_red=False, max_queue_size=1000, **kwargs ): + "Configure the port and set its properties." + + BasicIntf.config( self, **kwargs) + + # disable GRO + if disable_gro: + self.cmd( 'ethtool -K %s gro off' % self ) + + if bw is None and not delay and not loss: + return + + if bw and ( bw < 0 or bw > 1000 ): + error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' ) + return + + if delay and delay < 0: + error( 'Negative delay', delay, '\n' ) + return + + if loss and ( loss < 0 or loss > 100 ): + error( 'Bad loss percentage', loss, '%%\n' ) + return + + if delay is None: + delay = '0ms' + + if bw is not None and delay is not None: + info( self, '(bw %.2fMbit, delay %s, loss %d%%)\n' % + ( bw, delay, loss ) ) + + # BL: hmm... what exactly is this??? + # This seems kind of brittle + if speedup > 0 and self.node.name[0:2] == 'sw': + bw = speedup + + tc = 'tc' # was getCmd( 'tc' ) + + # Bandwidth control algorithms + if use_hfsc: + cmds = [ '%s qdisc del dev %s root', + '%s qdisc add dev %s root handle 1:0 hfsc default 1' ] + if bw is not None: + cmds.append( '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' + + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ) + elif use_tbf: + latency_us = 10 * 1500 * 8 / bw + cmds = ['%s qdisc del dev %s root', + '%s qdisc add dev %s root handle 1: tbf ' + + 'rate %fMbit burst 15000 latency %fus' % (bw, latency_us) ] + else: + cmds = [ '%s qdisc del dev %s root', + '%s qdisc add dev %s root handle 1:0 htb default 1', + '%s class add dev %s parent 1:0 classid 1:1 htb ' + + 'rate %fMbit burst 15k' % bw ] + + # ECN or RED + if enable_ecn: + info( 'Enabling ECN\n' ) + cmds += [ '%s qdisc add dev %s parent 1:1 '+ + 'handle 10: red limit 1000000 '+ + 'min 20000 max 25000 avpkt 1000 '+ + 'burst 20 '+ + 'bandwidth %fmbit probability 1 ecn' % bw ] + elif enable_red: + info( 'Enabling RED\n' ) + cmds += [ '%s qdisc add dev %s parent 1:1 '+ + 'handle 10: red limit 1000000 '+ + 'min 20000 max 25000 avpkt 1000 '+ + 'burst 20 '+ + 'bandwidth %fmbit probability 1' % bw ] + else: + cmds += [ '%s qdisc add dev %s parent 1:1 handle 10:0 netem ' + + 'delay ' + '%s' % delay + ' loss ' + '%d' % loss + + ' limit %d' % (max_queue_size) ] + + # Execute all the commands in the container + debug("at map stage w/cmds: %s\n" % cmds) + + def doConfigPort(s): + c = s % (tc, self) + debug(" *** executing command: %s\n" % c) + return self.cmd(c) + + outputs = [ doConfigPort(cmd) for cmd in cmds ] + debug( "outputs: %s\n" % outputs ) + +Intf = TCIntf + +class Link( object ): + + """A basic link is just a veth pair. + Other types of links could be tunnels, link emulators, etc..""" + + def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None, + intf=Intf, params1={}, params2={} ): + """Create veth link to another node, making two new interfaces. + node1: first node + node2: second node + port1: node1 port number (optional) + port2: node2 port number (optional) + intfName1: node1 interface name (optional) + intfName2: node2 interface name (optional)""" + # This is a bit awkward; it seems that having everything in + # params would be more orthogonal, but being able to specify + # in-line arguments is more convenient! + if port1 is None: + port1 = node1.newPort() + if port2 is None: + port2 = node2.newPort() + if not intfName1: + intfName1 = self.intfName( node1, port1 ) + if not intfName2: + intfName2 = self.intfName( node2, port2 ) + self.makeIntfPair( intfName1, intfName2 ) + intf1 = intf( name=intfName1, node=node1, link=self, **params1 ) + intf2 = intf( name=intfName2, node=node2, link=self, **params2 ) + # Add to nodes + node1.addIntf( intf1 ) + node2.addIntf( intf2 ) + self.intf1, self.intf2 = intf1, intf2 + + @classmethod + def intfName( cls, node, n ): + "Construct a canonical interface name node-ethN for interface n." + return node.name + '-eth' + repr( n ) + + @classmethod + def makeIntfPair( cls, intf1, intf2 ): + """Create pair of interfaces + intf1: name of interface 1 + intf2: name of interface 2 + (override this class method [and possibly delete()] to change link type)""" + makeIntfPair( intf1, intf2 ) + + def delete( self ): + "Delete this link" + self.intf1.delete() + self.intf2.delete() + + def __str__( self ): + return '%s<->%s' % ( self.intf1, self.intf2 ) + + + + + + diff --git a/mininet/net.py b/mininet/net.py index 973a694..c5c2931 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -1,6 +1,6 @@ """ - Mininet: A simple networking testbed for OpenFlow! + Mininet: A simple networking testbed for OpenFlow/SDN! author: Bob Lantz (rlantz@cs.stanford.edu) author: Brandon Heller (brandonh@stanford.edu) @@ -96,6 +96,7 @@ from mininet.cli import CLI from mininet.log import info, error, debug, output from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller from mininet.node import ControllerParams +from mininet.link import Link from mininet.util import quietRun, fixLimits from mininet.util import createLink, macColonHex, ipStr, ipParse from mininet.term import cleanUpScreens, makeTerms @@ -104,16 +105,17 @@ class Mininet( object ): "Network emulation with hosts spawned in network namespaces." def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, - controller=Controller, + controller=Controller, link=Link, cparams=ControllerParams( '10.0.0.0', 8 ), build=True, xterms=False, cleanup=False, inNamespace=False, autoSetMacs=False, autoStaticArp=False, listenPort=None ): """Create Mininet object. topo: Topo (topology) object or None - switch: Switch class - host: Host class - controller: Controller class + switch: default Switch class + host: default Host class/constructor + controller: default Controller class/constructor + link: default Link class/constructor cparams: ControllerParams object build: build now from topo? xterms: if build now, spawn xterms? @@ -126,6 +128,7 @@ class Mininet( object ): self.switch = switch self.host = host self.controller = controller + self.link = link self.cparams = cparams self.topo = topo self.inNamespace = inNamespace @@ -150,30 +153,38 @@ class Mininet( object ): if topo and build: self.build() - def addHost( self, name, mac=None, ip=None ): + # BL Note: + # The specific items for host/switch/etc. should probably be + # handled in the node classes rather than here!! + + def addHost( self, name, mac=None, ip=None, host=None, **params ): """Add host. name: name of host to add mac: default MAC address for intf 0 ip: default IP address for intf 0 returns: added host""" - host = self.host( name, defaultMAC=mac, defaultIP=ip ) - self.hosts.append( host ) - self.nameToNode[ name ] = host - return host + if not host: + host = self.host + defaults = { 'defaultMAC': mac, 'defaultIP': ip } + defaults.update( params ) + h = host( name, **defaults) + self.hosts.append( h ) + self.nameToNode[ name ] = h + return h - def addSwitch( self, name, mac=None, ip=None ): + def addSwitch( self, name, switch=None, **params ): """Add switch. name: name of switch to add - mac: default MAC address for kernel/OVS switch intf 0 returns: added switch - side effect: increments the listenPort member variable.""" - if self.switch == UserSwitch: - sw = self.switch( name, listenPort=self.listenPort, - defaultMAC=mac, defaultIP=ip, inNamespace=self.inNamespace ) - else: - sw = self.switch( name, listenPort=self.listenPort, - defaultMAC=mac, defaultIP=ip, dp=self.dps, - inNamespace=self.inNamespace ) + side effect: increments listenPort and dps ivars.""" + defaults = { 'listenPort': self.listenPort, + 'inNamespace': self.inNamespace } + if not switch: + switch = self.switch + if switch != UserSwitch: + defaults[ 'dps' ] = self.dps + defaults.update( params ) + sw = self.switch( name, **defaults ) if not self.inNamespace and self.listenPort: self.listenPort += 1 self.dps += 1 @@ -181,12 +192,12 @@ class Mininet( object ): self.nameToNode[ name ] = sw return sw - def addController( self, name='c0', controller=None, **kwargs ): + def addController( self, name='c0', controller=None, **params ): """Add controller. controller: Controller class""" if not controller: controller = self.controller - controller_new = controller( name, **kwargs ) + controller_new = controller( name, **params ) if controller_new: # allow controller-less setups self.controllers.append( controller_new ) self.nameToNode[ name ] = controller_new @@ -210,6 +221,9 @@ class Mininet( object ): # 4. Even if we dispense with this in general, it could still be # useful for people who wish to simulate a separate control # network (since real networks may need one!) + # + # 5. Basically nobody ever uses this method, so perhaps it should be moved + # out of this core class. def configureControlNetwork( self ): "Configure control network." @@ -221,8 +235,7 @@ class Mininet( object ): def configureRoutedControlNetwork( self, ip='192.168.123.1', prefixLen=16 ): """Configure a routed control network on controller and switches. - For use with the user datapath only right now. - """ + For use with the user datapath only right now.""" controller = self.controllers[ 0 ] info( controller.name + ' <->' ) cip = ip @@ -256,8 +269,8 @@ class Mininet( object ): "Configure a set of hosts." # params were: hosts, ips for host in self.hosts: - hintf = host.intfs[ 0 ] - host.setIP( hintf, host.defaultIP, self.cparams.prefixLen ) + hintf = host.defaultIntf() + host.setIP( host.defaultIP, self.cparams.prefixLen, hintf ) host.setDefaultRoute( hintf ) # You're low priority, dude! quietRun( 'renice +18 -p ' + repr( host.pid ) ) @@ -272,9 +285,11 @@ class Mininet( object ): def addNode( prefix, addMethod, nodeId ): "Add a host or a switch." name = prefix + topo.name( nodeId ) + # MAC and IP should probably be from nodeInfo... mac = macColonHex( nodeId ) if self.setMacs else None ip = topo.ip( nodeId ) - node = addMethod( name, mac=mac, ip=ip ) + ni = topo.nodeInfo( nodeId ) + node = addMethod( name, cls=ni.cls, mac=mac, ip=ip, **ni.params ) self.idToNode[ nodeId ] = node info( name + ' ' ) @@ -291,12 +306,16 @@ class Mininet( object ): addNode( 'h', self.addHost, hostId ) info( '\n*** Adding switches:\n' ) for switchId in sorted( topo.switches() ): - addNode( 's', self.addSwitch, switchId ) + addNode( 's', self.addSwitch, switchId) info( '\n*** Adding links:\n' ) for srcId, dstId in sorted( topo.edges() ): src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] srcPort, dstPort = topo.port( srcId, dstId ) - createLink( src, dst, srcPort, dstPort ) + ei = topo.edgeInfo( srcId, dstId ) + link, params = ei.cls, ei.params + if not link: + link = self.link + link( src, dst, srcPort, dstPort, **params ) info( '(%s, %s) ' % ( src.name, dst.name ) ) info( '\n' ) @@ -510,7 +529,7 @@ class Mininet( object ): servout += server.monitor() while 'Connected' not in client.cmd( 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()): - output('waiting for iperf to start up') + output('waiting for iperf to start up...') sleep(.5) cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' + bwArgs ) diff --git a/mininet/node.py b/mininet/node.py index 70381ec..b9262e3 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -46,11 +46,11 @@ import re import signal import select from subprocess import Popen, PIPE, STDOUT -from time import sleep from mininet.log import info, error, debug -from mininet.util import quietRun, errRun, makeIntfPair, moveIntf, isShellBuiltin +from mininet.util import quietRun, errRun, moveIntf, isShellBuiltin from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN +from mininet.link import Link SWITCH_PORT_BASE = 1 # For OF > 0.9, switch ports start at 1 rather than zero @@ -78,7 +78,7 @@ class Node( object ): opts += 'n' cmd = [ 'mnexec', opts, 'bash', '-m' ] self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, - close_fds=False ) + close_fds=True ) self.stdin = self.shell.stdin self.stdout = self.shell.stdout self.pid = self.shell.pid @@ -89,12 +89,10 @@ class Node( object ): # using select.poll() self.outToNode[ self.stdout.fileno() ] = self self.inToNode[ self.stdin.fileno() ] = self - self.intfs = {} # dict of port numbers to interface names - self.ports = {} # dict of interface names to port numbers + self.intfs = {} # dict of port numbers to interfaces + self.ports = {} # dict of interfaces to port numbers # replace with Port objects, eventually ? - self.ips = {} # dict of interfaces to ip addresses as strings - self.macs = {} # dict of interfacesto mac addresses as strings - self.connection = {} # remote node connected to each interface + self.nameToIntf = {} # dict of interface names to Intfs self.execed = False self.lastCmd = None self.lastPid = None @@ -172,7 +170,7 @@ class Node( object ): if len( args ) > 0: cmd = args if not isinstance( cmd, str ): - cmd = ' '.join( cmd ) + cmd = ' '.join( [ str( c ) for c in cmd ] ) if not re.search( r'\w', cmd ): # Replace empty commands with something harmless cmd = 'echo -n' @@ -254,10 +252,6 @@ class Node( object ): # the real interfaces are created as veth pairs, so we can't # make a single interface at a time. - def intfName( self, n ): - "Construct a canonical interface name node-ethN for interface n." - return self.name + '-eth' + repr( n ) - def newPort( self ): "Return the next port number to allocate." if len( self.ports ) > 0: @@ -266,56 +260,45 @@ class Node( object ): def addIntf( self, intf, port=None ): """Add an interface. - intf: interface name (e.g. nodeN-ethM) + intf: interface port: port number (optional, typically OpenFlow port number)""" if port is None: port = self.newPort() self.intfs[ port ] = intf self.ports[ intf ] = port - #info( '\n' ) - #info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) + self.nameToIntf[ intf.name ] = intf + info( '\n' ) + info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) if self.inNamespace: - #info( 'moving w/inNamespace set\n' ) - moveIntf( intf, self ) + info( 'moving', intf, 'into namespace for', self.name, '\n' ) + moveIntf( intf.name, self ) - def registerIntf( self, intf, dstNode, dstIntf ): - "Register connection of intf to dstIntf on dstNode." - self.connection[ intf ] = ( dstNode, dstIntf ) + def defaultIntf( self ): + "Return interface for lowest port" + ports = self.intfs.keys() + if ports: + return self.intfs[ min( ports ) ] - def connectionsTo( self, node): - "Return [(srcIntf, dstIntf)..] for connections to dstNode." + def intf( self, intf='' ): + """Return our interface object with given name,x + or default intf if name is empty""" + if not intf: + return self.defaultIntf() + elif type( intf) is str: + return self.nameToIntf[ intf ] + else: + return intf + + def linksTo( self, node): + "Return [ link1, link2...] for all links from self to node." # We could optimize this if it is important - connections = [] - for intf in self.connection.keys(): - dstNode, dstIntf = self.connection[ intf ] - if dstNode == node: - connections.append( ( intf, dstIntf ) ) - return connections - - # This is a symmetric operation, but it makes sense to put - # the code here since it is tightly coupled to routines in - # this class. For a more symmetric API, you can use - # mininet.util.createLink() - - def linkTo( self, node2, port1=None, port2=None ): - """Create link to another node, making two new interfaces. - node2: Node to link us to - port1: our port number (optional) - port2: node2 port number (optional) - returns: intf1 name, intf2 name""" - node1 = self - if port1 is None: - port1 = node1.newPort() - if port2 is None: - port2 = node2.newPort() - intf1 = node1.intfName( port1 ) - intf2 = node2.intfName( port2 ) - makeIntfPair( intf1, intf2 ) - node1.addIntf( intf1, port1 ) - node2.addIntf( intf2, port2 ) - node1.registerIntf( intf1, node2, intf2 ) - node2.registerIntf( intf2, node1, intf1 ) - return intf1, intf2 + links = [] + for intf in self.intfs: + link = intf.link + nodes = ( link.intf1.node, link.intf2.node ) + if self in nodes and node in nodes: + links.append( link ) + return links def deleteIntfs( self ): "Delete all of our interfaces." @@ -325,18 +308,10 @@ class Node( object ): # have been removed by the kernel. Unfortunately this is very slow, # at least with Linux kernels before 2.6.33 for intf in self.intfs.values(): - quietRun( 'ip link del ' + intf ) + intf.delete() info( '.' ) - # Does it help to sleep to let things run? - sleep( 0.001 ) - def setMAC( self, intf, mac ): - """Set the MAC address for an interface. - mac: MAC address as string""" - result = self.cmd( 'ifconfig', intf, 'down' ) - result += self.cmd( 'ifconfig', intf, 'hw', 'ether', mac ) - result += self.cmd( 'ifconfig', intf, 'up' ) - return result + # Routing support def setARP( self, ip, mac ): """Add an ARP entry. @@ -345,16 +320,6 @@ class Node( object ): result = self.cmd( 'arp', '-s', ip, mac ) return result - def setIP( self, intf, ip, prefixLen=8 ): - """Set the IP address for an interface. - intf: interface name - ip: IP address as a string - prefixLen: prefix length, e.g. 8 for /8 or 16M addrs""" - ipSub = '%s/%d' % ( ip, prefixLen ) - result = self.cmd( 'ifconfig', intf, ipSub, 'up' ) - self.ips[ intf ] = ip - return result - def setHostRoute( self, ip, intf ): """Add route to host. ip: IP address as dotted decimal @@ -365,62 +330,52 @@ class Node( object ): """Set the default route to go through intf. intf: string, interface name""" self.cmd( 'ip route flush root 0/0' ) - return self.cmd( 'route add default ' + intf ) + return self.cmd( 'route add default %s' % intf ) - def defaultIntf( self ): - "Return interface for lowest port" - ports = self.intfs.keys() - if ports: - return self.intfs[ min( ports ) ] + # Convenience methods - _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' ) - _macMatchRegex = re.compile( r'..:..:..:..:..:..' ) + def setMAC( self, mac, intf=''): + """Set the MAC address for an interface. + intf: intf or intf name + mac: MAC address as string""" + return self.intf( intf ).setMAC( mac ) + + def setIP( self, ip, prefixLen=8, intf='' ): + """Set the IP address for an interface. + intf: interface name + ip: IP address as a string + prefixLen: prefix length, e.g. 8 for /8 or 16M addrs""" + # This should probably be rethought: + ipSub = '%s/%s' % ( ip, prefixLen ) + return self.intf( intf ).setIP( ipSub ) def IP( self, intf=None ): "Return IP address of a node or specific interface." - if intf is None: - intf = self.defaultIntf() - if intf and not self.waiting: - self.updateIP( intf ) - return self.ips.get( intf, None ) + return self.intf( intf ).IP() def MAC( self, intf=None ): "Return MAC address of a node or specific interface." - if intf is None: - intf = self.defaultIntf() - if intf and not self.waiting: - self.updateMAC( intf ) - return self.macs.get( intf, None ) + return self.intf( intf ).MAC() - def updateIP( self, intf ): - "Update IP address for an interface" - assert not self.waiting - ifconfig = self.cmd( 'ifconfig ' + intf ) - ips = self._ipMatchRegex.findall( ifconfig ) - if ips: - self.ips[ intf ] = ips[ 0 ] - else: - self.ips[ intf ] = None - - def updateMAC( self, intf ): - "Update MAC address for an interface" - assert not self.waiting - ifconfig = self.cmd( 'ifconfig ' + intf ) - macs = self._macMatchRegex.findall( ifconfig ) - if macs: - self.macs[ intf ] = macs[ 0 ] - else: - self.macs[ intf ] = None - - def intfIsUp( self, intf ): + def intfIsUp( self, intf=None ): "Check if an interface is up." - return 'UP' in self.cmd( 'ifconfig ' + intf ) + return self.intf( intf ).isUp() + + # This is here for backward compatibility + def linkTo( self, node, link=Link ): + """(Deprecated) Link to another node + replace with Link( node1, node2)""" + return link( self, node ) # Other methods + + def intfNames( self ): + "The names of our interfaces" + return [ str( i ) for i in sorted( self.ports.values() ) ] + def __str__( self ): - intfs = sorted( self.intfs.values() ) return '%s: IP=%s intfs=%s pid=%s' % ( - self.name, self.IP(), ','.join( intfs ), self.pid ) + self.name, self.IP(), ','.join( self.intfNames() ), self.pid ) class Host( Node ): @@ -623,10 +578,9 @@ class OVSSwitch( Switch ): def __init__( self, name, dp=None, **kwargs ): """Init. name: name for switch - dp: netlink id (0, 1, 2, ...) defaultMAC: default MAC as unsigned int; random value if None""" Switch.__init__( self, name, **kwargs ) - self.dp = 'dp%i' % dp + self.dp = name @staticmethod def setup(): @@ -671,7 +625,6 @@ class OVSSwitch( Switch ): OVSKernelSwitch = OVSSwitch - class Controller( Node ): """A Controller is a Node that is running (or has execed?) an OpenFlow controller.""" @@ -704,8 +657,9 @@ class Controller( Node ): def IP( self, intf=None ): "Return IP address of the Controller" - ip = Node.IP( self, intf=intf ) - if ip is None: + if self.intfs: + ip = Node.IP( self, intf ) + else: ip = self.defaultIP return ip diff --git a/mininet/topo.py b/mininet/topo.py index f7de5fc..6330f56 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,7 +16,14 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph -from mininet.node import SWITCH_PORT_BASE +from mininet.node import SWITCH_PORT_BASE, Host, OVSSwitch +from mininet.link import Link + +# BL: it's hard to figure out how to do this right yet remain flexible +# These classes will be used as the defaults if no class is passed +# into either Topo() or Node() +TopoDefaultNode = Host +TopoDefaultSwitch = OVSSwitch class NodeID(object): '''Topo node identifier.''' @@ -54,11 +61,12 @@ class NodeID(object): return "10.%i.%i.%i" % (hi, mid, lo) -class Node(object): +class Node( object ): '''Node-specific vertex metadata for a Topo object.''' - def __init__(self, connected = False, admin_on = True, - power_on = True, fault = False, is_switch = True): + def __init__(self, connected=False, admin_on=True, + power_on=True, fault=False, is_switch=True, + cls=None, **params ): '''Init. @param connected actively connected to controller @@ -66,18 +74,26 @@ class Node(object): @param power_on powered on or off @param fault fault seen on node @param is_switch switch or host + @param cls node class (e.g. Host, Switch) + @param params node parameters ''' self.connected = connected self.admin_on = admin_on self.power_on = power_on self.fault = fault self.is_switch = is_switch + # Above should be deleted and replaced by the following + # BL: is_switch is a bit annoying if we can just specify + # the node class instead!! + self.cls = cls if cls else ( TopoDefaultSwitch if is_switch else TopoDefaultNode ) + self.params = params if params else {} class Edge(object): '''Edge-specific metadata for a StructuredTopo graph.''' - def __init__(self, admin_on = True, power_on = True, fault = False): + def __init__(self, admin_on=True, power_on=True, fault=False, + cls=Link, **params): '''Init. @param admin_on administratively on or off; defaults to True @@ -87,31 +103,40 @@ class Edge(object): self.admin_on = admin_on self.power_on = power_on self.fault = fault + # Above should be deleted and replaced by the following + self.cls = cls + self.params = params class Topo(object): '''Data center network representation for structured multi-trees.''' - - def __init__(self): - '''Create Topo object. - - ''' + + def __init__(self, node=Host, switch=None, link=Link): + """Create Topo object. + node: default node/host class + switch: default switch class + Link: default link class""" self.g = Graph() self.node_info = {} # dpids hash to Node objects self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects self.ports = {} # ports[src][dst] is port on src that connects to dst self.id_gen = NodeID # class used to generate dpid + self.node = node + self.switch = switch + self.link = link - def add_node(self, dpid, node): + def add_node(self, dpid, node=None): '''Add Node to graph. @param dpid dpid @param node Node object ''' self.g.add_node(dpid) + if not node: + node = Node( link=self.link ) self.node_info[dpid] = node - def add_edge(self, src, dst, edge = None): + def add_edge(self, src, dst, edge=None): '''Add edge (Node, Node) to graph. @param src src dpid @@ -121,7 +146,7 @@ class Topo(object): src, dst = tuple(sorted([src, dst])) self.g.add_edge(src, dst) if not edge: - edge = Edge() + edge = Edge( link=self.link ) self.edge_info[(src, dst)] = edge self.add_port(src, dst) @@ -276,6 +301,12 @@ class Topo(object): assert dst in self.ports and src in self.ports[dst] return (self.ports[src][dst], self.ports[dst][src]) + def edgeInfo( self, src, dst ): + "Return edge metadata" + # BL: Perhaps this should be rethought or we should just use the + # dicts... + return self.edge_info[ ( src, dst ) ] + def enable_edges(self): '''Enable all edges in the network graph. @@ -321,7 +352,12 @@ class Topo(object): ''' return self.id_gen(dpid = dpid).ip_str() + def nodeInfo( self, dpid ): + "Return metadata for node" + # BL: may wish to rethink this or just use dicts.. + return self.node_info[ dpid ] + class SingleSwitchTopo(Topo): '''Single switch connected to k hosts.''' From 03dd914edc3970ff7df946ff9cb2732558376537 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 2 Mar 2012 20:34:37 -0800 Subject: [PATCH 031/250] Tease out intfList() from intfNames(). --- mininet/node.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index b9262e3..cfeb02f 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -369,9 +369,13 @@ class Node( object ): # Other methods + def intfList( self ): + "List of our interfaces sorted by port number" + return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ] + def intfNames( self ): - "The names of our interfaces" - return [ str( i ) for i in sorted( self.ports.values() ) ] + "The names of our interfaces sorted by port number" + return [ str( i ) for i in self.intfList() ] def __str__( self ): return '%s: IP=%s intfs=%s pid=%s' % ( From ee222055f1c07499931e0075a9daf0d0c2677fab Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 2 Mar 2012 20:34:56 -0800 Subject: [PATCH 032/250] Use install(1) to install mnexec so that setup.py develop works. --- Makefile | 20 ++++++++++++-------- setup.py | 3 +-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index cc65d2e..681495c 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,16 @@ -all: codecheck test - -clean: - rm -rf build dist *.egg-info *.pyc mnexec bin/mnexec - MININET = mininet/*.py TEST = mininet/test/*.py EXAMPLES = examples/*.py BIN = bin/mn PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN) - +MNEXEC = mnexec P8IGN = E251,E201,E302,E202 +all: codecheck test + +clean: + rm -rf build dist *.egg-info *.pyc $(MNEXEC) + codecheck: $(PYSRC) -echo "Running code check" pyflakes $(PYSRC) @@ -26,10 +26,14 @@ test: $(MININET) $(TEST) -echo "Running tests" mininet/test/test_nets.py -install: mnexec - cp mnexec bin/ +install: $(MNEXEC) + install $(MNEXEC) /usr/local/bin/ python setup.py install +develop: $(MNEXEC) + install $(MNEXEC) /usr/local/bin/ + python setup.py develop + doc: doxygen doxygen.cfg diff --git a/setup.py b/setup.py index 0394245..8bcfb12 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,7 @@ from setuptools import setup, find_packages from os.path import join -scripts = [ join( 'bin', filename ) for filename in [ - 'mn', 'mnexec' ] ] +scripts = [ join( 'bin', filename ) for filename in [ 'mn' ] ] modname = distname = 'mininet' From 542fb6167e89b09a99687cc9ba078b7e19c3031d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 2 Mar 2012 20:36:57 -0800 Subject: [PATCH 033/250] Ignore build, dist and emacs autosaves. --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 1d4a3fe..993ea4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ mnexec *.pyc +*~ mininet.egg-info +build/* +dist/* + From 551a3666eb7e8f56665951f514e63a2e7919f22b Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 5 Mar 2012 15:01:08 -0800 Subject: [PATCH 034/250] Tweak errRun; add errFail and numCores. --- mininet/util.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mininet/util.py b/mininet/util.py index da83490..8975048 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -61,10 +61,14 @@ def errRun( *cmd, **kwargs ): cmd = cmd[ 0 ] if isinstance( cmd, str ): cmd = cmd.split( ' ' ) + cmd = [ str( arg ) for arg in cmd ] # By default we separate stderr, don't run in a shell, and don't echo stderr = kwargs.get( 'stderr', PIPE ) shell = kwargs.get( 'shell', False ) echo = kwargs.get( 'echo', False ) + if echo: + # cmd goes to stderr, output goes to stdout + info( cmd, '\n' ) popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell ) # We use poll() because select() doesn't work with large fd numbers out, err = '', '' @@ -90,6 +94,14 @@ def errRun( *cmd, **kwargs ): break return out, err, returncode +def errFail( *cmd, **kwargs ): + "Run a command using errRun and raise exception on nonzero exit" + out, err, ret = errRun( *cmd, **kwargs ) + if ret: + raise Exception( "errFail: failed with return code %s" + % ret ) + return out, err, ret + def quietRun( cmd, **kwargs ): "Run a command and return merged stdout and stderr" return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ] @@ -260,3 +272,13 @@ def natural( text ): def num( s ): return int( s ) if s.isdigit() else text return [ num( s ) for s in re.split( r'(\d+)', text ) ] + +def numCores(): + "Returns number of CPU cores based on /proc/cpuinfo" + if hasattr( numCores, 'ncores' ): + return numCores.ncores + try: + numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') ) + except ValueError: + return 0 + return numCores.ncores From 7d557fd7596d0f231bc8f025347b88d78ec702bb Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 6 Mar 2012 23:48:26 -0800 Subject: [PATCH 035/250] Remove deprecated reference kernel switch. --- bin/mn | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/mn b/bin/mn index 1a63737..b232389 100755 --- a/bin/mn +++ b/bin/mn @@ -20,7 +20,7 @@ from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info from mininet.net import Mininet, init -from mininet.node import KernelSwitch, Host, Controller, ControllerParams, NOX +from mininet.node import Host, Controller, ControllerParams, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo @@ -35,8 +35,7 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), 'tree': TreeTopo } SWITCHDEF = 'ovsk' -SWITCHES = { 'kernel': KernelSwitch, - 'user': UserSwitch, +SWITCHES = { 'user': UserSwitch, 'ovsk': OVSKernelSwitch } HOSTDEF = 'process' From d8c88bedf3844f946b369a715fcb4102e340923d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 6 Mar 2012 23:49:51 -0800 Subject: [PATCH 036/250] Add custom() function for customizing constructors. --- mininet/util.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mininet/util.py b/mininet/util.py index 8975048..3bf9dd0 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -4,7 +4,7 @@ from time import sleep from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE from select import poll, POLLIN from subprocess import call, check_call, Popen, PIPE, STDOUT -from mininet.log import output, error +from mininet.log import output, info, error import re # Command execution support @@ -282,3 +282,12 @@ def numCores(): except ValueError: return 0 return numCores.ncores + +def custom( cls, **params ): + "Returns customized constructor for class cls." + def customized( *args, **kwargs): + kwargs.update( params ) + return cls( *args, **kwargs ) + return customized + + From 94c02695fd95c135896384a5b10fb1065e0bfc8e Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 6 Mar 2012 23:50:46 -0800 Subject: [PATCH 037/250] Clarify precedence of default classes. --- mininet/topo.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index 6330f56..2c8635b 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -17,7 +17,6 @@ setup for testing, and can even be emulated with the Mininet package. from networkx import Graph from mininet.node import SWITCH_PORT_BASE, Host, OVSSwitch -from mininet.link import Link # BL: it's hard to figure out how to do this right yet remain flexible # These classes will be used as the defaults if no class is passed @@ -93,7 +92,7 @@ class Edge(object): '''Edge-specific metadata for a StructuredTopo graph.''' def __init__(self, admin_on=True, power_on=True, fault=False, - cls=Link, **params): + cls=None, **params): '''Init. @param admin_on administratively on or off; defaults to True @@ -109,13 +108,17 @@ class Edge(object): class Topo(object): - '''Data center network representation for structured multi-trees.''' + """Data center network representation for structured multi-trees. + Note that the order of precedence is: + per-node/link classes and parameters + per-topo classes + per-network classes""" - def __init__(self, node=Host, switch=None, link=Link): + def __init__(self, node=None, switch=None, link=None): """Create Topo object. - node: default node/host class - switch: default switch class - Link: default link class""" + node: default node/host class (optional) + switch: default switch class (optional) + link: default link class (optional)""" self.g = Graph() self.node_info = {} # dpids hash to Node objects self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects @@ -146,7 +149,7 @@ class Topo(object): src, dst = tuple(sorted([src, dst])) self.g.add_edge(src, dst) if not edge: - edge = Edge( link=self.link ) + edge = Edge( cls=self.link ) self.edge_info[(src, dst)] = edge self.add_port(src, dst) From 84a91a14a46873b088793ebf136637a3486726a3 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 6 Mar 2012 23:52:00 -0800 Subject: [PATCH 038/250] New configuration scheme and support for CPU limits (RT). --- mininet/link.py | 94 ++++++++----- mininet/net.py | 249 ++++++++++++++++---------------- mininet/node.py | 366 ++++++++++++++++++++++++++++++++---------------- 3 files changed, 434 insertions(+), 275 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index c7c7aa2..d3193ca 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -39,10 +39,12 @@ class BasicIntf( object ): self.name = name self.link = link self.mac, self.ip = None, None + # Add to node (and move ourselves if necessary ) + node.addIntf( self ) self.config( **kwargs ) def cmd( self, *args, **kwargs ): - self.node.cmd( *args, **kwargs ) + return self.node.cmd( *args, **kwargs ) def ifconfig( self, *args ): "Configure ourselves using ifconfig" @@ -93,22 +95,43 @@ class BasicIntf( object ): return "UP" in self.ifconfig() - # Map of config params to config methods - # Perhaps this could be more graceful, but it - # is flexible - configMap = { 'mac': 'setMAC', - 'ip': 'setIP', - 'ifconfig': 'ifconfig' } + # The reason why we configure things in this way is so + # That the parameters can be listed and documented in + # the config method. + # Dealing with subclasses and superclasses is slightly + # annoying, but at least the information is there! - def config( self, **params ): - "Configure interface based on parameters" - self.__dict__.update(**params) - for name, value in params.iteritems(): - method = self.configMap.get( name, None ) - if method: - if type( value ) is str: - value = value.split( ',' ) - method( value ) + def setParam( self, result, method, **param ): + """Internal method: configure single parameter + result: dict of results to update + method: config method + param: foo=bar (ignore if bar=None)""" + name, value = param.items()[ 0 ] + if value is None: + return + if type( value ) is list: + result[ name ] = getattr( self, method )( *value ) + elif type( value ) is dict: + result[ name ] = getattr( self, method )( **value ) + else: + result[ name ] = getattr( self, method )( value ) + + def config( self, mac=None, ip=None, ifconfig=None, + defaultRoute=None, **params): + """Configure Node according to (optional) parameters: + mac: MAC address + ip: IP address + ifconfig: arbitrary interface configuration + Subclasses should override this method and call + the parent class's config(**params)""" + # If we were overriding this method, we would call + # the superclass config method here as follows: + # r = Parent.config( **params ) + r = {} + self.setParam( r, 'setMAC', mac=mac ) + self.setParam( r, 'setIP', ip=ip ) + self.setParam( r, 'ifconfig', ifconfig=ifconfig ) + return r def delete( self ): "Delete interface" @@ -125,10 +148,10 @@ class TCIntf( BasicIntf ): def config( self, bw=None, delay=None, loss=0, disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False, - enable_red=False, max_queue_size=1000, **kwargs ): + enable_red=False, max_queue_size=1000, **params ): "Configure the port and set its properties." - BasicIntf.config( self, **kwargs) + result = BasicIntf.config( self, **params) # disable GRO if disable_gro: @@ -153,7 +176,7 @@ class TCIntf( BasicIntf ): delay = '0ms' if bw is not None and delay is not None: - info( self, '(bw %.2fMbit, delay %s, loss %d%%)\n' % + info( self, '(bw %.2fMbit, delay %s, loss %d%%) ' % ( bw, delay, loss ) ) # BL: hmm... what exactly is this??? @@ -209,8 +232,11 @@ class TCIntf( BasicIntf ): debug(" *** executing command: %s\n" % c) return self.cmd(c) - outputs = [ doConfigPort(cmd) for cmd in cmds ] - debug( "outputs: %s\n" % outputs ) + tcoutputs = [ doConfigPort(cmd) for cmd in cmds ] + debug( "cmds:", cmds, '\n' ) + debug( "outputs:", tcoutputs, '\n' ) + result[ 'tcoutputs'] = tcoutputs + return result Intf = TCIntf @@ -220,14 +246,18 @@ class Link( object ): Other types of links could be tunnels, link emulators, etc..""" def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None, - intf=Intf, params1={}, params2={} ): + intf=Intf, cls1=None, cls2=None, params1={}, params2={} ): """Create veth link to another node, making two new interfaces. node1: first node node2: second node port1: node1 port number (optional) port2: node2 port number (optional) + intf: default interface class/constructor + cls1, cls2: optional interface-specific constructors intfName1: node1 interface name (optional) - intfName2: node2 interface name (optional)""" + intfName2: node2 interface name (optional) + params1: parameters for interface 1 + params2: parameters for interface 2""" # This is a bit awkward; it seems that having everything in # params would be more orthogonal, but being able to specify # in-line arguments is more convenient! @@ -240,11 +270,13 @@ class Link( object ): if not intfName2: intfName2 = self.intfName( node2, port2 ) self.makeIntfPair( intfName1, intfName2 ) - intf1 = intf( name=intfName1, node=node1, link=self, **params1 ) - intf2 = intf( name=intfName2, node=node2, link=self, **params2 ) - # Add to nodes - node1.addIntf( intf1 ) - node2.addIntf( intf2 ) + if not cls1: + cls1 = intf + if not cls2: + cls2 = intf + intf1 = cls1( name=intfName1, node=node1, link=self, **params1 ) + intf2 = cls2( name=intfName2, node=node2, link=self, **params2 ) + # All we are is dust in the wind, and our two interfaces self.intf1, self.intf2 = intf1, intf2 @classmethod @@ -267,9 +299,3 @@ class Link( object ): def __str__( self ): return '%s<->%s' % ( self.intf1, self.intf2 ) - - - - - - diff --git a/mininet/net.py b/mininet/net.py index c5c2931..18d0363 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -94,8 +94,7 @@ from time import sleep from mininet.cli import CLI from mininet.log import info, error, debug, output -from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller -from mininet.node import ControllerParams +from mininet.node import Host, OVSKernelSwitch, Controller from mininet.link import Link from mininet.util import quietRun, fixLimits from mininet.util import createLink, macColonHex, ipStr, ipParse @@ -106,7 +105,6 @@ class Mininet( object ): def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, controller=Controller, link=Link, - cparams=ControllerParams( '10.0.0.0', 8 ), build=True, xterms=False, cleanup=False, inNamespace=False, autoSetMacs=False, autoStaticArp=False, listenPort=None ): @@ -116,12 +114,12 @@ class Mininet( object ): host: default Host class/constructor controller: default Controller class/constructor link: default Link class/constructor - cparams: ControllerParams object + ipBase: base IP address for hosts, build: build now from topo? xterms: if build now, spawn xterms? cleanup: if build now, cleanup before creating? inNamespace: spawn switches and controller in net namespaces? - autoSetMacs: set MAC addrs from topo? + autoSetMacs: set MAC addrs from topo dpid? autoStaticArp: set all-pairs static MAC addrs? listenPort: base listening port to open; will be incremented for each additional switch in the net if inNamespace=False""" @@ -129,7 +127,6 @@ class Mininet( object ): self.host = host self.controller = controller self.link = link - self.cparams = cparams self.topo = topo self.inNamespace = inNamespace self.xterms = xterms @@ -141,13 +138,13 @@ class Mininet( object ): self.hosts = [] self.switches = [] self.controllers = [] + self.nameToNode = {} # name to Node (Host/Switch) objects self.idToNode = {} # dpid to Node (Host/Switch) objects - self.dps = 0 # number of created kernel datapaths + self.terms = [] # list of spawned xterm processes - init() - switch.setup() + init() # Initialize Mininet if necessary self.built = False if topo and build: @@ -157,17 +154,15 @@ class Mininet( object ): # The specific items for host/switch/etc. should probably be # handled in the node classes rather than here!! - def addHost( self, name, mac=None, ip=None, host=None, **params ): + def addHost( self, name, host=None, **params ): """Add host. name: name of host to add - mac: default MAC address for intf 0 - ip: default IP address for intf 0 + host: custom host constructor (optional) + params: parameters for host returns: added host""" if not host: host = self.host - defaults = { 'defaultMAC': mac, 'defaultIP': ip } - defaults.update( params ) - h = host( name, **defaults) + h = host( name, **params) self.hosts.append( h ) self.nameToNode[ name ] = h return h @@ -175,19 +170,17 @@ class Mininet( object ): def addSwitch( self, name, switch=None, **params ): """Add switch. name: name of switch to add + switch: custom switch constructor (optional) returns: added switch - side effect: increments listenPort and dps ivars.""" + side effect: increments listenPort ivar .""" defaults = { 'listenPort': self.listenPort, 'inNamespace': self.inNamespace } + defaults.update( params ) if not switch: switch = self.switch - if switch != UserSwitch: - defaults[ 'dps' ] = self.dps - defaults.update( params ) sw = self.switch( name, **defaults ) if not self.inNamespace and self.listenPort: self.listenPort += 1 - self.dps += 1 self.switches.append( sw ) self.nameToNode[ name ] = sw return sw @@ -203,122 +196,81 @@ class Mininet( object ): self.nameToNode[ name ] = controller_new return controller_new - # Control network support: - # - # Create an explicit control network. Currently this is only - # used by the user datapath configuration. - # - # Notes: - # - # 1. If the controller and switches are in the same (e.g. root) - # namespace, they can just use the loopback connection. - # - # 2. If we can get unix domain sockets to work, we can use them - # instead of an explicit control network. - # - # 3. Instead of routing, we could bridge or use 'in-band' control. - # - # 4. Even if we dispense with this in general, it could still be - # useful for people who wish to simulate a separate control - # network (since real networks may need one!) - # - # 5. Basically nobody ever uses this method, so perhaps it should be moved - # out of this core class. - - def configureControlNetwork( self ): - "Configure control network." - self.configureRoutedControlNetwork() - - # We still need to figure out the right way to pass - # in the control network location. - - def configureRoutedControlNetwork( self, ip='192.168.123.1', - prefixLen=16 ): - """Configure a routed control network on controller and switches. - For use with the user datapath only right now.""" - controller = self.controllers[ 0 ] - info( controller.name + ' <->' ) - cip = ip - snum = ipParse( ip ) - for switch in self.switches: - info( ' ' + switch.name ) - sintf, cintf = createLink( switch, controller ) - snum += 1 - while snum & 0xff in [ 0, 255 ]: - snum += 1 - sip = ipStr( snum ) - controller.setIP( cintf, cip, prefixLen ) - switch.setIP( sintf, sip, prefixLen ) - controller.setHostRoute( sip, cintf ) - switch.setHostRoute( cip, sintf ) - info( '\n' ) - info( '*** Testing control network\n' ) - while not controller.intfIsUp( cintf ): - info( '*** Waiting for', cintf, 'to come up\n' ) - sleep( 1 ) - for switch in self.switches: - while not switch.intfIsUp( sintf ): - info( '*** Waiting for', sintf, 'to come up\n' ) - sleep( 1 ) - if self.ping( hosts=[ switch, controller ] ) != 0: - error( '*** Error: control network test failed\n' ) - exit( 1 ) - info( '\n' ) - def configHosts( self ): "Configure a set of hosts." - # params were: hosts, ips for host in self.hosts: - hintf = host.defaultIntf() - host.setIP( host.defaultIP, self.cparams.prefixLen, hintf ) - host.setDefaultRoute( hintf ) + host.configDefault( defaultRoute=host.defaultIntf ) # You're low priority, dude! - quietRun( 'renice +18 -p ' + repr( host.pid ) ) + # BL: do we want to do this here or not? + # May not make sense if we have CPU lmiting... + # quietRun( 'renice +18 -p ' + repr( host.pid ) ) info( host.name + ' ' ) info( '\n' ) - def buildFromTopo( self, topo ): + def buildFromTopo( self, topo=None ): """Build mininet from a topology object At the end of this function, everything should be connected and up.""" + if not topo: + topo = self.topo() + def addNode( prefix, addMethod, nodeId ): - "Add a host or a switch." + "Add a host or a switch from topo" name = prefix + topo.name( nodeId ) - # MAC and IP should probably be from nodeInfo... - mac = macColonHex( nodeId ) if self.setMacs else None - ip = topo.ip( nodeId ) ni = topo.nodeInfo( nodeId ) - node = addMethod( name, cls=ni.cls, mac=mac, ip=ip, **ni.params ) + # Default IP and MAC addresses + defaults = { 'ip': topo.ip( nodeId ) } + if self.autoSetMacs: + defaults[ 'mac'] = macColonHex( nodeId ) + defaults.update( ni.params ) + node = addMethod( name, cls=ni.cls, **defaults ) self.idToNode[ nodeId ] = node info( name + ' ' ) + def addLink( srcId, dstId, link=None ): + "Add a link from topo" + src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] + srcPort, dstPort = topo.port( srcId, dstId ) + ei = topo.edgeInfo( srcId, dstId ) + link = getattr( ei, 'cls', link ) + params = ei.params + if not link: + link = self.link + info( '(%s, %s) ' % ( src.name, dst.name ) ) + link( src, dst, srcPort, dstPort, **params ) + # Possibly we should clean up here and/or validate # the topo if self.cleanup: pass - info( '*** Adding controller\n' ) - self.addController( 'c0' ) info( '*** Creating network\n' ) + + if not self.controllers: + # Add a default controller + info( '*** Adding controller\n' ) + self.addController( 'c0' ) + info( '*** Adding hosts:\n' ) for hostId in sorted( topo.hosts() ): addNode( 'h', self.addHost, hostId ) + info( '\n*** Adding switches:\n' ) for switchId in sorted( topo.switches() ): - addNode( 's', self.addSwitch, switchId) + addNode( 's', self.addSwitch, switchId ) + info( '\n*** Adding links:\n' ) for srcId, dstId in sorted( topo.edges() ): - src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] - srcPort, dstPort = topo.port( srcId, dstId ) - ei = topo.edgeInfo( srcId, dstId ) - link, params = ei.cls, ei.params - if not link: - link = self.link - link( src, dst, srcPort, dstPort, **params ) - info( '(%s, %s) ' % ( src.name, dst.name ) ) + addLink( srcId, dstId ) + info( '\n' ) + + def configureControlNetwork( self ): + error( "configureControlNetwork: override in subclass, or use" + "MininetWithControlNet class" ) + def build( self ): "Build mininet." if self.topo: @@ -330,8 +282,6 @@ class Mininet( object ): self.configHosts() if self.xterms: self.startTerms() - if self.autoSetMacs: - self.setMacs() if self.autoStaticArp: self.staticArp() self.built = True @@ -346,17 +296,10 @@ class Mininet( object ): def stopXterms( self ): "Kill each xterm." - # Kill xterms for term in self.terms: os.kill( term.pid, signal.SIGKILL ) cleanUpScreens() - def setMacs( self ): - """Set MAC addrs to correspond to default MACs on hosts. - Assume that the host only has one interface.""" - for host in self.hosts: - host.setMAC( host.intfs[ 0 ], host.defaultMAC ) - def staticArp( self ): "Add all-pairs ARP entries to remove the need to handle broadcast." for src in self.hosts: @@ -384,18 +327,19 @@ class Mininet( object ): self.stopXterms() info( '*** Stopping %i hosts\n' % len( self.hosts ) ) for host in self.hosts: - info( '%s ' % host.name ) + info( host.name + ' ' ) host.terminate() info( '\n' ) info( '*** Stopping %i switches\n' % len( self.switches ) ) for switch in self.switches: - info( switch.name ) + info( switch.name + ' ' ) switch.stop() info( '\n' ) info( '*** Stopping %i controllers\n' % len( self.controllers ) ) for controller in self.controllers: + info( controller.name + ' ' ) controller.stop() - info( '*** Done\n' ) + info( '\n*** Done\n' ) def run( self, test, *args, **kwargs ): "Perform a complete start/test/stop cycle." @@ -429,6 +373,9 @@ class Mininet( object ): if not ready and timeoutms >= 0: yield None, None + # XXX These test methods should be moved out of this class. + # Probably we should create a tests.py for them + @staticmethod def _parsePing( pingOutput ): "Parse ping output and return packets sent, received." @@ -543,6 +490,8 @@ class Mininet( object ): output( '*** Results: %s\n' % result ) return result + # BL: I think this can be rewritten now that we have + # a real link class. def configLinkStatus( self, src, dst, status ): """Change status of src <-> dst links. src: node name @@ -573,6 +522,70 @@ class Mininet( object ): return result +class MininetWithControlNet( Mininet ): + + """Control network support: + + Create an explicit control network. Currently this is only + used/usable with the user datapath. + + Notes: + + 1. If the controller and switches are in the same (e.g. root) + namespace, they can just use the loopback connection. + + 2. If we can get unix domain sockets to work, we can use them + instead of an explicit control network. + + 3. Instead of routing, we could bridge or use 'in-band' control. + + 4. Even if we dispense with this in general, it could still be + useful for people who wish to simulate a separate control + network (since real networks may need one!) + + 5. Basically nobody ever used this code, so it has been moved + into its own class.""" + + def configureControlNetwork( self ): + "Configure control network." + self.configureRoutedControlNetwork() + + # We still need to figure out the right way to pass + # in the control network location. + + def configureRoutedControlNetwork( self, ip='192.168.123.1', + prefixLen=16 ): + """Configure a routed control network on controller and switches. + For use with the user datapath only right now.""" + controller = self.controllers[ 0 ] + info( controller.name + ' <->' ) + cip = ip + snum = ipParse( ip ) + for switch in self.switches: + info( ' ' + switch.name ) + sintf, cintf = createLink( switch, controller ) + snum += 1 + while snum & 0xff in [ 0, 255 ]: + snum += 1 + sip = ipStr( snum ) + controller.setIP( cintf, cip, prefixLen ) + switch.setIP( sintf, sip, prefixLen ) + controller.setHostRoute( sip, cintf ) + switch.setHostRoute( cip, sintf ) + info( '\n' ) + info( '*** Testing control network\n' ) + while not controller.intfIsUp( cintf ): + info( '*** Waiting for', cintf, 'to come up\n' ) + sleep( 1 ) + for switch in self.switches: + while not switch.intfIsUp( sintf ): + info( '*** Waiting for', sintf, 'to come up\n' ) + sleep( 1 ) + if self.ping( hosts=[ switch, controller ] ) != 0: + error( '*** Error: control network test failed\n' ) + exit( 1 ) + info( '\n' ) + # pylint thinks inited is unused # pylint: disable-msg=W0612 @@ -585,10 +598,6 @@ def init(): # Perhaps we should do so automatically! print "*** Mininet must run as root." exit( 1 ) - # If which produces no output, then mnexec is not in the path. - # May want to loosen this to handle mnexec in the current dir. - if not quietRun( 'which mnexec' ): - raise Exception( "Could not find mnexec - check $PATH" ) fixLimits() init.inited = True diff --git a/mininet/node.py b/mininet/node.py index cfeb02f..68a843e 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -48,7 +48,8 @@ import select from subprocess import Popen, PIPE, STDOUT from mininet.log import info, error, debug -from mininet.util import quietRun, errRun, moveIntf, isShellBuiltin +from mininet.util import quietRun, errRun, errFail, moveIntf, isShellBuiltin +from mininet.util import numCores from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN from mininet.link import Link @@ -58,21 +59,74 @@ class Node( object ): """A virtual network node is simply a shell in a network namespace. We communicate with it using pipes.""" + portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0 + + def __init__( self, name, inNamespace=True, **params ): + """name: name of node + inNamespace: in network namespace? + params: Node parameters (see config() for details)""" + + # Make sure class actually works + self.checkSetup() + + self.name = name + self.inNamespace = inNamespace + + # Stash configuration parameters for future reference + self.params = params + + self.intfs = {} # dict of port numbers to interfaces + self.ports = {} # dict of interfaces to port numbers + # replace with Port objects, eventually ? + self.nameToIntf = {} # dict of interface names to Intfs + + # Start command interpreter shell + self.shell = None + self.startShell() + + # File descriptor to node mapping support + # Class variables and methods + inToNode = {} # mapping of input fds to nodes outToNode = {} # mapping of output fds to nodes - portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0 + @classmethod + def fdToNode( cls, fd ): + """Return node corresponding to given file descriptor. + fd: file descriptor + returns: node""" + node = cls.outToNode.get( fd ) + return node or cls.inToNode.get( fd ) - def __init__( self, name, inNamespace=True, - defaultMAC=None, defaultIP=None, **kwargs ): - """name: name of node - inNamespace: in network namespace? - defaultMAC: default MAC address for intf 0 - defaultIP: default IP address for intf 0""" - self.name = name - self.inNamespace = inNamespace - self.defaultIP = defaultIP - self.defaultMAC = defaultMAC + # Automatic class setup support + + isSetup = False; + + @classmethod + def checkSetup( cls ): + "Make sure our class and superclasses are set up" + while cls and not getattr( cls, 'isSetup', True ): + cls.setup() + cls.isSetup = True + # Make pylint happy + cls = getattr( type( cls ), '__base__', None ) + + @classmethod + def setup( cls ): + "Make sure our class dependencies are available" + pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet') + + def cleanup( self ): + "Help python collect its garbage." + self.shell = None + + # Command support via shell process in namespace + + def startShell( self ): + "Start a shell process for running commands" + if self.shell: + error( "%s: shell is already running" ) + return opts = '-cdp' if self.inNamespace: opts += 'n' @@ -89,31 +143,12 @@ class Node( object ): # using select.poll() self.outToNode[ self.stdout.fileno() ] = self self.inToNode[ self.stdin.fileno() ] = self - self.intfs = {} # dict of port numbers to interfaces - self.ports = {} # dict of interfaces to port numbers - # replace with Port objects, eventually ? - self.nameToIntf = {} # dict of interface names to Intfs self.execed = False self.lastCmd = None self.lastPid = None self.readbuf = '' self.waiting = False - # Stash additional information as desired - self.args = kwargs - @classmethod - def fdToNode( cls, fd ): - """Return node corresponding to given file descriptor. - fd: file descriptor - returns: node""" - node = Node.outToNode.get( fd ) - return node or Node.inToNode.get( fd ) - - def cleanup( self ): - "Help python collect its garbage." - self.shell = None - - # Subshell I/O, commands and control def read( self, bytes=1024 ): """Buffered read from node, non-blocking. bytes: maximum number of bytes to return""" @@ -267,10 +302,10 @@ class Node( object ): self.intfs[ port ] = intf self.ports[ intf ] = port self.nameToIntf[ intf.name ] = intf - info( '\n' ) - info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) + debug( '\n' ) + debug( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) if self.inNamespace: - info( 'moving', intf, 'into namespace for', self.name, '\n' ) + debug( 'moving', intf, 'into namespace for', self.name, '\n' ) moveIntf( intf.name, self ) def defaultIntf( self ): @@ -326,13 +361,15 @@ class Node( object ): intf: string, interface name""" return self.cmd( 'route add -host ' + ip + ' dev ' + intf ) - def setDefaultRoute( self, intf ): + def setDefaultRoute( self, intf=None ): """Set the default route to go through intf. intf: string, interface name""" + if not intf: + intf = self.defaultIntf() self.cmd( 'ip route flush root 0/0' ) return self.cmd( 'route add default %s' % intf ) - # Convenience methods + # Convenience and configuration methods def setMAC( self, mac, intf=''): """Set the MAC address for an interface. @@ -361,6 +398,49 @@ class Node( object ): "Check if an interface is up." return self.intf( intf ).isUp() + # The reason why we configure things in this way is so + # That the parameters can be listed and documented in + # the config method. + # Dealing with subclasses and superclasses is slightly + # annoying, but at least the information is there! + + def setParam( self, results, method, **param ): + """Internal method: configure single parameter""" + name, value = param.items()[ 0 ] + f = getattr( self, method, None ) + if not value or not f: + return + if type( value ) is list: + result = f( *value ) + elif type( value ) is dict: + result = f( **value ) + else: + result = f( value ) + results[ name ] = result + + def config( self, mac=None, ip=None, ifconfig=None, + defaultRoute=None, **params): + """Configure Node according to (optional) parameters: + mac: MAC address for default interface + ip: IP address for default interface + ifconfig: arbitrary interface configuration + Subclasses should override this method and call + the parent class's config(**params)""" + # If we were overriding this method, we would call + # the superclass config method here as follows: + # r = Parent.config( **params ) + r = {} + self.setParam( r, 'setMAC', mac=mac ) + self.setParam( r, 'setIP', ip=ip ) + self.setParam( r, 'ifconfig', ifconfig=ifconfig ) + self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute ) + return r + + def configDefault( self, **moreParams ): + "Configure with default parameters" + self.params.update( moreParams ) + self.config( **self.params ) + # This is here for backward compatibility def linkTo( self, node, link=Link ): """(Deprecated) Link to another node @@ -382,9 +462,94 @@ class Node( object ): self.name, self.IP(), ','.join( self.intfNames() ), self.pid ) -class Host( Node ): - "A host is simply a Node." +class CPULimitedHost( Node ): + "CPU limited host" + + def __init__( self, *args, **kwargs ): + Node.__init__( self, *args, **kwargs ) + # Create a cgroup and move shell into it + cgroup = 'cpu,cpuacct:/' + self.name + errFail( 'cgcreate -g ' + cgroup ) + errFail( 'cgclassify -g %s %s' % ( cgroup, self.pid ) ) + self.sched = 'rt' + self.period_us = 10000 + self.rtset = False + + def cgroupSet( self, param, value, resource='cpu' ): + "Set a cgroup parameter and return its value" + cmd = 'cgset -r %s.%s=%s /%s' % ( + resource, param, value, self.name ) + return quietRun( cmd ) + + def cgroupGet( self, param, resource='cpu' ): + cmd = 'cgget -r %s.%s /%s' % ( + resource, param, self.name ) + return quietRun( cmd ).split()[ -1 ] + + def chrt( self, prio=20 ): + "Set RT scheduling priority" + quietRun( 'chrt -p %s %s' % ( prio, self.pid ) ) + result = quietRun( 'chrt -p %s' % self.pid ) + firstline = result.split( '\n' )[ 0 ] + lastword = firstline.split( ' ' )[ -1 ] + return lastword + + def setCPUFrac( self, f=-1 ): + "Set overall CPU fraction for this host" + if ( f < 0 or f is None): + # Reset to unlimited + f = -1 + # Set new period and quota + pstr, qstr = 'rt_period_us', 'rt_runtime_us' + quota = int( self.period_us * f * numCores() ) + self.cgroupSet( pstr, self.period_us ) + nquota = int ( self.cgroupGet( qstr ) ) + self.cgroupSet( qstr, quota ) + nperiod = int( self.cgroupGet( pstr ) ) + # Set RT priority + nchrt = self.chrt( prio=20 ) + # Check to make sure it worked + if 'SCHED_RR' not in nchrt: + error( '*** error: could not assign SCHED_RR to %s\n' % self.name ) + if nperiod != self.period_us: + error( '*** error: period is %s rather than %s\n' % ( + nperiod, self.period_us ) ) + if nquota != quota: + error( '*** error: quota is %s rather than %s\n' % ( + nquota, quota ) ) + + def config( self, cpu=None, **params ): + """cpu: desired overall system CPU fraction + params: parameters for Node.config()""" + r = Node.config( self, **params ) + self.setParam( r, 'setCPUFrac', cpu=cpu ) + return r + +Host = CPULimitedHost + + +# Some important things to note: +# +# The "IP" address which we assign to the switch is not +# an "IP address for the switch" in the sense of IP routing. +# Rather, it is the IP address for a control interface if +# (and only if) you happen to be running the switch in a +# namespace, which is something we currently don't support +# for OVS! +# +# In general, you NEVER want to attempt to use Linux's +# network stack (i.e. ifconfig) to "assign" an IP address or +# MAC address to a switch data port. Instead, you "assign" +# the IP and MAC addresses in the controller by specifying +# packets that you want to receive or send. The "MAC" address +# reported by ifconfig for a switch data port is essentially +# meaningless. +# +# So, I'm tyring changing the API to make it +# impossible to try this, since it will not work, since nobody +# ever makes separate control networks in Mininet, and indeed +# we don't even support running OVS in a namespace. class Switch( Node ): """A Switch is a Node that is running (or has execed?) @@ -392,19 +557,31 @@ class Switch( Node ): portBase = SWITCH_PORT_BASE # 0 for OF < 1.0, 1 for OF >= 1.0 - def __init__( self, name, opts='', listenPort=None, **kwargs): - Node.__init__( self, name, **kwargs ) + def __init__( self, name, dpid=None, opts='', listenPort=None, **params): + """dpid: dpid for switch (or None for default) + 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.opts = opts self.listenPort = listenPort if self.listenPort: self.opts += ' --listen=ptcp:%i ' % self.listenPort + self.controlIntf = None + + def defaultDpid( self ): + "Derive dpid from switch name, s1 -> 1" + dpid = int( re.findall( '\d+', self.name )[ 0 ] ) + dpid = hex( dpid )[ 2: ] + dpid = '0' * ( 12 - len( dpid ) ) + dpid + return dpid def defaultIntf( self ): - "Return interface for HIGHEST port" - ports = self.intfs.keys() - if ports: - intf = self.intfs[ max( ports ) ] - return intf + "Return control interface, if any" + if not self.inNamespace: + error( "error: tried to access control interface of " + " switch %s in root namespace" % self.name ) + return self.controlIntf def sendCmd( self, *cmd, **kwargs ): """Send command to Node. @@ -440,15 +617,13 @@ class UserSwitch( Switch ): ofdlog = '/tmp/' + self.name + '-ofd.log' ofplog = '/tmp/' + self.name + '-ofp.log' self.cmd( 'ifconfig lo up' ) - mac_str = '' - if self.defaultMAC: - # ofdatapath expects a string of hex digits with no colons. - mac_str = ' -d ' + ''.join( self.defaultMAC.split( ':' ) ) - intfs = sorted( self.intfs.values() ) + ports = sorted( self.ports.values() ) + intfs = [ str( self.intfs[ p ] ) for p in ports ] if self.inNamespace: intfs = intfs[ :-1 ] self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + - ' punix:/tmp/' + self.name + mac_str + ' --no-slicing ' + + ' punix:/tmp/' + self.name + ' -d ' + self.dpid + + ' --no-slicing ' + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) self.cmd( 'ofprotocol unix:/tmp/' + self.name + ' tcp:%s:%d' % ( controller.IP(), controller.port ) + @@ -461,61 +636,6 @@ class UserSwitch( Switch ): self.cmd( 'kill %ofprotocol' ) self.deleteIntfs() -class KernelSwitch( Switch ): - """Kernel-space switch. - Currently only works in root namespace.""" - - def __init__( self, name, dp=None, **kwargs ): - """Init. - name: name for switch - dp: netlink id (0, 1, 2, ...) - defaultMAC: default MAC as string; random value if None""" - Switch.__init__( self, name, **kwargs ) - self.dp = 'nl:%i' % dp - self.intf = 'of%i' % dp - if self.inNamespace: - error( "KernelSwitch currently only works" - " in the root namespace." ) - exit( 1 ) - - @staticmethod - def setup(): - "Ensure any dependencies are loaded; if not, try to load them." - pathCheck( 'ofprotocol', - moduleName='the OpenFlow reference kernel switch' - ' (openflow.org) (NOTE: not available in OpenFlow 1.0!)' ) - moduleDeps( subtract=OVS_KMOD, add=OF_KMOD ) - - def start( self, controllers ): - "Start up reference kernel datapath." - ofplog = '/tmp/' + self.name + '-ofp.log' - quietRun( 'ifconfig lo up' ) - # Delete local datapath if it exists; - # then create a new one monitoring the given interfaces - quietRun( 'dpctl deldp ' + self.dp ) - self.cmd( 'dpctl adddp ' + self.dp ) - if self.defaultMAC: - self.cmd( 'ifconfig', self.intf, 'hw', 'ether', self.defaultMAC ) - ports = sorted( self.ports.values() ) - if len( ports ) != ports[ -1 ] + 1 - self.portBase: - raise Exception( 'only contiguous, zero-indexed port ranges' - 'supported: %s' % ports ) - intfs = [ self.intfs[ port ] for port in ports ] - self.cmd( 'dpctl', 'addif', self.dp, ' '.join( intfs ) ) - # Run protocol daemon - controller = controllers[ 0 ] - self.cmd( 'ofprotocol ' + self.dp + - ' tcp:%s:%d' % ( controller.IP(), controller.port ) + - ' --fail=closed ' + self.opts + - ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) - self.execed = False - - def stop( self ): - "Terminate kernel datapath." - quietRun( 'dpctl deldp ' + self.dp ) - self.cmd( 'kill %ofprotocol' ) - self.deleteIntfs() - class OVSLegacyKernelSwitch( Switch ): """Open VSwitch legacy kernel-space switch using ovs-openflowd. @@ -549,12 +669,6 @@ class OVSLegacyKernelSwitch( Switch ): # then create a new one monitoring the given interfaces quietRun( 'ovs-dpctl del-dp ' + self.dp ) self.cmd( 'ovs-dpctl add-dp ' + self.dp ) - mac_str = '' - if self.defaultMAC: - # ovs-openflowd expects a string of exactly 16 hex digits with no - # colons. - mac_str = ' --datapath-id=0000' + \ - ''.join( self.defaultMAC.split( ':' ) ) + ' ' ports = sorted( self.ports.values() ) if len( ports ) != ports[ -1 ] + 1 - self.portBase: raise Exception( 'only contiguous, one-indexed port ranges ' @@ -565,7 +679,8 @@ class OVSLegacyKernelSwitch( Switch ): controller = controllers[ 0 ] self.cmd( 'ovs-openflowd ' + self.dp + ' tcp:%s:%d' % ( controller.IP(), controller.port ) + - ' --fail=secure ' + self.opts + mac_str + + ' --fail=secure ' + self.opts + + ' --datapath-id=' + self.dpid + ' 1>' + ofplog + ' 2>' + ofplog + '&' ) self.execed = False @@ -579,13 +694,17 @@ class OVSLegacyKernelSwitch( Switch ): class OVSSwitch( Switch ): "Open vSwitch switch. Depends on ovs-vsctl." - def __init__( self, name, dp=None, **kwargs ): + def __init__( self, name, **params ): """Init. name: name for switch defaultMAC: default MAC as unsigned int; random value if None""" - Switch.__init__( self, name, **kwargs ) + Switch.__init__( self, name, **params ) + # self.dp is the text name for the datapath that + # we use for ovs-vsctl. This is different from the + # dpid, which is a 64-bit numerical value used by + # the openflow protocol. self.dp = name - + @staticmethod def setup(): "Make sure Open vSwitch is installed and working" @@ -609,7 +728,6 @@ class OVSSwitch( Switch ): self.cmd( 'ovs-vsctl del-br ', self.dp ) self.cmd( 'ovs-vsctl add-br', self.dp ) self.cmd( 'ovs-vsctl set-fail-mode', self.dp, 'secure' ) - # Add ports ports = sorted( self.ports.values() ) intfs = [ self.intfs[ port ] for port in ports ] # XXX: Ugly check - we should probably fix this! @@ -629,19 +747,21 @@ class OVSSwitch( Switch ): OVSKernelSwitch = OVSSwitch + class Controller( Node ): """A Controller is a Node that is running (or has execed?) an OpenFlow controller.""" def __init__( self, name, inNamespace=False, command='controller', - cargs='-v ptcp:%d', cdir=None, defaultIP="127.0.0.1", - port=6633 ): + cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1", + port=6633, **params ): self.command = command self.cargs = cargs self.cdir = cdir + self.ip = ip self.port = port Node.__init__( self, name, inNamespace=inNamespace, - defaultIP=defaultIP ) + ip=ip, **params ) def start( self ): """Start on controller. @@ -664,9 +784,13 @@ class Controller( Node ): if self.intfs: ip = Node.IP( self, intf ) else: - ip = self.defaultIP + ip = self.ip return ip + +# BL: This really seems to be poorly specified, +# so it's going to go away! + class ControllerParams( object ): "Container for controller IP parameters." From 4ac1148e9fd2d961c006dc0ceca6f65afb2aae4d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 6 Mar 2012 23:52:26 -0800 Subject: [PATCH 039/250] Example/test of link and CPU bandwidth limits. --- examples/limit.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 examples/limit.py diff --git a/examples/limit.py b/examples/limit.py new file mode 100755 index 0000000..58e2ff7 --- /dev/null +++ b/examples/limit.py @@ -0,0 +1,52 @@ +#!/usr/bin/python + +""" +limit.py: example of using link and CPU limits +""" + +from mininet.net import Mininet +from mininet.link import TCIntf, Link +from mininet.node import CPULimitedHost +from mininet.topolib import TreeTopo +from mininet.util import custom, quietRun +from mininet.log import setLogLevel +from time import sleep + +def testLinkLimit( net ): + print '*** Testing network bandwidth limit' + net.iperf() + +def testCpuLimit( net ): + print '*** Testing CPU bandwidth limit' + h1, h2 = net.hosts + h1.cmd( 'while true; do a=1; done &' ) + h2.cmd( 'while true; do a=1; done &' ) + pid1 = h1.cmd( 'echo $!' ).strip() + pid2 = h2.cmd( 'echo $!' ).strip() + cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 ) + for i in range( 0, 5): + sleep( 1 ) + print quietRun( cmd ) + h1.cmd( 'kill %1') + h2.cmd( 'kill %1') + +def limit(): + "Example/test of link and CPU bandwidth limits" + # 1 Mbps interfaces limited using tc + intf1Mbps = custom( TCIntf, bw=1 ) + # Links consisting of two 10 Mbps interfaces + link1Mbps = custom( Link, intf=intf1Mbps, cls2=TCIntf ) + # Hosts with 30% of system bandwidth + host30pct = custom( CPULimitedHost, cpu=.3 ) + myTopo = TreeTopo( depth=1, fanout=2 ) + net = Mininet( topo=myTopo, + link=link1Mbps, + host=host30pct ) + net.start() + testLinkLimit( net ) + testCpuLimit( net ) + net.stop() + +if __name__ == '__main__': + setLogLevel( 'info' ) + limit() From b1f90976a37800296dbbfedeaf9544ec688c9c4a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 7 Mar 2012 00:02:30 -0800 Subject: [PATCH 040/250] Remove default classes since Mininet() really handles them. --- mininet/topo.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index 2c8635b..404833a 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -18,11 +18,6 @@ setup for testing, and can even be emulated with the Mininet package. from networkx import Graph from mininet.node import SWITCH_PORT_BASE, Host, OVSSwitch -# BL: it's hard to figure out how to do this right yet remain flexible -# These classes will be used as the defaults if no class is passed -# into either Topo() or Node() -TopoDefaultNode = Host -TopoDefaultSwitch = OVSSwitch class NodeID(object): '''Topo node identifier.''' @@ -81,11 +76,13 @@ class Node( object ): self.power_on = power_on self.fault = fault self.is_switch = is_switch - # Above should be deleted and replaced by the following - # BL: is_switch is a bit annoying if we can just specify - # the node class instead!! - self.cls = cls if cls else ( TopoDefaultSwitch if is_switch else TopoDefaultNode ) - self.params = params if params else {} + # BL: Above should mostly be deleted and replaced by the following + # is_switch is a bit annoying if we are already specifying + # a switch class!! Except that if cls is not specified, + # then Mininet() knows whether to create a switch or a host + # node and can call its own constructors... + self.cls = cls + self.params = params class Edge(object): From edf46e9570e9cb8cd480ac53e6a4fb036e02d351 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 7 Mar 2012 23:38:08 -0800 Subject: [PATCH 041/250] Slightly cleaned up setParam to match node.py. --- mininet/link.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index d3193ca..a833298 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -94,27 +94,30 @@ class BasicIntf( object ): "Return whether interface is up" return "UP" in self.ifconfig() - # The reason why we configure things in this way is so # That the parameters can be listed and documented in # the config method. # Dealing with subclasses and superclasses is slightly # annoying, but at least the information is there! - def setParam( self, result, method, **param ): - """Internal method: configure single parameter - result: dict of results to update - method: config method - param: foo=bar (ignore if bar=None)""" + def setParam( self, results, method, **param ): + """Internal method: configure a *single* parameter + results: dict of results to update + method: config method name + param: arg=value (ignore if value=None) + value may also be list or dict""" name, value = param.items()[ 0 ] - if value is None: + f = getattr( self, method, None ) + if not f or value is None: return if type( value ) is list: - result[ name ] = getattr( self, method )( *value ) + result = f( *value ) elif type( value ) is dict: - result[ name ] = getattr( self, method )( **value ) + result = f( **value ) else: - result[ name ] = getattr( self, method )( value ) + result = f( value ) + results[ name ] = result + return result def config( self, mac=None, ip=None, ifconfig=None, defaultRoute=None, **params): From cbe20c75871f2891b48e61e1a0d80153ba71cfb4 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 00:05:25 -0800 Subject: [PATCH 042/250] Remove unused imports. --- mininet/topo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/topo.py b/mininet/topo.py index 404833a..55eab60 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,7 +16,7 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph -from mininet.node import SWITCH_PORT_BASE, Host, OVSSwitch +from mininet.node import SWITCH_PORT_BASE class NodeID(object): From 216a4b7c9d7f9ffa0e95dfae09586a9f0a73b17f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 00:05:45 -0800 Subject: [PATCH 043/250] Support for CFS bandwidth limiting. Also trying to fix NOX cmdline opt, but broken at the moment. --- bin/mn | 136 +++++++++++++++++++++++++--------------------- examples/limit.py | 43 +++++++-------- mininet/net.py | 12 +++- mininet/node.py | 93 +++++++++++++++++++++++-------- 4 files changed, 174 insertions(+), 110 deletions(-) diff --git a/bin/mn b/bin/mn index b232389..7eab6c9 100755 --- a/bin/mn +++ b/bin/mn @@ -20,11 +20,27 @@ from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info from mininet.net import Mininet, init -from mininet.node import Host, Controller, ControllerParams, NOX +from mininet.node import Host, CPULimitedHost, Controller, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch +from mininet.link import Intf, TCIntf from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo -from mininet.util import makeNumeric +from mininet.util import makeNumeric, custom + +def customNode( constructors, argStr ): + "Return custom Node constructor based on argStr" + cname, noargs, kwargs = splitArgs( argStr ) + constructor = constructors.get( cname, None ) + if noargs: + raise Exception( "please specify keyword arguments for " + cname ) + if not constructor: + raise Exception( "error: %s is unknown - please specify one of %s" % + ( cname, constructors.keys() ) ) + def custom( *args, **params ): + params.update( kwargs ) + print 'CONSTRUCTOR', constructor, 'ARGS', args, 'PARAMS', params + return constructor( *args, **params ) + return custom # built in topologies, created only when run TOPODEF = 'minimal' @@ -38,17 +54,22 @@ SWITCHDEF = 'ovsk' SWITCHES = { 'user': UserSwitch, 'ovsk': OVSKernelSwitch } -HOSTDEF = 'process' -HOSTS = { 'process': Host } +HOSTDEF = 'proc' +HOSTS = { 'proc': Host, + 'rt': custom( CPULimitedHost, sched='rt' ), + 'cfs': custom( CPULimitedHost, sched='cfs' ) } CONTROLLERDEF = 'ref' -# a and b are the name and inNamespace params. CONTROLLERS = { 'ref': Controller, - 'nox_dump': lambda name: NOX( name, 'packetdump' ), - 'nox_pysw': lambda name: NOX( name, 'pyswitch' ), - 'remote': lambda name: None, + 'nox': NOX, + 'remote': RemoteController, 'none': lambda name: None } +INTFDEF = 'default' +INTFS = { 'default': Intf, + 'tc': TCIntf } + + # optional tests to run TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', 'none' ] @@ -56,24 +77,31 @@ TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair', 'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' } -def buildTopo( topo ): - "Create topology from string with format (object, arg1, arg2,...)." - topo_split = topo.split( ',' ) - topo_name = topo_split[ 0 ] - topo_params = topo_split[ 1: ] - # Convert int and float args; removes the need for every topology to - # be flexible with input arg formats. - topo_seq_params = [ s for s in topo_params if '=' not in s ] - topo_seq_params = [ makeNumeric( s ) for s in topo_seq_params ] - topo_kw_params = {} - for s in [ p for p in topo_params if '=' in p ]: +def splitArgs( argstr ): + """Split argument string into usable python arguments + argstr: argument string with format fn,arg2,kw1=arg3... + returns: fn, args, kwargs""" + split = argstr.split( ',' ) + fn = split[ 0 ] + params = split[ 1: ] + # Convert int and float args; removes the need for function + # to be flexible with input arg formats. + args = [ s for s in params if '=' not in s ] + args = map( makeNumeric, args ) + kwargs = {} + for s in [ p for p in params if '=' in p ]: key, val = s.split( '=' ) - topo_kw_params[ key ] = makeNumeric( val ) + kwargs[ key ] = makeNumeric( val ) + return fn, args, kwargs - if topo_name not in TOPOS.keys(): - raise Exception( 'Invalid topo_name %s' % topo_name ) - return TOPOS[ topo_name ]( *topo_seq_params, **topo_kw_params ) + +def buildTopo( topoStr ): + "Create topology from string with format (object, arg1, arg2,...)." + topo, args, kwargs = splitArgs( topoStr ) + if topo not in TOPOS: + raise Exception( 'Invalid topo name %s' % topo ) + return TOPOS[ topo ]( *args, **kwargs ) def addDictOption( opts, choicesDict, default, name, helpStr=None ): @@ -87,10 +115,9 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ): raise Exception( 'Invalid default %s for choices dict: %s' % ( default, name ) ) if not helpStr: - helpStr = '[' + ' '.join( choicesDict.keys() ) + ']' + helpStr = '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]' opts.add_option( '--' + name, - type='choice', - choices=choicesDict.keys(), + type='string', default = default, help = helpStr ) @@ -135,7 +162,6 @@ class MininetRunner( object ): """Parse command-line args and return options object. returns: opts parse options dict""" if '--custom' in sys.argv: - print "custom in sys.argv" index = sys.argv.index( '--custom' ) if len( sys.argv ) > index + 1: custom = sys.argv[ index + 1 ] @@ -147,46 +173,41 @@ class MininetRunner( object ): addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' ) addDictOption( opts, HOSTS, HOSTDEF, 'host' ) addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' ) + addDictOption( opts, INTFS, INTFDEF, 'intf' ) + addDictOption( opts, TOPOS, TOPODEF, 'topo' ) - opts.add_option( '--topo', type='string', default=TOPODEF, - help='[' + ' '.join( TOPOS.keys() ) + '],arg1,arg2,' - '...argN') opts.add_option( '--clean', '-c', action='store_true', default=False, help='clean and exit' ) opts.add_option( '--custom', type='string', default=None, help='read custom topo and node params from .py file' ) opts.add_option( '--test', type='choice', choices=TESTS, default=TESTS[ 0 ], - help='[' + ' '.join( TESTS ) + ']' ) + help='|'.join( TESTS ) ) opts.add_option( '--xterms', '-x', action='store_true', default=False, help='spawn xterms for each node' ) opts.add_option( '--mac', action='store_true', - default=False, help='set MACs equal to DPIDs' ) + default=False, help='automatically set host MACs' ) opts.add_option( '--arp', action='store_true', default=False, help='set all-pairs ARP entries' ) opts.add_option( '--verbosity', '-v', type='choice', choices=LEVELS.keys(), default = 'info', - help = '[' + ' '.join( LEVELS.keys() ) + ']' ) + help = '|'.join( LEVELS.keys() ) ) opts.add_option( '--ip', type='string', default='127.0.0.1', - help='[ip address as a dotted decimal string for a' - 'remote controller]' ) - opts.add_option( '--port', type='int', default=6633, - help='[port integer for a listening remote' - ' controller]' ) + help='ip address as a dotted decimal string for a' + 'remote controller' ) opts.add_option( '--innamespace', action='store_true', default=False, help='sw and ctrl in namespace?' ) opts.add_option( '--listenport', type='int', default=6634, - help='[base port for passive switch listening' - ' controller]' ) + help='base port for passive switch listening' ) opts.add_option( '--nolistenport', action='store_true', default=False, help="don't use passive listening port") opts.add_option( '--pre', type='string', default=None, - help='[CLI script to run before tests]' ) + help='CLI script to run before tests' ) opts.add_option( '--post', type='string', default=None, - help='[CLI script to run after tests]' ) + help='CLI script to run after tests' ) opts.add_option( '--prefixlen', type='int', default=8, - help='[prefix length (e.g. /8) for automatic ' - 'network configuration]' ) + help='prefix length (e.g. /8) for automatic ' + 'network configuration' ) self.options, self.args = opts.parse_args() @@ -214,23 +235,14 @@ class MininetRunner( object ): start = time.time() topo = buildTopo( self.options.topo ) - switch = SWITCHES[ self.options.switch ] - host = HOSTS[ self.options.host ] - controller = CONTROLLERS[ self.options.controller ] - if self.options.controller == 'remote': - controller = lambda a: RemoteController( a, - defaultIP=self.options.ip, - port=self.options.port ) + switch = customNode( SWITCHES, self.options.switch ) + host = customNode( HOSTS, self.options.host ) + controller = customNode( CONTROLLERS, self.options.controller ) + intf = customNode( INTFS, self.options.intf ) if self.validate: self.validate( self.options ) - # We should clarify what this is actually for... - # It seems like it should be default values for the - # *data* network, so it may be misnamed. - controllerParams = ControllerParams( '10.0.0.0', - self.options.prefixlen) - inNamespace = self.options.innamespace xterms = self.options.xterms mac = self.options.mac @@ -238,10 +250,12 @@ class MininetRunner( object ): listenPort = None if not self.options.nolistenport: listenPort = self.options.listenport - mn = Mininet( topo, switch, host, controller, controllerParams, - inNamespace=inNamespace, - xterms=xterms, autoSetMacs=mac, - autoStaticArp=arp, listenPort=listenPort ) + mn = Mininet( topo=topo, + switch=switch, host=host, controller=controller, + intf=intf, + inNamespace=inNamespace, + xterms=xterms, autoSetMacs=mac, + autoStaticArp=arp, listenPort=listenPort ) if self.options.pre: CLI( mn, script=self.options.pre ) diff --git a/examples/limit.py b/examples/limit.py index 58e2ff7..801159a 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -5,19 +5,20 @@ limit.py: example of using link and CPU limits """ from mininet.net import Mininet -from mininet.link import TCIntf, Link +from mininet.link import TCIntf from mininet.node import CPULimitedHost from mininet.topolib import TreeTopo from mininet.util import custom, quietRun from mininet.log import setLogLevel from time import sleep -def testLinkLimit( net ): - print '*** Testing network bandwidth limit' - net.iperf() +def testLinkLimit( net, bw ): + print '*** Testing network %.2f Mbps bandwidth limit' % bw + net.iperf( ) -def testCpuLimit( net ): - print '*** Testing CPU bandwidth limit' +def testCpuLimit( net, cpu ): + pct = cpu * 100 + print '*** Testing CPU %.0f%% bandwidth limit' % pct h1, h2 = net.hosts h1.cmd( 'while true; do a=1; done &' ) h2.cmd( 'while true; do a=1; done &' ) @@ -26,26 +27,24 @@ def testCpuLimit( net ): cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 ) for i in range( 0, 5): sleep( 1 ) - print quietRun( cmd ) + print quietRun( cmd ).strip() h1.cmd( 'kill %1') h2.cmd( 'kill %1') -def limit(): - "Example/test of link and CPU bandwidth limits" - # 1 Mbps interfaces limited using tc - intf1Mbps = custom( TCIntf, bw=1 ) - # Links consisting of two 10 Mbps interfaces - link1Mbps = custom( Link, intf=intf1Mbps, cls2=TCIntf ) - # Hosts with 30% of system bandwidth - host30pct = custom( CPULimitedHost, cpu=.3 ) +def limit( bw=1, cpu=.3 ): + """Example/test of link and CPU bandwidth limits + bw: interface bandwidth limit in Mbps + cpu: cpu limit as fraction of overall CPU time""" + intf = custom( TCIntf, bw=1 ) myTopo = TreeTopo( depth=1, fanout=2 ) - net = Mininet( topo=myTopo, - link=link1Mbps, - host=host30pct ) - net.start() - testLinkLimit( net ) - testCpuLimit( net ) - net.stop() + for sched in 'rt', 'cfs': + print '*** Testing with', sched, 'bandwidth limiting' + host = custom( CPULimitedHost, sched=sched, cpu=cpu ) + net = Mininet( topo=myTopo, intf=intf, host=host ) + net.start() + testLinkLimit( net, bw=bw ) + testCpuLimit( net, cpu=cpu ) + net.stop() if __name__ == '__main__': setLogLevel( 'info' ) diff --git a/mininet/net.py b/mininet/net.py index 18d0363..953a5bc 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -104,7 +104,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, + controller=Controller, link=Link, intf=None, build=True, xterms=False, cleanup=False, inNamespace=False, autoSetMacs=False, autoStaticArp=False, listenPort=None ): @@ -114,6 +114,7 @@ class Mininet( object ): host: default Host class/constructor controller: default Controller class/constructor link: default Link class/constructor + intf: default Intf class/constructor ipBase: base IP address for hosts, build: build now from topo? xterms: if build now, spawn xterms? @@ -123,11 +124,12 @@ class Mininet( object ): autoStaticArp: set all-pairs static MAC addrs? listenPort: base listening port to open; will be incremented for each additional switch in the net if inNamespace=False""" + self.topo = topo self.switch = switch self.host = host self.controller = controller self.link = link - self.topo = topo + self.intf = intf self.inNamespace = inNamespace self.xterms = xterms self.cleanup = cleanup @@ -199,12 +201,12 @@ class Mininet( object ): def configHosts( self ): "Configure a set of hosts." for host in self.hosts: + info( host.name + ' ' ) host.configDefault( defaultRoute=host.defaultIntf ) # You're low priority, dude! # BL: do we want to do this here or not? # May not make sense if we have CPU lmiting... # quietRun( 'renice +18 -p ' + repr( host.pid ) ) - info( host.name + ' ' ) info( '\n' ) def buildFromTopo( self, topo=None ): @@ -235,6 +237,8 @@ class Mininet( object ): ei = topo.edgeInfo( srcId, dstId ) link = getattr( ei, 'cls', link ) params = ei.params + if self.intf and not 'intf' in params: + params[ 'intf' ] = self.intf if not link: link = self.link info( '(%s, %s) ' % ( src.name, dst.name ) ) @@ -447,6 +451,8 @@ class Mininet( object ): error( 'could not parse iperf output: ' + iperfOutput ) return '' + # XXX This should be cleaned up + def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ): """Run iperf between two hosts. hosts: list of hosts; if None, uses opposite hosts diff --git a/mininet/node.py b/mininet/node.py index 68a843e..f78ebe9 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -127,9 +127,12 @@ class Node( object ): if self.shell: error( "%s: shell is already running" ) return + # mnexec: (c)lose descriptors, (d)etach from tty, + # (p)rint pid, and run in (n)amespace opts = '-cdp' if self.inNamespace: opts += 'n' + # bash -m: enable job control cmd = [ 'mnexec', opts, 'bash', '-m' ] self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True ) @@ -405,10 +408,14 @@ class Node( object ): # annoying, but at least the information is there! def setParam( self, results, method, **param ): - """Internal method: configure single parameter""" + """Internal method: configure a *single* parameter + results: dict of results to update + method: config method name + param: arg=value (ignore if value=None) + value may also be list or dict""" name, value = param.items()[ 0 ] f = getattr( self, method, None ) - if not value or not f: + if not f or value is None: return if type( value ) is list: result = f( *value ) @@ -417,6 +424,7 @@ class Node( object ): else: result = f( value ) results[ name ] = result + return result def config( self, mac=None, ip=None, ifconfig=None, defaultRoute=None, **params): @@ -462,7 +470,12 @@ class Node( object ): self.name, self.IP(), ','.join( self.intfNames() ), self.pid ) -class CPULimitedHost( Node ): +class Host( Node ): + "A host is simply a Node" + pass + + +class CPULimitedHost( Host ): "CPU limited host" @@ -472,9 +485,8 @@ class CPULimitedHost( Node ): cgroup = 'cpu,cpuacct:/' + self.name errFail( 'cgcreate -g ' + cgroup ) errFail( 'cgclassify -g %s %s' % ( cgroup, self.pid ) ) - self.sched = 'rt' - self.period_us = 10000 - self.rtset = False + self.period_us = kwargs.get( 'period_us', 10000 ) + self.sched = kwargs.get( 'sched', 'rt' ) def cgroupSet( self, param, value, resource='cpu' ): "Set a cgroup parameter and return its value" @@ -495,40 +507,73 @@ class CPULimitedHost( Node ): lastword = firstline.split( ' ' )[ -1 ] return lastword - def setCPUFrac( self, f=-1 ): - "Set overall CPU fraction for this host" - if ( f < 0 or f is None): + # BL comment: + # This may not be the right API, + # since it doesn't specify CPU bandwidth in "absolute" + # units the way link bandwidth is specified. + # We should use MIPS or SPECINT or something instead. + # Alternatively, we should change from system fraction + # to CPU seconds per second, essentially assuming that + # all CPUs are the same. + + def setCPUFrac( self, f=-1, sched=None): + """Set overall CPU fraction for this host + f: CPU bandwidth limit (fraction) + sched: 'rt' or 'cfs' + Note 'cfs' requires CONFIG_CFS_BANDWIDTH""" + if not f: + return + if not sched: + sched = self.sched + period = self.period_us + if sched == 'rt': + pstr, qstr = 'rt_period_us', 'rt_runtime_us' + # RT uses system time for period and quota + quota = int( period * f * numCores() ) + elif sched == 'cfs': + pstr, qstr = 'cfs_period_us', 'cfs_quota_us' + # CFS uses wall clock time for period and CPU time for quota. + quota = int( self.period_us * f * numCores() ) + if f > 0 and quota < 1000: + info( '*** setCPUFrac: quota too small - adjusting period\n' ) + quota = 1000 + period = int( quota / f / numCores() ) + else: + return + if quota < 0: # Reset to unlimited - f = -1 - # Set new period and quota - pstr, qstr = 'rt_period_us', 'rt_runtime_us' - quota = int( self.period_us * f * numCores() ) - self.cgroupSet( pstr, self.period_us ) + quota = -1 + # Set cgroup's period and quota + self.cgroupSet( pstr, period ) nquota = int ( self.cgroupGet( qstr ) ) self.cgroupSet( qstr, quota ) nperiod = int( self.cgroupGet( pstr ) ) - # Set RT priority - nchrt = self.chrt( prio=20 ) - # Check to make sure it worked - if 'SCHED_RR' not in nchrt: - error( '*** error: could not assign SCHED_RR to %s\n' % self.name ) + # Make sure it worked if nperiod != self.period_us: error( '*** error: period is %s rather than %s\n' % ( nperiod, self.period_us ) ) if nquota != quota: error( '*** error: quota is %s rather than %s\n' % ( nquota, quota ) ) + if sched == 'rt': + # Set RT priority if necessary + nchrt = self.chrt( prio=20 ) + # Nake sure it worked + if sched == 'SCHED_RR' not in nchrt: + error( '*** error: could not assign SCHED_RR to %s\n' % self.name ) + info( '( period', nperiod, 'quota', nquota, nchrt, ') ' ) + else: + info( '( period', nperiod, 'quota', nquota, ') ' ) - def config( self, cpu=None, **params ): + def config( self, cpu=None, sched=None, **params ): """cpu: desired overall system CPU fraction params: parameters for Node.config()""" r = Node.config( self, **params ) + # Was considering cpu={'cpu': cpu , 'sched': sched}, but + # that seems redundant self.setParam( r, 'setCPUFrac', cpu=cpu ) return r -Host = CPULimitedHost - - # Some important things to note: # # The "IP" address which we assign to the switch is not @@ -805,7 +850,7 @@ class ControllerParams( object ): class NOX( Controller ): "Controller to run a NOX application." - def __init__( self, name, noxArgs=None, **kwargs ): + def __init__( self, name, noxArgs=[], **kwargs ): """Init. name: name to give controller noxArgs: list of args, or single arg, to pass to NOX""" From bf5becc7d52e9eaf440fafd82580bafdb87d35db Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 13:38:46 -0800 Subject: [PATCH 044/250] Get rid of SWITCH_PORT_BASE since it's 1 for OF >= 1.0. --- mininet/topo.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index 55eab60..2cbfe7d 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,8 +16,6 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph -from mininet.node import SWITCH_PORT_BASE - class NodeID(object): '''Topo node identifier.''' @@ -156,8 +154,8 @@ class Topo(object): @param src source switch DPID @param dst destination switch DPID ''' - src_base = SWITCH_PORT_BASE if self.is_switch(src) else 0 - dst_base = SWITCH_PORT_BASE if self.is_switch(dst) else 0 + src_base = 1 if self.is_switch(src) else 0 + dst_base = 1 if self.is_switch(dst) else 0 if src not in self.ports: self.ports[src] = {} if dst not in self.ports[src]: From 8a622c3a9a382d3d3f7da0a9565222a4abd42d38 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 13:39:28 -0800 Subject: [PATCH 045/250] Reorganize CPULimitedHost and add cgroup cleanup. --- mininet/node.py | 85 ++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index f78ebe9..f815fd6 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -53,8 +53,6 @@ from mininet.util import numCores from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN from mininet.link import Link -SWITCH_PORT_BASE = 1 # For OF > 0.9, switch ports start at 1 rather than zero - class Node( object ): """A virtual network node is simply a shell in a network namespace. We communicate with it using pipes.""" @@ -482,17 +480,28 @@ class CPULimitedHost( Host ): def __init__( self, *args, **kwargs ): Node.__init__( self, *args, **kwargs ) # Create a cgroup and move shell into it - cgroup = 'cpu,cpuacct:/' + self.name - errFail( 'cgcreate -g ' + cgroup ) - errFail( 'cgclassify -g %s %s' % ( cgroup, self.pid ) ) + self.cgroup = 'cpu,cpuacct:/' + self.name + errFail( 'cgcreate -g ' + self.cgroup ) + errFail( 'cgclassify -g %s %s' % ( self.cgroup, self.pid ) ) self.period_us = kwargs.get( 'period_us', 10000 ) self.sched = kwargs.get( 'sched', 'rt' ) + def cleanup( self ): + "Clean up our cgroup" + Host.cleanup( self ) + debug( '*** deleting cgroup', self.cgroup, '\n' ) + errFail( 'cgdelete -r ' + self.cgroup ) + def cgroupSet( self, param, value, resource='cpu' ): "Set a cgroup parameter and return its value" cmd = 'cgset -r %s.%s=%s /%s' % ( resource, param, value, self.name ) - return quietRun( cmd ) + out = quietRun( cmd ) + nvalue = int( self.cgroupGet( param, resource ) ) + if nvalue != value: + error( '*** error: cgroupSet: %s set to %s instead of %s\n' + % ( param, nvalue, value ) ) + return nvalue def cgroupGet( self, param, resource='cpu' ): cmd = 'cgget -r %s.%s /%s' % ( @@ -505,8 +514,29 @@ class CPULimitedHost( Host ): result = quietRun( 'chrt -p %s' % self.pid ) firstline = result.split( '\n' )[ 0 ] lastword = firstline.split( ' ' )[ -1 ] + if lastword != 'SCHED_RR': + error( '*** error: could not assign SCHED_RR to %s\n' % self.name ) return lastword + def rtInfo( self, f ): + "Internal method: return parameters for RT bandwidth" + pstr, qstr = 'rt_period_us', 'rt_runtime_us' + # RT uses wall clock time for period and quota + quota = int( self.period_us * f * numCores() ) + return pstr, qstr, self.period_us, quota + + def cfsInfo( self, f): + "Internal method: return parameters for CFS bandwidth" + pstr, qstr = 'cfs_period_us', 'cfs_quota_us' + # CFS uses wall clock time for period and CPU time for quota. + quota = int( self.period_us * f * numCores() ) + period = self.period_us + if f > 0 and quota < 1000: + debug( '(cfsInfo: increasing default period) ' ) + quota = 1000 + period = int( quota / f / numCores() ) + return pstr, qstr, period, quota + # BL comment: # This may not be the right API, # since it doesn't specify CPU bandwidth in "absolute" @@ -515,7 +545,7 @@ class CPULimitedHost( Host ): # Alternatively, we should change from system fraction # to CPU seconds per second, essentially assuming that # all CPUs are the same. - + def setCPUFrac( self, f=-1, sched=None): """Set overall CPU fraction for this host f: CPU bandwidth limit (fraction) @@ -525,45 +555,22 @@ class CPULimitedHost( Host ): return if not sched: sched = self.sched - period = self.period_us if sched == 'rt': - pstr, qstr = 'rt_period_us', 'rt_runtime_us' - # RT uses system time for period and quota - quota = int( period * f * numCores() ) + pstr, qstr, period, quota = self.rtInfo( f ) elif sched == 'cfs': - pstr, qstr = 'cfs_period_us', 'cfs_quota_us' - # CFS uses wall clock time for period and CPU time for quota. - quota = int( self.period_us * f * numCores() ) - if f > 0 and quota < 1000: - info( '*** setCPUFrac: quota too small - adjusting period\n' ) - quota = 1000 - period = int( quota / f / numCores() ) + pstr, qstr, period, quota = self.cfsInfo( f ) else: return if quota < 0: # Reset to unlimited quota = -1 # Set cgroup's period and quota - self.cgroupSet( pstr, period ) - nquota = int ( self.cgroupGet( qstr ) ) - self.cgroupSet( qstr, quota ) - nperiod = int( self.cgroupGet( pstr ) ) - # Make sure it worked - if nperiod != self.period_us: - error( '*** error: period is %s rather than %s\n' % ( - nperiod, self.period_us ) ) - if nquota != quota: - error( '*** error: quota is %s rather than %s\n' % ( - nquota, quota ) ) + nperiod = self.cgroupSet( pstr, period ) + nquota = self.cgroupSet( qstr, quota ) if sched == 'rt': # Set RT priority if necessary nchrt = self.chrt( prio=20 ) - # Nake sure it worked - if sched == 'SCHED_RR' not in nchrt: - error( '*** error: could not assign SCHED_RR to %s\n' % self.name ) - info( '( period', nperiod, 'quota', nquota, nchrt, ') ' ) - else: - info( '( period', nperiod, 'quota', nquota, ') ' ) + info( '(%s %d/%dus) ' % ( sched, quota, period ) ) def config( self, cpu=None, sched=None, **params ): """cpu: desired overall system CPU fraction @@ -591,16 +598,20 @@ class CPULimitedHost( Host ): # reported by ifconfig for a switch data port is essentially # meaningless. # -# So, I'm tyring changing the API to make it +# So, I'm trying changing the API to make it # impossible to try this, since it will not work, since nobody # ever makes separate control networks in Mininet, and indeed # we don't even support running OVS in a namespace. +# +# Note if we have a separate control network, then it does +# make sense to have s1-eth0 as s1's control network interface, +# and we should set controlIntf accordingly. class Switch( Node ): """A Switch is a Node that is running (or has execed?) an OpenFlow switch.""" - portBase = SWITCH_PORT_BASE # 0 for OF < 1.0, 1 for OF >= 1.0 + portBase = 1 # Switches start with port 1 in OpenFlow def __init__( self, name, dpid=None, opts='', listenPort=None, **params): """dpid: dpid for switch (or None for default) From 8688ca924928d26e2b00f7fd881bd4ab5fd0d1c7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 13:40:03 -0800 Subject: [PATCH 046/250] Remove debugging message. --- bin/mn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/mn b/bin/mn index 7eab6c9..8da7e50 100755 --- a/bin/mn +++ b/bin/mn @@ -38,7 +38,7 @@ def customNode( constructors, argStr ): ( cname, constructors.keys() ) ) def custom( *args, **params ): params.update( kwargs ) - print 'CONSTRUCTOR', constructor, 'ARGS', args, 'PARAMS', params + # print 'CONSTRUCTOR', constructor, 'ARGS', args, 'PARAMS', params return constructor( *args, **params ) return custom From a908fafad7c9c60396d793bc6ac920c549f87ba9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 13:48:25 -0800 Subject: [PATCH 047/250] Change default to vanilla Intf. Also edit comments. --- mininet/link.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index a833298..026ee4f 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -1,5 +1,4 @@ """ - link.py: interface and link abstractions for mininet It seems useful to bundle functionality for interfaces into a single @@ -19,6 +18,10 @@ Basic division of labor: Intfs: know how to configure themselves Links: know how to connect nodes together +Intf: basic interface object that can configure itself +TCIntf: interface with bandwidth limiting and delay via tc + +Link: basic link class for creating veth pairs """ from mininet.log import info, error, debug @@ -26,7 +29,7 @@ from mininet.util import makeIntfPair from time import sleep import re -class BasicIntf( object ): +class Intf( object ): "Basic interface object that can configure itself." @@ -146,7 +149,7 @@ class BasicIntf( object ): return self.name -class TCIntf( BasicIntf ): +class TCIntf( Intf ): "Interface customized by tc (traffic control) utility" def config( self, bw=None, delay=None, loss=0, disable_gro=True, @@ -154,7 +157,7 @@ class TCIntf( BasicIntf ): enable_red=False, max_queue_size=1000, **params ): "Configure the port and set its properties." - result = BasicIntf.config( self, **params) + result = Intf.config( self, **params) # disable GRO if disable_gro: @@ -241,7 +244,6 @@ class TCIntf( BasicIntf ): result[ 'tcoutputs'] = tcoutputs return result -Intf = TCIntf class Link( object ): From 0dbfd3a636d14c2c9b459dce22f2081c58def128 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 13:48:46 -0800 Subject: [PATCH 048/250] Add CPULimitedHost to file comment. --- mininet/node.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mininet/node.py b/mininet/node.py index f815fd6..27235d8 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -13,6 +13,9 @@ Host: a virtual host. By default, a host is simply a shell; commands monitor(). Examples of how to run experiments using this functionality are provided in the examples/ directory. +CPULimitedHost: a virtual host whose CPU bandwidth is limited by + RT or CFS bandwidth limiting. + Switch: superclass for switch nodes. UserSwitch: a switch using the user-space switch from the OpenFlow From 2db4268ba8dada74bc27590ff5ba91accdf0969e Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 22:07:19 -0800 Subject: [PATCH 049/250] Fix NOX controller so that mn --controller nox,pyswitch,... works. --- bin/mn | 20 +++++++++++++------- mininet/node.py | 10 ++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/bin/mn b/bin/mn index 8da7e50..be870fb 100755 --- a/bin/mn +++ b/bin/mn @@ -18,7 +18,7 @@ import time from mininet.clean import cleanup from mininet.cli import CLI -from mininet.log import lg, LEVELS, info +from mininet.log import lg, LEVELS, info, warn from mininet.net import Mininet, init from mininet.node import Host, CPULimitedHost, Controller, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch @@ -29,19 +29,25 @@ from mininet.util import makeNumeric, custom def customNode( constructors, argStr ): "Return custom Node constructor based on argStr" - cname, noargs, kwargs = splitArgs( argStr ) + cname, newargs, kwargs = splitArgs( argStr ) constructor = constructors.get( cname, None ) - if noargs: - raise Exception( "please specify keyword arguments for " + cname ) + #if args: + # raise Exception( "please specify keyword arguments for " + cname ) if not constructor: raise Exception( "error: %s is unknown - please specify one of %s" % ( cname, constructors.keys() ) ) - def custom( *args, **params ): + def custom( name, *args, **params ): params.update( kwargs ) - # print 'CONSTRUCTOR', constructor, 'ARGS', args, 'PARAMS', params - return constructor( *args, **params ) + if not newargs: + return constructor( name, *args, **params ) + if args: + warn( 'warning: %s replacing %s with %s\n', + constructor, args, newargs ) + return constructor( name, *newargs, **params ) return custom + + # built in topologies, created only when run TOPODEF = 'minimal' TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), diff --git a/mininet/node.py b/mininet/node.py index 27235d8..1990695 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -50,7 +50,7 @@ import signal import select from subprocess import Popen, PIPE, STDOUT -from mininet.log import info, error, debug +from mininet.log import info, error, warn, debug from mininet.util import quietRun, errRun, errFail, moveIntf, isShellBuiltin from mininet.util import numCores from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN @@ -864,13 +864,15 @@ class ControllerParams( object ): class NOX( Controller ): "Controller to run a NOX application." - def __init__( self, name, noxArgs=[], **kwargs ): + def __init__( self, name, *noxArgs, **kwargs ): """Init. name: name to give controller - noxArgs: list of args, or single arg, to pass to NOX""" + noxArgs: arguments (strings) to pass to NOX""" if not noxArgs: + warn( 'warning: no NOX modules specified; ' + 'running packetdump only\n' ) noxArgs = [ 'packetdump' ] - elif type( noxArgs ) != list: + elif type( noxArgs ) not in ( list, tuple ): noxArgs = [ noxArgs ] if 'NOX_CORE_DIR' not in os.environ: From d27a3c52b92f798511606cefb9f110a0e68183d9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 22:08:40 -0800 Subject: [PATCH 050/250] Allow various subsets of (delay, bw, loss) and clean up status output. --- mininet/link.py | 117 +++++++++++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 55 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index 026ee4f..ed65e5b 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -33,11 +33,11 @@ class Intf( object ): "Basic interface object that can configure itself." - def __init__( self, node, name=None, link=None, **kwargs ): - """node: owning node (where this intf most likely lives) - name: interface name (e.g. h1-eth0) - link: parent link if any - other arguments are used to configure link parameters""" + def __init__( self, name, node=None, link=None, **kwargs ): + """name: interface name (e.g. h1-eth0) + node: owning node (where this intf most likely lives) + link: parent link if we're part of a link + other arguments are passed to config()""" self.node = node self.name = name self.link = link @@ -152,9 +152,9 @@ class Intf( object ): class TCIntf( Intf ): "Interface customized by tc (traffic control) utility" - def config( self, bw=None, delay=None, loss=0, disable_gro=True, + def config( self, bw=None, delay=None, loss=None, disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False, - enable_red=False, max_queue_size=1000, **params ): + enable_red=False, max_queue_size=None, **params ): "Configure the port and set its properties." result = Intf.config( self, **params) @@ -163,7 +163,8 @@ class TCIntf( Intf ): if disable_gro: self.cmd( 'ethtool -K %s gro off' % self ) - if bw is None and not delay and not loss: + if ( bw is None and not delay and not loss + and max_queue_size is None ): return if bw and ( bw < 0 or bw > 1000 ): @@ -177,59 +178,65 @@ class TCIntf( Intf ): if loss and ( loss < 0 or loss > 100 ): error( 'Bad loss percentage', loss, '%%\n' ) return - - if delay is None: - delay = '0ms' - - if bw is not None and delay is not None: - info( self, '(bw %.2fMbit, delay %s, loss %d%%) ' % - ( bw, delay, loss ) ) - - # BL: hmm... what exactly is this??? - # This seems kind of brittle - if speedup > 0 and self.node.name[0:2] == 'sw': - bw = speedup + + # Ugly but functional + stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + + ( [ '%s delay' % delay ] if delay is not None else [] ) + + ( ['%d%% loss' % loss ] if loss is not None else [] ) + + ( [ 'ECN' ] if enable_ecn else [ 'RED' ] if enable_red else [] ) ) + info( '(' + ' '.join( stuff ) + ') ' ) + + cmds = [ '%s qdisc del dev %s root' ] tc = 'tc' # was getCmd( 'tc' ) # Bandwidth control algorithms - if use_hfsc: - cmds = [ '%s qdisc del dev %s root', - '%s qdisc add dev %s root handle 1:0 hfsc default 1' ] - if bw is not None: - cmds.append( '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' + - 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ) - elif use_tbf: - latency_us = 10 * 1500 * 8 / bw - cmds = ['%s qdisc del dev %s root', - '%s qdisc add dev %s root handle 1: tbf ' + - 'rate %fMbit burst 15000 latency %fus' % (bw, latency_us) ] + if bw is None: + parent = ' root ' else: - cmds = [ '%s qdisc del dev %s root', - '%s qdisc add dev %s root handle 1:0 htb default 1', - '%s class add dev %s parent 1:0 classid 1:1 htb ' + - 'rate %fMbit burst 15k' % bw ] + parent = ' parent 1:1 ' + # BL: hmm... this seems a bit brittle + if speedup > 0 and self.node.name[0:2] == 'sw': + bw = speedup + if use_hfsc: + cmds += [ '%s qdisc add dev %s root handle 1:0 hfsc default 1', + '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' + + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] + elif use_tbf: + latency_us = 10 * 1500 * 8 / bw + cmds += ['%s qdisc add dev %s root handle 1: tbf ' + + 'rate %fMbit burst 15000 latency %fus' % (bw, latency_us) ] + else: + cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1', + '%s class add dev %s parent 1:0 classid 1:1 htb ' + + 'rate %fMbit burst 15k' % bw ] + parent = ' parent 1:1 ' + + # ECN or RED + if enable_ecn: + cmds += [ '%s qdisc add dev %s' + parent + + 'handle 10: red limit 1000000 '+ + 'min 20000 max 25000 avpkt 1000 '+ + 'burst 20 '+ + 'bandwidth %fmbit probability 1 ecn' % bw ] + parent = ' parent 10: ' + elif enable_red: + cmds += [ '%s qdisc add dev %s' + parent + + 'handle 10: red limit 1000000 '+ + 'min 20000 max 25000 avpkt 1000 '+ + 'burst 20 '+ + 'bandwidth %fmbit probability 1' % bw ] + parent = ' parent 10: ' + + # Delay/loss/max queue size + netemargs = '%s%s%s' % ( + 'delay %s ' % delay if delay is not None else '', + 'loss %d ' % loss if loss is not None else '', + 'limit %d' % max_queue_size if max_queue_size is not None else '' ) + if netemargs: + cmds += [ '%s qdisc add dev %s ' + parent + ' netem ' + + netemargs ] - # ECN or RED - if enable_ecn: - info( 'Enabling ECN\n' ) - cmds += [ '%s qdisc add dev %s parent 1:1 '+ - 'handle 10: red limit 1000000 '+ - 'min 20000 max 25000 avpkt 1000 '+ - 'burst 20 '+ - 'bandwidth %fmbit probability 1 ecn' % bw ] - elif enable_red: - info( 'Enabling RED\n' ) - cmds += [ '%s qdisc add dev %s parent 1:1 '+ - 'handle 10: red limit 1000000 '+ - 'min 20000 max 25000 avpkt 1000 '+ - 'burst 20 '+ - 'bandwidth %fmbit probability 1' % bw ] - else: - cmds += [ '%s qdisc add dev %s parent 1:1 handle 10:0 netem ' + - 'delay ' + '%s' % delay + ' loss ' + '%d' % loss + - ' limit %d' % (max_queue_size) ] - # Execute all the commands in the container debug("at map stage w/cmds: %s\n" % cmds) From 9addfc13ce7499d5ea1c26711cdc62ad6a565551 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Mar 2012 23:48:07 -0800 Subject: [PATCH 051/250] Add OVSController to complete out-of-box Ubuntu experience. --- bin/mn | 9 +++++---- mininet/node.py | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bin/mn b/bin/mn index be870fb..ec4cf8e 100755 --- a/bin/mn +++ b/bin/mn @@ -20,7 +20,7 @@ from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info, warn from mininet.net import Mininet, init -from mininet.node import Host, CPULimitedHost, Controller, NOX +from mininet.node import Host, CPULimitedHost, Controller, OVSController, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch from mininet.link import Intf, TCIntf from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo @@ -67,9 +67,10 @@ HOSTS = { 'proc': Host, CONTROLLERDEF = 'ref' CONTROLLERS = { 'ref': Controller, - 'nox': NOX, - 'remote': RemoteController, - 'none': lambda name: None } + 'ovsc': OVSController, + 'nox': NOX, + 'remote': RemoteController, + 'none': lambda name: None } INTFDEF = 'default' INTFS = { 'default': Intf, diff --git a/mininet/node.py b/mininet/node.py index 1990695..77b6712 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -847,6 +847,12 @@ class Controller( Node ): return ip +class OVSController( Controller ): + "Open vSwitch controller" + def __init__( self, name, command='ovs-controller', **kwargs ): + Controller.__init__( self, name, command=command, **kwargs ) + + # BL: This really seems to be poorly specified, # so it's going to go away! From e3c074b88178317e044257b33e61b91293ef1670 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Mar 2012 13:45:22 -0800 Subject: [PATCH 052/250] Remove deprecated ControllerParams (for now.) --- mininet/test/test_nets.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/mininet/test/test_nets.py b/mininet/test/test_nets.py index 37094c4..db63404 100755 --- a/mininet/test/test_nets.py +++ b/mininet/test/test_nets.py @@ -7,14 +7,12 @@ import unittest from mininet.net import init, Mininet from mininet.node import Host, Controller, ControllerParams -# from mininet.node import KernelSwitch from mininet.node import UserSwitch, OVSKernelSwitch from mininet.topo import SingleSwitchTopo, LinearTopo from mininet.log import setLogLevel SWITCHES = { 'user': UserSwitch, 'ovsk': OVSKernelSwitch, - # 'kernel': KernelSwitch } @@ -25,9 +23,7 @@ class testSingleSwitch( unittest.TestCase ): "Ping test with both datapaths on minimal topology" init() for switch in SWITCHES.values(): - controllerParams = ControllerParams( '10.0.0.0', 8 ) - mn = Mininet( SingleSwitchTopo(), switch, Host, Controller, - controllerParams ) + mn = Mininet( SingleSwitchTopo(), switch, Host, Controller ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) @@ -35,9 +31,7 @@ class testSingleSwitch( unittest.TestCase ): "Ping test with both datapaths on 5-host single-switch topology" init() for switch in SWITCHES.values(): - controllerParams = ControllerParams( '10.0.0.0', 8 ) - mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller, - controllerParams ) + mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) @@ -49,13 +43,11 @@ class testLinear( unittest.TestCase ): "Ping test with both datapaths on a 5-switch topology" init() for switch in SWITCHES.values(): - controllerParams = ControllerParams( '10.0.0.0', 8 ) - mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller, - controllerParams ) + mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) if __name__ == '__main__': - setLogLevel('warning') + setLogLevel( 'warning' ) unittest.main() From 8e3699eca6b9dd948e1b7ef1e67cf9868bc253c8 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Mar 2012 14:10:20 -0800 Subject: [PATCH 053/250] Move init() into Mininet() and remove calls (since called automatically.) Note: we should probably rename it "setup()" to avoid confusion. --- bin/mn | 7 ++----- examples/linearbandwidth.py | 3 +-- examples/scratchnet.py | 9 ++++----- examples/scratchnetuser.py | 4 ++-- mininet/net.py | 34 ++++++++++++++++------------------ mininet/node.py | 8 ++++---- mininet/test/test_nets.py | 7 ++----- 7 files changed, 31 insertions(+), 41 deletions(-) diff --git a/bin/mn b/bin/mn index ec4cf8e..07bca4d 100755 --- a/bin/mn +++ b/bin/mn @@ -19,7 +19,7 @@ import time from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info, warn -from mininet.net import Mininet, init +from mininet.net import Mininet from mininet.node import Host, CPULimitedHost, Controller, OVSController, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch from mininet.link import Intf, TCIntf @@ -27,6 +27,7 @@ from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo from mininet.util import makeNumeric, custom + def customNode( constructors, argStr ): "Return custom Node constructor based on argStr" cname, newargs, kwargs = splitArgs( argStr ) @@ -47,7 +48,6 @@ def customNode( constructors, argStr ): return custom - # built in topologies, created only when run TOPODEF = 'minimal' TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), @@ -229,9 +229,6 @@ class MininetRunner( object ): % self.options.verbosity ) lg.setLogLevel( self.options.verbosity ) - # validate environment setup - init() - def begin( self ): "Create and run mininet." diff --git a/examples/linearbandwidth.py b/examples/linearbandwidth.py index da14898..4eb6371 100755 --- a/examples/linearbandwidth.py +++ b/examples/linearbandwidth.py @@ -26,7 +26,7 @@ of switches, this example demonstrates: import sys flush = sys.stdout.flush -from mininet.net import init, Mininet +from mininet.net import Mininet # from mininet.node import KernelSwitch from mininet.node import UserSwitch, OVSKernelSwitch from mininet.topo import Topo, Node @@ -106,7 +106,6 @@ def linearBandwidthTest( lengths ): if __name__ == '__main__': lg.setLogLevel( 'info' ) - init() sizes = [ 1, 10, 20, 40, 60, 80, 100 ] print "*** Running linearBandwidthTest", sizes linearBandwidthTest( sizes ) diff --git a/examples/scratchnet.py b/examples/scratchnet.py index cdb1329..2154620 100755 --- a/examples/scratchnet.py +++ b/examples/scratchnet.py @@ -8,13 +8,13 @@ but it exposes the configuration details and allows customization. For most tasks, the higher-level API will be preferable. """ -from mininet.net import init -from mininet.node import Node, OVSKernelSwitch +from mininet.net import Mininet +from mininet.node import Node from mininet.util import createLink from mininet.log import setLogLevel, info def scratchNet( cname='controller', cargs='ptcp:' ): - "Create network from scratch using kernel switch." + "Create network from scratch using Open vSwitch." info( "*** Creating nodes\n" ) controller = Node( 'c0', inNamespace=False ) @@ -53,6 +53,5 @@ def scratchNet( cname='controller', cargs='ptcp:' ): if __name__ == '__main__': setLogLevel( 'info' ) info( '*** Scratch network demo (kernel datapath)\n' ) - OVSKernelSwitch.setup() - init() + Mininet.init() scratchNet() diff --git a/examples/scratchnetuser.py b/examples/scratchnetuser.py index ea053fa..e43bc3f 100755 --- a/examples/scratchnetuser.py +++ b/examples/scratchnetuser.py @@ -10,7 +10,7 @@ For most tasks, the higher-level API will be preferable. This version uses the user datapath and an explicit control network. """ -from mininet.net import init +from mininet.net import Mininet from mininet.node import Node from mininet.util import createLink from mininet.log import setLogLevel, info @@ -64,5 +64,5 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ): if __name__ == '__main__': setLogLevel( 'info' ) info( '*** Scratch network demo (user datapath)\n' ) - init() + Mininet.init() scratchNetUser() diff --git a/mininet/net.py b/mininet/net.py index 953a5bc..3be43c0 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -146,7 +146,7 @@ class Mininet( object ): self.terms = [] # list of spawned xterm processes - init() # Initialize Mininet if necessary + Mininet.init() # Initialize Mininet if necessary self.built = False if topo and build: @@ -527,6 +527,21 @@ class Mininet( object ): self.stop() return result + inited = False + + @classmethod + def init( cls ): + "Initialize Mininet" + if cls.inited: + return + if os.getuid() != 0: + # Note: this script must be run as root + # Perhaps we should do so automatically! + print "*** Mininet must run as root." + exit( 1 ) + fixLimits() + cls.inited = True + class MininetWithControlNet( Mininet ): @@ -592,21 +607,4 @@ class MininetWithControlNet( Mininet ): exit( 1 ) info( '\n' ) -# pylint thinks inited is unused -# pylint: disable-msg=W0612 -def init(): - "Initialize Mininet." - if init.inited: - return - if os.getuid() != 0: - # Note: this script must be run as root - # Perhaps we should do so automatically! - print "*** Mininet must run as root." - exit( 1 ) - fixLimits() - init.inited = True - -init.inited = False - -# pylint: enable-msg=W0612 diff --git a/mininet/node.py b/mininet/node.py index 77b6712..90a749c 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -499,7 +499,7 @@ class CPULimitedHost( Host ): "Set a cgroup parameter and return its value" cmd = 'cgset -r %s.%s=%s /%s' % ( resource, param, value, self.name ) - out = quietRun( cmd ) + quietRun( cmd ) nvalue = int( self.cgroupGet( param, resource ) ) if nvalue != value: error( '*** error: cgroupSet: %s set to %s instead of %s\n' @@ -568,11 +568,11 @@ class CPULimitedHost( Host ): # Reset to unlimited quota = -1 # Set cgroup's period and quota - nperiod = self.cgroupSet( pstr, period ) - nquota = self.cgroupSet( qstr, quota ) + self.cgroupSet( pstr, period ) + self.cgroupSet( qstr, quota ) if sched == 'rt': # Set RT priority if necessary - nchrt = self.chrt( prio=20 ) + self.chrt( prio=20 ) info( '(%s %d/%dus) ' % ( sched, quota, period ) ) def config( self, cpu=None, sched=None, **params ): diff --git a/mininet/test/test_nets.py b/mininet/test/test_nets.py index db63404..fde8e87 100755 --- a/mininet/test/test_nets.py +++ b/mininet/test/test_nets.py @@ -5,8 +5,8 @@ import unittest -from mininet.net import init, Mininet -from mininet.node import Host, Controller, ControllerParams +from mininet.net import Mininet +from mininet.node import Host, Controller from mininet.node import UserSwitch, OVSKernelSwitch from mininet.topo import SingleSwitchTopo, LinearTopo from mininet.log import setLogLevel @@ -21,7 +21,6 @@ class testSingleSwitch( unittest.TestCase ): def testMinimal( self ): "Ping test with both datapaths on minimal topology" - init() for switch in SWITCHES.values(): mn = Mininet( SingleSwitchTopo(), switch, Host, Controller ) dropped = mn.run( mn.ping ) @@ -29,7 +28,6 @@ class testSingleSwitch( unittest.TestCase ): def testSingle5( self ): "Ping test with both datapaths on 5-host single-switch topology" - init() for switch in SWITCHES.values(): mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller ) dropped = mn.run( mn.ping ) @@ -41,7 +39,6 @@ class testLinear( unittest.TestCase ): def testLinear5( self ): "Ping test with both datapaths on a 5-switch topology" - init() for switch in SWITCHES.values(): mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller ) dropped = mn.run( mn.ping ) From a49c85a61082d9bacf822cc369bcf62b98a1416c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Mar 2012 16:06:23 -0800 Subject: [PATCH 054/250] Fix examples to work with new API (and vice-versa.) --- examples/README | 17 +++++++++++------ examples/baresshd.py | 7 +++++-- examples/scratchnet.py | 37 ++++++++++++++++++++++++------------- examples/scratchnetuser.py | 23 ++++++++++++++--------- examples/sshd.py | 8 ++++---- mininet/clean.py | 6 ++++++ mininet/link.py | 5 ++++- mininet/net.py | 4 ++-- mininet/node.py | 13 ++++++++----- mininet/util.py | 9 --------- 10 files changed, 78 insertions(+), 51 deletions(-) diff --git a/examples/README b/examples/README index 4f233a0..984c9a3 100644 --- a/examples/README +++ b/examples/README @@ -6,12 +6,21 @@ Mininet's Python API. --- +baresshd.py: + +This example uses Mininet's medium-level API to create an sshd +process running in a namespace. Doesn't use OpenFlow. + consoles.py: This example creates a grid of console windows, one for each node, and allows interaction with and monitoring of each console, including graphical monitoring. +controllers.py: + +This example creates a network and adds multiple controllers to it. + emptynet.py: This example demonstrates creating an empty network (i.e. with no @@ -44,10 +53,10 @@ to an interface in the root namespace (generaly the control network already lives in the root namespace, so it does not need to be explicitly connected.) -treeping64: +treeping64.py: This example creates a 64-host tree network, and attempts to check full -connectivity using ping, for three different switch/datapath types. +connectivity using ping, for different switch/datapath types. tree1024.py: @@ -55,7 +64,3 @@ This example attempts to create a 1024-host network, and then runs the CLI on it. It may run into scalability limits, depending on available memory and sysctl configuration (see INSTALL.) -udpbwtest.py: - -This example shows how to run a test across an entire network, and monitor -the output of a set of hosts in real time. diff --git a/examples/baresshd.py b/examples/baresshd.py index 841454a..3b616d1 100755 --- a/examples/baresshd.py +++ b/examples/baresshd.py @@ -6,14 +6,17 @@ from mininet.node import Host print "*** Creating nodes" h1 = Host( 'h1' ) + root = Host( 'root', inNamespace=False ) print "*** Creating links" h1.linkTo( root ) +print h1 + print "*** Configuring nodes" -h1.setIP( h1.intfs[ 0 ], '10.0.0.1', 8 ) -root.setIP( root.intfs[ 0 ], '10.0.0.2', 8 ) +h1.setIP( '10.0.0.1', 8 ) +root.setIP( '10.0.0.2', 8 ) print "*** Creating banner file" f = open( '/tmp/%s.banner' % h1.name, 'w' ) diff --git a/examples/scratchnet.py b/examples/scratchnet.py index 2154620..966a183 100755 --- a/examples/scratchnet.py +++ b/examples/scratchnet.py @@ -10,10 +10,13 @@ For most tasks, the higher-level API will be preferable. from mininet.net import Mininet from mininet.node import Node -from mininet.util import createLink +from mininet.link import Link from mininet.log import setLogLevel, info +from mininet.util import quietRun -def scratchNet( cname='controller', cargs='ptcp:' ): +from time import sleep + +def scratchNet( cname='controller', cargs='-v ptcp:' ): "Create network from scratch using Open vSwitch." info( "*** Creating nodes\n" ) @@ -23,30 +26,38 @@ def scratchNet( cname='controller', cargs='ptcp:' ): h1 = Node( 'h1' ) info( "*** Creating links\n" ) - createLink( node1=h0, node2=switch, port1=0, port2=0 ) - createLink( node1=h1, node2=switch, port1=0, port2=1 ) + Link( h0, switch ) + Link( h1, switch ) info( "*** Configuring hosts\n" ) - h0.setIP( h0.intfs[ 0 ], '192.168.123.1', 24 ) - h1.setIP( h1.intfs[ 0 ], '192.168.123.2', 24 ) + h0.setIP( '192.168.123.1/24' ) + h1.setIP( '192.168.123.2/24' ) info( str( h0 ) + '\n' ) info( str( h1 ) + '\n' ) - info( "*** Starting network using Open vSwitch kernel datapath\n" ) + info( "*** Starting network using Open vSwitch\n" ) controller.cmd( cname + ' ' + cargs + '&' ) - switch.cmd( 'ovs-dpctl del-dp dp0' ) - switch.cmd( 'ovs-dpctl add-dp dp0' ) + switch.cmd( 'ovs-vsctl del-br dp0' ) + switch.cmd( 'ovs-vsctl add-br dp0' ) for intf in switch.intfs.values(): - print switch.cmd( 'ovs-dpctl add-if dp0 ' + intf ) - print switch.cmd( 'ovs-openflowd dp0 tcp:127.0.0.1 &' ) + print switch.cmd( 'ovs-vsctl add-port dp0 %s' % intf ) + + # Note: controller and switch are in root namespace, and we + # can connect via loopback interface + switch.cmd( 'ovs-vsctl set-controller dp0 tcp:127.0.0.1:6633' ) + + info( '*** Waiting for switch to connect to controller' ) + while 'is_connected' not in quietRun( 'ovs-vsctl show' ): + sleep( 1 ) + info( '.' ) + info( '\n' ) info( "*** Running test\n" ) h0.cmdPrint( 'ping -c1 ' + h1.IP() ) info( "*** Stopping network\n" ) controller.cmd( 'kill %' + cname ) - switch.cmd( 'ovs-dpctl del-dp dp0' ) - switch.cmd( 'kill %ovs-openflowd' ) + switch.cmd( 'ovs-vsctl del-br dp0' ) switch.deleteIntfs() info( '\n' ) diff --git a/examples/scratchnetuser.py b/examples/scratchnetuser.py index e43bc3f..59bc601 100755 --- a/examples/scratchnetuser.py +++ b/examples/scratchnetuser.py @@ -12,9 +12,14 @@ This version uses the user datapath and an explicit control network. from mininet.net import Mininet from mininet.node import Node -from mininet.util import createLink +from mininet.link import Link from mininet.log import setLogLevel, info +def linkIntfs( node1, node2 ): + "Create link from node1 to node2 and return intfs" + link = Link( node1, node2 ) + return link.intf1, link.intf2 + def scratchNetUser( cname='controller', cargs='ptcp:' ): "Create network from scratch using user switch." @@ -28,17 +33,17 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ): switch = Node( 's0') h0 = Node( 'h0' ) h1 = Node( 'h1' ) - cintf, sintf = createLink( controller, switch ) - h0intf, sintf1 = createLink( h0, switch ) - h1intf, sintf2 = createLink( h1, switch ) + cintf, sintf = linkIntfs( controller, switch ) + h0intf, sintf1 = linkIntfs( h0, switch ) + h1intf, sintf2 = linkIntfs( h1, switch ) info( '*** Configuring control network\n' ) - controller.setIP( cintf, '10.0.123.1', 24 ) - switch.setIP( sintf, '10.0.123.2', 24 ) + controller.setIP( '10.0.123.1/24', cintf ) + switch.setIP( '10.0.123.2/24', sintf) info( '*** Configuring hosts\n' ) - h0.setIP( h0intf, '192.168.123.1', 24 ) - h1.setIP( h1intf, '192.168.123.2', 24 ) + h0.setIP( '192.168.123.1/24', h0intf ) + h1.setIP( '192.168.123.2/24', h1intf ) info( '*** Network state:\n' ) for node in controller, switch, h0, h1: @@ -47,7 +52,7 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ): info( '*** Starting controller and user datapath\n' ) controller.cmd( cname + ' ' + cargs + '&' ) switch.cmd( 'ifconfig lo 127.0.0.1' ) - intfs = [ sintf1, sintf2 ] + intfs = map( str, [ sintf1, sintf2 ] ) switch.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' ptcp: &' ) switch.cmd( 'ofprotocol tcp:' + controller.IP() + ' tcp:localhost &' ) diff --git a/examples/sshd.py b/examples/sshd.py index 9082d7d..2bedb9c 100755 --- a/examples/sshd.py +++ b/examples/sshd.py @@ -21,7 +21,7 @@ from mininet.cli import CLI from mininet.log import lg from mininet.node import Node, OVSKernelSwitch from mininet.topolib import TreeTopo -from mininet.util import createLink +from mininet.link import Link def TreeNet( depth=1, fanout=2, **kwargs ): "Convenience function for creating tree networks." @@ -37,13 +37,13 @@ def connectToRootNS( network, switch, ip, prefixLen, routes ): routes: host networks to route to""" # Create a node in root namespace and link to switch 0 root = Node( 'root', inNamespace=False ) - intf = createLink( root, switch )[ 0 ] - root.setIP( intf, ip, prefixLen ) + intf = Link( root, switch ).intf1 + root.setIP( ip, prefixLen, intf ) # Start network that now includes link to root namespace network.start() # Add routes from root ns to hosts for route in routes: - root.cmd( 'route add -net ' + route + ' dev ' + intf ) + root.cmd( 'route add -net ' + route + ' dev ' + str( intf ) ) def sshd( network, cmd='/usr/sbin/sshd', opts='-D' ): "Start a network, connect it to root ns, and run sshd on all hosts." diff --git a/mininet/clean.py b/mininet/clean.py index 3052e97..eac8fda 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -45,6 +45,12 @@ def cleanup(): if dp != '': sh( 'dpctl deldp ' + dp ) + info( "*** Removing OVS datapaths" ) + dps = sh("ovs-vsctl list-br").split( '\n' ) + for dp in dps: + if dp: + sh( 'ovs-vsctl del-br ' + dp ) + info( "*** Removing all links of the pattern foo-ethX\n" ) links = sh( "ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' ) for link in links: diff --git a/mininet/link.py b/mininet/link.py index ed65e5b..7d606f9 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -95,6 +95,8 @@ class Intf( object ): def isUp( self, set=False ): "Return whether interface is up" + if set: + self.ifconfig( 'up' ) return "UP" in self.ifconfig() # The reason why we configure things in this way is so @@ -123,7 +125,7 @@ class Intf( object ): return result def config( self, mac=None, ip=None, ifconfig=None, - defaultRoute=None, **params): + defaultRoute=None, up=True, **params): """Configure Node according to (optional) parameters: mac: MAC address ip: IP address @@ -136,6 +138,7 @@ class Intf( object ): r = {} self.setParam( r, 'setMAC', mac=mac ) self.setParam( r, 'setIP', ip=ip ) + self.setParam( r, 'isUp', up=up ) self.setParam( r, 'ifconfig', ifconfig=ifconfig ) return r diff --git a/mininet/net.py b/mininet/net.py index 3be43c0..3fb2d3d 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -97,7 +97,7 @@ from mininet.log import info, error, debug, output from mininet.node import Host, OVSKernelSwitch, Controller from mininet.link import Link from mininet.util import quietRun, fixLimits -from mininet.util import createLink, macColonHex, ipStr, ipParse +from mininet.util import macColonHex, ipStr, ipParse from mininet.term import cleanUpScreens, makeTerms class Mininet( object ): @@ -584,7 +584,7 @@ class MininetWithControlNet( Mininet ): snum = ipParse( ip ) for switch in self.switches: info( ' ' + switch.name ) - sintf, cintf = createLink( switch, controller ) + sintf, cintf = self.link( switch, controller ) snum += 1 while snum & 0xff in [ 0, 255 ]: snum += 1 diff --git a/mininet/node.py b/mininet/node.py index 90a749c..cbe2917 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -386,17 +386,20 @@ class Node( object ): intf: interface name ip: IP address as a string prefixLen: prefix length, e.g. 8 for /8 or 16M addrs""" - # This should probably be rethought: - ipSub = '%s/%s' % ( ip, prefixLen ) - return self.intf( intf ).setIP( ipSub ) + # This should probably be rethought + if '/' not in ip: + ip = '%s/%s' % ( ip, prefixLen ) + return self.intf( intf ).setIP( ip ) def IP( self, intf=None ): "Return IP address of a node or specific interface." - return self.intf( intf ).IP() + i = self.intf( intf ) + return self.intf( i ).IP() if i else None def MAC( self, intf=None ): "Return MAC address of a node or specific interface." - return self.intf( intf ).MAC() + i = self.intf( intf ) + return self.intf( i ).MAC() if i else None def intfIsUp( self, intf=None ): "Check if an interface is up." diff --git a/mininet/util.py b/mininet/util.py index 3bf9dd0..1a503e1 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -183,15 +183,6 @@ def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ): printError: if true, print error""" retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError ) -def createLink( node1, node2, port1=None, port2=None ): - """Create a link between nodes, making an interface for each. - node1: Node object - node2: Node object - port1: node1 port number (optional) - port2: node2 port number (optional) - returns: intf1 name, intf2 name""" - return node1.linkTo( node2, port1, port2 ) - # IP and Mac address formatting and parsing From 82f483f5591acd10c1def9d47489ab6494ccf2dc Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Mar 2012 17:44:47 -0800 Subject: [PATCH 055/250] Add support for specifying host IP range with --ipbase. --- bin/mn | 4 ++++ mininet/net.py | 11 ++++++++--- mininet/topo.py | 31 ++++++++++++++++++++----------- mininet/util.py | 19 ++++++++++++++----- 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/bin/mn b/bin/mn index 07bca4d..1768207 100755 --- a/bin/mn +++ b/bin/mn @@ -192,6 +192,8 @@ class MininetRunner( object ): help='|'.join( TESTS ) ) opts.add_option( '--xterms', '-x', action='store_true', default=False, help='spawn xterms for each node' ) + opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8', + help='base IP address for hosts' ) opts.add_option( '--mac', action='store_true', default=False, help='automatically set host MACs' ) opts.add_option( '--arp', action='store_true', @@ -248,6 +250,7 @@ class MininetRunner( object ): self.validate( self.options ) inNamespace = self.options.innamespace + ipBase = self.options.ipbase xterms = self.options.xterms mac = self.options.mac arp = self.options.arp @@ -257,6 +260,7 @@ class MininetRunner( object ): mn = Mininet( topo=topo, switch=switch, host=host, controller=controller, intf=intf, + ipBase=ipBase, inNamespace=inNamespace, xterms=xterms, autoSetMacs=mac, autoStaticArp=arp, listenPort=listenPort ) diff --git a/mininet/net.py b/mininet/net.py index 3fb2d3d..9e1e161 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -97,7 +97,7 @@ from mininet.log import info, error, debug, output from mininet.node import Host, OVSKernelSwitch, Controller from mininet.link import Link from mininet.util import quietRun, fixLimits -from mininet.util import macColonHex, ipStr, ipParse +from mininet.util import macColonHex, ipStr, ipParse, netParse from mininet.term import cleanUpScreens, makeTerms class Mininet( object ): @@ -105,7 +105,7 @@ class Mininet( object ): def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, controller=Controller, link=Link, intf=None, - build=True, xterms=False, cleanup=False, + build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', inNamespace=False, autoSetMacs=False, autoStaticArp=False, listenPort=None ): """Create Mininet object. @@ -130,6 +130,7 @@ class Mininet( object ): self.controller = controller self.link = link self.intf = intf + self.ipBase = ipBase self.inNamespace = inNamespace self.xterms = xterms self.cleanup = cleanup @@ -214,6 +215,8 @@ class Mininet( object ): At the end of this function, everything should be connected and up.""" + ipBaseNum, prefixLen = netParse( self.ipBase ) + if not topo: topo = self.topo() @@ -222,7 +225,9 @@ class Mininet( object ): name = prefix + topo.name( nodeId ) ni = topo.nodeInfo( nodeId ) # Default IP and MAC addresses - defaults = { 'ip': topo.ip( nodeId ) } + defaults = { 'ip': topo.ip( nodeId, + ipBaseNum=ipBaseNum, + prefixLen=prefixLen ) } if self.autoSetMacs: defaults[ 'mac'] = macColonHex( nodeId ) defaults.update( ni.params ) diff --git a/mininet/topo.py b/mininet/topo.py index 2cbfe7d..6b4e572 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,6 +16,7 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph +from util import netParse, ipStr class NodeID(object): '''Topo node identifier.''' @@ -42,15 +43,22 @@ class NodeID(object): ''' return str(self.dpid) - def ip_str(self): + def ip_str(self, ipBase=None, prefixLen=8, ipBaseNum=0x0a000000): '''Name conversion. - + ipBase: optional base IP address string + prefixLen: optional IP prefix length + ipBaseNum: option base IP address as int @return ip ip as string ''' - hi = (self.dpid & 0xff0000) >> 16 - mid = (self.dpid & 0xff00) >> 8 - lo = self.dpid & 0xff - return "10.%i.%i.%i" % (hi, mid, lo) + if ipBase: + ipnum, prefixLen = netParse( ipBase ) + else: + ipBaseNum = ipBaseNum + # Ugly but functional + assert self.dpid < ( 1 << ( 32 - prefixLen ) ) + mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 ) + ipnum = self.dpid + ( ipBaseNum & mask ) + return ipStr( ipnum ) class Node( object ): @@ -109,11 +117,12 @@ class Topo(object): per-topo classes per-network classes""" - def __init__(self, node=None, switch=None, link=None): + def __init__(self, node=None, switch=None, link=None ): """Create Topo object. node: default node/host class (optional) switch: default switch class (optional) - link: default link class (optional)""" + link: default link class (optional) + ipBase: default IP address base (optional)""" self.g = Graph() self.node_info = {} # dpids hash to Node objects self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects @@ -342,13 +351,13 @@ class Topo(object): ''' return self.id_gen(dpid = dpid).name_str() - def ip(self, dpid): + def ip(self, dpid, **params): '''Get IP dotted-decimal string of node ID. - @param dpid DPID of host or switch + @param params: params to pass to ip_str @return ip_str ''' - return self.id_gen(dpid = dpid).ip_str() + return self.id_gen(dpid = dpid).ip_str(**params) def nodeInfo( self, dpid ): "Return metadata for node" diff --git a/mininet/util.py b/mininet/util.py index 1a503e1..79b4a12 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -204,19 +204,19 @@ def macColonHex( mac ): returns: macStr MAC colon-hex string""" return _colonHex( mac, 6 ) -def ipStr( ip ): +def ipStr( ip, defaultNet=10 ): """Generate IP address string from an unsigned int. ip: unsigned int of form w << 24 | x << 16 | y << 8 | z returns: ip address string w.x.y.z, or 10.x.y.z if w==0""" - w = ( ip & 0xff000000 ) >> 24 + w = ( ip >> 24 ) & 0xff w = 10 if w == 0 else w - x = ( ip & 0xff0000 ) >> 16 - y = ( ip & 0xff00 ) >> 8 + x = ( ip >> 16 ) & 0xff + y = ( ip >> 8 ) & 0xff z = ip & 0xff return "%i.%i.%i.%i" % ( w, x, y, z ) def ipNum( w, x, y, z ): - """Generate unsigned int from components ofIP address + """Generate unsigned int from components of IP address returns: w << 24 | x << 16 | y << 8 | z""" return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z @@ -225,6 +225,15 @@ def ipParse( ip ): args = [ int( arg ) for arg in ip.split( '.' ) ] return ipNum( *args ) +def netParse( ipstr ): + """Parse an IP network specification, returning + address and prefix len as unsigned ints""" + prefixLen = 0 + if '/' in ipstr: + ip, pf = ipstr.split( '/' ) + prefixLen = int( pf ) + return ipParse( ip ), prefixLen + def checkInt( s ): "Check if input string is an int" try: From 1d814c606dc7ec65f2a67a51770212e9b4a65cb7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Mar 2012 19:27:04 -0800 Subject: [PATCH 056/250] disabled-msg -> disabled for current pylint --- .pylint | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/.pylint b/.pylint index 4c4b48b..de4ac44 100644 --- a/.pylint +++ b/.pylint @@ -25,9 +25,6 @@ ignore=CVS # Pickle collected data for later comparisons. persistent=yes -# Set the cache size for astng objects. -cache-size=500 - # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= @@ -35,32 +32,23 @@ load-plugins= [MESSAGES CONTROL] -# Enable only checker(s) with the given id(s). This option conflicts with the -# disable-checker option -#enable-checker= +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. +#enable= -# Enable all checker(s) except those with the given id(s). This option -# conflicts with the enable-checker option -#disable-checker= - -# Enable all messages in the listed categories (IRCWEF). -#enable-msg-cat= - -# Disable all messages in the listed categories (IRCWEF). -disable-msg-cat=IR - -# Enable the message(s) with the given id(s). -#enable-msg= - -# Disable the message(s) with the given id(s). -disable-msg=W0704,C0103,W0231,E1102,W0511,W0142,R0902,R0903,R0904,R0913,R0914,R0801 +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). +disable=W0704,C0103,W0231,E1102,W0511,W0142,R0902,R0903,R0904,R0913,R0914,R0801,I0011 [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html -output-format=text +output-format=colorized # Include message's id in outpu include-ids=yes From 14ff3ad3d02dbebf65d0e9aecdbe9c531189261f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 10 Mar 2012 20:44:34 -0800 Subject: [PATCH 057/250] Fix codecheck and MininetWithControlNet. --- bin/mn | 49 ++++---- examples/consoles.py | 16 +-- examples/limit.py | 7 +- examples/miniedit.py | 25 ++--- examples/scratchnetuser.py | 2 +- mininet/cli.py | 18 +-- mininet/link.py | 224 ++++++++++++++++++++++--------------- mininet/net.py | 40 ++++--- mininet/node.py | 120 +++++++++++--------- mininet/topo.py | 6 +- mininet/util.py | 24 ++-- 11 files changed, 292 insertions(+), 239 deletions(-) diff --git a/bin/mn b/bin/mn index 1768207..a07833a 100755 --- a/bin/mn +++ b/bin/mn @@ -19,7 +19,7 @@ import time from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info, warn -from mininet.net import Mininet +from mininet.net import Mininet, MininetWithControlNet from mininet.node import Host, CPULimitedHost, Controller, OVSController, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch from mininet.link import Intf, TCIntf @@ -32,12 +32,13 @@ def customNode( constructors, argStr ): "Return custom Node constructor based on argStr" cname, newargs, kwargs = splitArgs( argStr ) constructor = constructors.get( cname, None ) - #if args: - # raise Exception( "please specify keyword arguments for " + cname ) + if not constructor: raise Exception( "error: %s is unknown - please specify one of %s" % ( cname, constructors.keys() ) ) - def custom( name, *args, **params ): + + def customized( name, *args, **params ): + "Customized Node constructor" params.update( kwargs ) if not newargs: return constructor( name, *args, **params ) @@ -45,7 +46,8 @@ def customNode( constructors, argStr ): warn( 'warning: %s replacing %s with %s\n', constructor, args, newargs ) return constructor( name, *newargs, **params ) - return custom + + return customized # built in topologies, created only when run @@ -68,7 +70,7 @@ HOSTS = { 'proc': Host, CONTROLLERDEF = 'ref' CONTROLLERS = { 'ref': Controller, 'ovsc': OVSController, - 'nox': NOX, + 'nox': NOX, 'remote': RemoteController, 'none': lambda name: None } @@ -94,8 +96,7 @@ def splitArgs( argstr ): params = split[ 1: ] # Convert int and float args; removes the need for function # to be flexible with input arg formats. - args = [ s for s in params if '=' not in s ] - args = map( makeNumeric, args ) + args = [ makeNumeric( s ) for s in params if '=' not in s ] kwargs = {} for s in [ p for p in params if '=' in p ]: key, val = s.split( '=' ) @@ -122,7 +123,8 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ): raise Exception( 'Invalid default %s for choices dict: %s' % ( default, name ) ) if not helpStr: - helpStr = '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]' + helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + + '[,param=value...]' ) opts.add_option( '--' + name, type='string', default = default, @@ -157,11 +159,11 @@ class MininetRunner( object ): def parseCustomFile( self, fileName ): "Parse custom file and add params before parsing cmd-line options." - custom = {} + customs = {} if os.path.isfile( fileName ): - execfile( fileName, custom, custom ) - for name in custom: - self.setCustom( name, custom[ name ] ) + execfile( fileName, customs, customs ) + for name, val in customs.iteritems(): + self.setCustom( name, val ) else: raise Exception( 'could not find custom file: %s' % fileName ) @@ -171,8 +173,8 @@ class MininetRunner( object ): if '--custom' in sys.argv: index = sys.argv.index( '--custom' ) if len( sys.argv ) > index + 1: - custom = sys.argv[ index + 1 ] - self.parseCustomFile( custom ) + filename = sys.argv[ index + 1 ] + self.parseCustomFile( filename ) else: raise Exception( 'Custom file name not found' ) @@ -241,7 +243,7 @@ class MininetRunner( object ): start = time.time() topo = buildTopo( self.options.topo ) - switch = customNode( SWITCHES, self.options.switch ) + switch = customNode( SWITCHES, self.options.switch ) host = customNode( HOSTS, self.options.host ) controller = customNode( CONTROLLERS, self.options.controller ) intf = customNode( INTFS, self.options.intf ) @@ -250,6 +252,7 @@ class MininetRunner( object ): self.validate( self.options ) inNamespace = self.options.innamespace + Net = MininetWithControlNet if inNamespace else Mininet ipBase = self.options.ipbase xterms = self.options.xterms mac = self.options.mac @@ -257,13 +260,13 @@ class MininetRunner( object ): listenPort = None if not self.options.nolistenport: listenPort = self.options.listenport - mn = Mininet( topo=topo, - switch=switch, host=host, controller=controller, - intf=intf, - ipBase=ipBase, - inNamespace=inNamespace, - xterms=xterms, autoSetMacs=mac, - autoStaticArp=arp, listenPort=listenPort ) + mn = Net( topo=topo, + switch=switch, host=host, controller=controller, + intf=intf, + ipBase=ipBase, + inNamespace=inNamespace, + xterms=xterms, autoSetMacs=mac, + autoStaticArp=arp, listenPort=listenPort ) if self.options.pre: CLI( mn, script=self.options.pre ) diff --git a/examples/consoles.py b/examples/consoles.py index 5729454..2a0c195 100755 --- a/examples/consoles.py +++ b/examples/consoles.py @@ -107,7 +107,7 @@ class Console( Frame ): self.text.insert( 'end', text ) self.text.mark_set( 'insert', 'end' ) self.text.see( 'insert' ) - outputHook = lambda x,y: True # make pylint happy + outputHook = lambda x, y: True # make pylint happier if self.outputHook: outputHook = self.outputHook outputHook( self, text ) @@ -132,27 +132,22 @@ class Console( Frame ): self.sendCmd( cmd ) # Callback ignores event - # pylint: disable-msg=W0613 - def handleInt( self, event=None ): + def handleInt( self, _event=None ): "Handle control-c." self.node.sendInt() - # pylint: enable-msg=W0613 def sendCmd( self, cmd ): "Send a command to our node." if not self.node.waiting: self.node.sendCmd( cmd ) - # Callback ignores fds - # pylint: disable-msg=W0613 - def handleReadable( self, fds, timeoutms=None ): + def handleReadable( self, _fds, timeoutms=None ): "Handle file readable event." data = self.node.monitor( timeoutms ) self.append( data ) if not self.node.waiting: # Print prompt self.append( self.prompt ) - # pylint: enable-msg=W0613 def waiting( self ): "Are we waiting for output?" @@ -321,9 +316,7 @@ class ConsoleApp( Frame ): self.pack( expand=True, fill='both' ) - # Update callback doesn't use console arg - # pylint: disable-msg=W0613 - def updateGraph( self, console, output ): + def updateGraph( self, _console, output ): "Update our graph." m = re.search( r'(\d+) Mbits/sec', output ) if not m: @@ -334,7 +327,6 @@ class ConsoleApp( Frame ): self.graph.addBar( self.bw ) self.bw = 0 self.updates = 0 - # pylint: enable-msg=W0613 def setOutputHook( self, fn=None, consoles=None ): "Register fn as output hook [on specific consoles.]" diff --git a/examples/limit.py b/examples/limit.py index 801159a..4514514 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -13,10 +13,12 @@ from mininet.log import setLogLevel from time import sleep def testLinkLimit( net, bw ): + "Run bandwidth limit test" print '*** Testing network %.2f Mbps bandwidth limit' % bw net.iperf( ) def testCpuLimit( net, cpu ): + "run CPU limit test" pct = cpu * 100 print '*** Testing CPU %.0f%% bandwidth limit' % pct h1, h2 = net.hosts @@ -25,8 +27,9 @@ def testCpuLimit( net, cpu ): pid1 = h1.cmd( 'echo $!' ).strip() pid2 = h2.cmd( 'echo $!' ).strip() cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 ) - for i in range( 0, 5): - sleep( 1 ) + # It's a shame that this is what pylint prefers + for _ in range( 5 ): + sleep( 1 ) print quietRun( cmd ).strip() h1.cmd( 'kill %1') h2.cmd( 'kill %1') diff --git a/examples/miniedit.py b/examples/miniedit.py index 172a1f4..900b94a 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -299,14 +299,11 @@ class MiniEdit( Frame ): # Delete from view self.canvas.delete( item ) - # Callback ignores event - # pylint: disable-msg=W0613 - def deleteSelection( self, event ): + def deleteSelection( self, _event ): "Delete the selected item." if self.selection is not None: self.deleteItem( self.selection ) self.selectItem( None ) - # pylint: enable-msg=W0613 def nodeIcon( self, node, name ): "Create a new node icon." @@ -350,14 +347,11 @@ class MiniEdit( Frame ): c = self.canvas c.coords( self.link, self.linkx, self.linky, x, y ) - # Callback ignores event - # pylint: disable-msg=W0613 - def releaseLink( self, event ): + def releaseLink( self, _event ): "Give up on the current link." if self.link is not None: self.canvas.delete( self.link ) self.linkWidget = self.linkItem = self.link = None - # pylint: enable-msg=W0613 # Generic node handlers @@ -385,12 +379,9 @@ class MiniEdit( Frame ): "Select node on entry." self.selectNode( event ) - # Callback ignores event - # pylint: disable-msg=W0613 - def leaveNode( self, event ): + def leaveNode( self, _event ): "Restore old selection on exit." self.selectItem( self.lastSelection ) - # pylint: enable-msg=W0613 def clickNode( self, event ): "Node click handler." @@ -454,23 +445,21 @@ class MiniEdit( Frame ): # Link bindings # Selection still needs a bit of work overall # Callbacks ignore event - # pylint: disable-msg=W0613 - def select( event, link=self.link ): + def select( _event, link=self.link ): "Select item on mouse entry." self.selectItem( link ) - def highlight( event, link=self.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 ): + def unhighlight( _event, link=self.link ): "Unhighlight item on mouse exit." self.canvas.itemconfig( link, fill='blue' ) # self.selectItem( None ) - # pylint: disable-msg=W0613 self.canvas.tag_bind( self.link, '', highlight ) self.canvas.tag_bind( self.link, '', unhighlight ) self.canvas.tag_bind( self.link, '', select ) @@ -602,7 +591,7 @@ class MiniEdit( Frame ): cleanUpScreens() self.net = None - def xterm( self, ignore=None ): + def xterm( self, _=None ): "Make an xterm when a button is pressed." if ( self.selection is None or self.net is None or diff --git a/examples/scratchnetuser.py b/examples/scratchnetuser.py index 59bc601..4b8b9fd 100755 --- a/examples/scratchnetuser.py +++ b/examples/scratchnetuser.py @@ -52,7 +52,7 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ): info( '*** Starting controller and user datapath\n' ) controller.cmd( cname + ' ' + cargs + '&' ) switch.cmd( 'ifconfig lo 127.0.0.1' ) - intfs = map( str, [ sintf1, sintf2 ] ) + intfs = [ str( i ) for i in sintf1, sintf2 ] switch.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' ptcp: &' ) switch.cmd( 'ofprotocol tcp:' + controller.IP() + ' tcp:localhost &' ) diff --git a/mininet/cli.py b/mininet/cli.py index 3fbedd1..53cd0c0 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -77,7 +77,7 @@ class CLI( Cmd ): # Disable pylint "Unused argument: 'arg's'" messages, as well as # "method could be a function" warning, since each CLI function # must have the same interface - # pylint: disable-msg=W0613,R0201 + # pylint: disable-msg=R0201 helpStr = ( 'You may also send a command to a node using:\n' @@ -104,12 +104,12 @@ class CLI( Cmd ): if line is '': output( self.helpStr ) - def do_nodes( self, line ): + def do_nodes( self, _line ): "List all nodes." nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] ) output( 'available nodes are: \n%s\n' % nodes ) - def do_net( self, line ): + def do_net( self, _line ): "List network connections." for switch in self.mn.switches: output( switch.name, '<->' ) @@ -143,11 +143,11 @@ class CLI( Cmd ): # pylint: enable-msg=W0703 - def do_pingall( self, line ): + def do_pingall( self, _line ): "Ping between all hosts." self.mn.pingAll() - def do_pingpair( self, line ): + def do_pingpair( self, _line ): "Ping between first two hosts, useful for testing." self.mn.pingPair() @@ -191,13 +191,13 @@ class CLI( Cmd ): error( 'invalid number of args: iperfudp bw src dst\n' + 'bw examples: 10M\n' ) - def do_intfs( self, line ): + def do_intfs( self, _line ): "List interfaces." for node in self.nodelist: output( '%s: %s\n' % ( node.name, ' '.join( sorted( node.intfs.values() ) ) ) ) - def do_dump( self, line ): + def do_dump( self, _line ): "Dump node info." for node in self.nodelist: output( '%s\n' % node ) @@ -229,7 +229,7 @@ class CLI( Cmd ): "Spawn gnome-terminal(s) for the given node(s)." self.do_xterm( line, term='gterm' ) - def do_exit( self, line ): + def do_exit( self, _line ): "Exit" return 'exited by user command' @@ -311,7 +311,7 @@ class CLI( Cmd ): else: error( '*** Unknown command: %s\n' % first ) - # pylint: enable-msg=W0613,R0201 + # pylint: enable-msg=R0201 def waitForNode( self, node ): "Wait for a node to finish, and print its output." diff --git a/mininet/link.py b/mininet/link.py index 7d606f9..c07b040 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -41,30 +41,35 @@ class Intf( object ): self.node = node self.name = name self.link = link - self.mac, self.ip = None, None + self.mac, self.ip, self.prefixLen = None, None, None # Add to node (and move ourselves if necessary ) node.addIntf( self ) self.config( **kwargs ) def cmd( self, *args, **kwargs ): + "Run a command in our owning node" return self.node.cmd( *args, **kwargs ) def ifconfig( self, *args ): "Configure ourselves using ifconfig" return self.cmd( 'ifconfig', self.name, *args ) - def setIP( self, ipstr ): + def setIP( self, ipstr, prefixLen=None ): """Set our IP address""" # This is a sign that we should perhaps rethink our prefix - # mechanism - self.ip, self.prefixLen = ipstr.split( '/' ) - return self.ifconfig( ipstr, 'up' ) + # mechanism and/or the way we specify IP addresses + if '/' in ipstr: + self.ip, self.prefixLen = ipstr.split( '/' ) + return self.ifconfig( ipstr, 'up' ) + else: + self.ip, self.prefixLen = ipstr, prefixLen + return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) ) def setMAC( self, macstr ): """Set the MAC address for an interface. macstr: MAC address as string""" self.mac = macstr - return ( self.ifconfig( 'down' ) + + return ( self.ifconfig( 'down' ) + self.ifconfig( 'hw', 'ether', macstr ) + self.ifconfig( 'up' ) ) @@ -78,13 +83,13 @@ class Intf( object ): self.ip = ips[ 0 ] if ips else None return self.ip - def updateMAC( self, intf ): + def updateMAC( self ): "Return updated MAC address based on ifconfig" ifconfig = self.ifconfig() macs = self._macMatchRegex.findall( ifconfig ) self.mac = macs[ 0 ] if macs else None return self.mac - + def IP( self ): "Return IP address" return self.ip @@ -93,9 +98,9 @@ class Intf( object ): "Return MAC address" return self.mac - def isUp( self, set=False ): + def isUp( self, setUp=False ): "Return whether interface is up" - if set: + if setUp: self.ifconfig( 'up' ) return "UP" in self.ifconfig() @@ -124,8 +129,8 @@ class Intf( object ): results[ name ] = result return result - def config( self, mac=None, ip=None, ifconfig=None, - defaultRoute=None, up=True, **params): + def config( self, mac=None, ip=None, ifconfig=None, + up=True, **_params ): """Configure Node according to (optional) parameters: mac: MAC address ip: IP address @@ -153,7 +158,83 @@ class Intf( object ): class TCIntf( Intf ): - "Interface customized by tc (traffic control) utility" + """Interface customized by tc (traffic control) utility + Allows specification of bandwidth limits (various methods) + as well as delay, loss and max queue length""" + + def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False, + enable_ecn=False, enable_red=False ): + "Return tc commands to set bandwidth" + + cmds, parent = [], ' root ' + + if bw and ( bw < 0 or bw > 1000 ): + error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' ) + + elif bw is not None: + # BL: this seems a bit brittle... + if ( speedup > 0 and + self.node.name[0:2] == 'sw' ): + bw = speedup + if use_hfsc: + cmds = [ '%s qdisc add dev %s root handle 1:0 hfsc default 1', + 'class add dev %s parent 1:0 classid 1:1 hfsc sc ' + + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] + elif use_tbf: + latency_us = 10 * 1500 * 8 / bw + cmds = ['%s qdisc add dev %s root handle 1: tbf ' + + 'rate %fMbit burst 15000 latency %fus' % + (bw, latency_us) ] + else: + cmds = [ '%s qdisc add dev %s root handle 1:0 htb default 1', + '%s class add dev %s parent 1:0 classid 1:1 htb ' + + 'rate %fMbit burst 15k' % bw ] + parent = ' parent 1:1 ' + + # ECN or RED + if enable_ecn: + cmds = [ '%s qdisc add dev %s' + parent + + 'handle 10: red limit 1000000 ' + + 'min 20000 max 25000 avpkt 1000 ' + + 'burst 20 ' + + 'bandwidth %fmbit probability 1 ecn' % bw ] + parent = ' parent 10: ' + elif enable_red: + cmds = [ '%s qdisc add dev %s' + parent + + 'handle 10: red limit 1000000 ' + + 'min 20000 max 25000 avpkt 1000 ' + + 'burst 20 ' + + 'bandwidth %fmbit probability 1' % bw ] + parent = ' parent 10: ' + + return cmds, parent + + @staticmethod + def delayCmds( parent, delay=None, loss=None, + max_queue_size=None ): + "Internal method: return tc commands for delay and loss" + cmds = [] + if delay and delay < 0: + error( 'Negative delay', delay, '\n' ) + elif loss and ( loss < 0 or loss > 100 ): + error( 'Bad loss percentage', loss, '%%\n' ) + else: + # Delay/loss/max queue size + netemargs = '%s%s%s' % ( + 'delay %s ' % delay if delay is not None else '', + 'loss %d ' % loss if loss is not None else '', + 'limit %d' % max_queue_size if max_queue_size is not None + else '' ) + if netemargs: + cmds = [ '%s qdisc add dev %s ' + parent + ' netem ' + + netemargs ] + return cmds + + def tc( self, cmd, tc='tc' ): + "Execute tc command for our interface" + c = cmd % (tc, self) # Add in tc command and our name + debug(" *** executing command: %s\n" % c) + return self.cmd( c ) def config( self, bw=None, delay=None, loss=None, disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False, @@ -162,106 +243,58 @@ class TCIntf( Intf ): result = Intf.config( self, **params) - # disable GRO + # Disable GRO if disable_gro: self.cmd( 'ethtool -K %s gro off' % self ) - - if ( bw is None and not delay and not loss + + # Optimization: return if nothing else to configure + # Question: what happens if we want to reset things? + if ( bw is None and not delay and not loss and max_queue_size is None ): return - if bw and ( bw < 0 or bw > 1000 ): - error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' ) - return - - if delay and delay < 0: - error( 'Negative delay', delay, '\n' ) - return + # Clear existing configuration + cmds = [ '%s qdisc del dev %s root' ] - if loss and ( loss < 0 or loss > 100 ): - error( 'Bad loss percentage', loss, '%%\n' ) - return + # Bandwidth limits via various methods + bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup, + use_hfsc=use_hfsc, use_tbf=use_tbf, + enable_ecn=enable_ecn, + enable_red=enable_red ) + cmds += bwcmds - # Ugly but functional + # Delay/loss/max_queue_size using netem + cmds += self.delayCmds( delay=delay, loss=loss, + max_queue_size=max_queue_size, + parent=parent ) + + # Ugly but functional: display configuration info stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + ( [ '%s delay' % delay ] if delay is not None else [] ) + ( ['%d%% loss' % loss ] if loss is not None else [] ) + - ( [ 'ECN' ] if enable_ecn else [ 'RED' ] if enable_red else [] ) ) + ( [ 'ECN' ] if enable_ecn else [ 'RED' ] + if enable_red else [] ) ) info( '(' + ' '.join( stuff ) + ') ' ) - cmds = [ '%s qdisc del dev %s root' ] - - tc = 'tc' # was getCmd( 'tc' ) - - # Bandwidth control algorithms - if bw is None: - parent = ' root ' - else: - parent = ' parent 1:1 ' - # BL: hmm... this seems a bit brittle - if speedup > 0 and self.node.name[0:2] == 'sw': - bw = speedup - if use_hfsc: - cmds += [ '%s qdisc add dev %s root handle 1:0 hfsc default 1', - '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' + - 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] - elif use_tbf: - latency_us = 10 * 1500 * 8 / bw - cmds += ['%s qdisc add dev %s root handle 1: tbf ' + - 'rate %fMbit burst 15000 latency %fus' % (bw, latency_us) ] - else: - cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1', - '%s class add dev %s parent 1:0 classid 1:1 htb ' + - 'rate %fMbit burst 15k' % bw ] - parent = ' parent 1:1 ' - - # ECN or RED - if enable_ecn: - cmds += [ '%s qdisc add dev %s' + parent + - 'handle 10: red limit 1000000 '+ - 'min 20000 max 25000 avpkt 1000 '+ - 'burst 20 '+ - 'bandwidth %fmbit probability 1 ecn' % bw ] - parent = ' parent 10: ' - elif enable_red: - cmds += [ '%s qdisc add dev %s' + parent + - 'handle 10: red limit 1000000 '+ - 'min 20000 max 25000 avpkt 1000 '+ - 'burst 20 '+ - 'bandwidth %fmbit probability 1' % bw ] - parent = ' parent 10: ' - - # Delay/loss/max queue size - netemargs = '%s%s%s' % ( - 'delay %s ' % delay if delay is not None else '', - 'loss %d ' % loss if loss is not None else '', - 'limit %d' % max_queue_size if max_queue_size is not None else '' ) - if netemargs: - cmds += [ '%s qdisc add dev %s ' + parent + ' netem ' + - netemargs ] - - # Execute all the commands in the container + # Execute all the commands in our node debug("at map stage w/cmds: %s\n" % cmds) - - def doConfigPort(s): - c = s % (tc, self) - debug(" *** executing command: %s\n" % c) - return self.cmd(c) - - tcoutputs = [ doConfigPort(cmd) for cmd in cmds ] + tcoutputs = [ self.tc(cmd) for cmd in cmds ] debug( "cmds:", cmds, '\n' ) debug( "outputs:", tcoutputs, '\n' ) result[ 'tcoutputs'] = tcoutputs + return result class Link( object ): - + """A basic link is just a veth pair. Other types of links could be tunnels, link emulators, etc..""" - def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None, - intf=Intf, cls1=None, cls2=None, params1={}, params2={} ): + def __init__( self, node1, node2, port1=None, port2=None, + intfName1=None, intfName2=None, + intf=Intf, cls1=None, cls2=None, params1=None, + params2=None ): """Create veth link to another node, making two new interfaces. node1: first node node2: second node @@ -284,13 +317,21 @@ class Link( object ): intfName1 = self.intfName( node1, port1 ) if not intfName2: intfName2 = self.intfName( node2, port2 ) + self.makeIntfPair( intfName1, intfName2 ) + if not cls1: cls1 = intf if not cls2: cls2 = intf + if not params1: + params1 = {} + if not params2: + params2 = {} + intf1 = cls1( name=intfName1, node=node1, link=self, **params1 ) intf2 = cls2( name=intfName2, node=node2, link=self, **params2 ) + # All we are is dust in the wind, and our two interfaces self.intf1, self.intf2 = intf1, intf2 @@ -304,7 +345,8 @@ class Link( object ): """Create pair of interfaces intf1: name of interface 1 intf2: name of interface 2 - (override this class method [and possibly delete()] to change link type)""" + (override this class method [and possibly delete()] + to change link type)""" makeIntfPair( intf1, intf2 ) def delete( self ): diff --git a/mininet/net.py b/mininet/net.py index 9e1e161..7384700 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -104,7 +104,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=None, + controller=Controller, link=Link, intf=None, build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', inNamespace=False, autoSetMacs=False, autoStaticArp=False, listenPort=None ): @@ -176,7 +176,7 @@ class Mininet( object ): switch: custom switch constructor (optional) returns: added switch side effect: increments listenPort ivar .""" - defaults = { 'listenPort': self.listenPort, + defaults = { 'listenPort': self.listenPort, 'inNamespace': self.inNamespace } defaults.update( params ) if not switch: @@ -229,7 +229,7 @@ class Mininet( object ): ipBaseNum=ipBaseNum, prefixLen=prefixLen ) } if self.autoSetMacs: - defaults[ 'mac'] = macColonHex( nodeId ) + defaults[ 'mac'] = macColonHex( nodeId ) defaults.update( ni.params ) node = addMethod( name, cls=ni.cls, **defaults ) self.idToNode[ nodeId ] = node @@ -275,17 +275,16 @@ class Mininet( object ): info( '\n' ) - def configureControlNetwork( self ): - error( "configureControlNetwork: override in subclass, or use" - "MininetWithControlNet class" ) + "Control net config hook: override in subclass" + raise Exception( 'configureControlNetwork: ' + 'should be overriden in subclass', self ) def build( self ): "Build mininet." if self.topo: self.buildFromTopo( self.topo ) - if self.inNamespace: - info( '*** Configuring control network\n' ) + if ( self.inNamespace ): self.configureControlNetwork() info( '*** Configuring hosts\n' ) self.configHosts() @@ -533,7 +532,7 @@ class Mininet( object ): return result inited = False - + @classmethod def init( cls ): "Initialize Mininet" @@ -541,7 +540,8 @@ class Mininet( object ): return if os.getuid() != 0: # Note: this script must be run as root - # Perhaps we should do so automatically! + # Probably we should only sudo when we need + # to as per Big Switch's patch print "*** Mininet must run as root." exit( 1 ) fixLimits() @@ -570,7 +570,11 @@ class MininetWithControlNet( Mininet ): network (since real networks may need one!) 5. Basically nobody ever used this code, so it has been moved - into its own class.""" + into its own class. + + 6. Ultimately we may wish to extend this to allow us to create a + control network which every node's control interface is + attached to.""" def configureControlNetwork( self ): "Configure control network." @@ -589,27 +593,27 @@ class MininetWithControlNet( Mininet ): snum = ipParse( ip ) for switch in self.switches: info( ' ' + switch.name ) - sintf, cintf = self.link( switch, controller ) + link = self.link( switch, controller, port1=0 ) + sintf, cintf = link.intf1, link.intf2 + switch.controlIntf = sintf snum += 1 while snum & 0xff in [ 0, 255 ]: snum += 1 sip = ipStr( snum ) - controller.setIP( cintf, cip, prefixLen ) - switch.setIP( sintf, sip, prefixLen ) + cintf.setIP( cip, prefixLen ) + sintf.setIP( sip, prefixLen ) controller.setHostRoute( sip, cintf ) switch.setHostRoute( cip, sintf ) info( '\n' ) info( '*** Testing control network\n' ) - while not controller.intfIsUp( cintf ): + while not cintf.isUp(): info( '*** Waiting for', cintf, 'to come up\n' ) sleep( 1 ) for switch in self.switches: - while not switch.intfIsUp( sintf ): + while not sintf.isUp(): info( '*** Waiting for', sintf, 'to come up\n' ) sleep( 1 ) if self.ping( hosts=[ switch, controller ] ) != 0: error( '*** Error: control network test failed\n' ) exit( 1 ) info( '\n' ) - - diff --git a/mininet/node.py b/mininet/node.py index cbe2917..304a67a 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -81,8 +81,14 @@ class Node( object ): # replace with Port objects, eventually ? self.nameToIntf = {} # dict of interface names to Intfs + # Make pylint happy + ( self.shell, self.execed, self.pid, self.stdin, self.stdout, + self.lastPid, self.lastCmd, self.pollOut ) = ( + None, None, None, None, None, None, None, None ) + self.waiting = False + self.readbuf = '' + # Start command interpreter shell - self.shell = None self.startShell() # File descriptor to node mapping support @@ -99,28 +105,6 @@ class Node( object ): node = cls.outToNode.get( fd ) return node or cls.inToNode.get( fd ) - # Automatic class setup support - - isSetup = False; - - @classmethod - def checkSetup( cls ): - "Make sure our class and superclasses are set up" - while cls and not getattr( cls, 'isSetup', True ): - cls.setup() - cls.isSetup = True - # Make pylint happy - cls = getattr( type( cls ), '__base__', None ) - - @classmethod - def setup( cls ): - "Make sure our class dependencies are available" - pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet') - - def cleanup( self ): - "Help python collect its garbage." - self.shell = None - # Command support via shell process in namespace def startShell( self ): @@ -129,7 +113,7 @@ class Node( object ): error( "%s: shell is already running" ) return # mnexec: (c)lose descriptors, (d)etach from tty, - # (p)rint pid, and run in (n)amespace + # (p)rint pid, and run in (n)amespace opts = '-cdp' if self.inNamespace: opts += 'n' @@ -153,19 +137,23 @@ class Node( object ): self.readbuf = '' self.waiting = False - def read( self, bytes=1024 ): + def cleanup( self ): + "Help python collect its garbage." + self.shell = None + + def read( self, maxbytes=1024 ): """Buffered read from node, non-blocking. - bytes: maximum number of bytes to return""" + maxbytes: maximum number of bytes to return""" count = len( self.readbuf ) - if count < bytes: - data = os.read( self.stdout.fileno(), bytes - count ) + if count < maxbytes: + data = os.read( self.stdout.fileno(), maxbytes - count ) self.readbuf += data - if bytes >= len( self.readbuf ): + if maxbytes >= len( self.readbuf ): result = self.readbuf self.readbuf = '' else: - result = self.readbuf[ :bytes ] - self.readbuf = self.readbuf[ bytes: ] + result = self.readbuf[ :maxbytes ] + self.readbuf = self.readbuf[ maxbytes: ] return result def readline( self ): @@ -307,7 +295,7 @@ class Node( object ): self.ports[ intf ] = port self.nameToIntf[ intf.name ] = intf debug( '\n' ) - debug( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) + debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) ) if self.inNamespace: debug( 'moving', intf, 'into namespace for', self.name, '\n' ) moveIntf( intf.name, self ) @@ -363,7 +351,7 @@ class Node( object ): """Add route to host. ip: IP address as dotted decimal intf: string, interface name""" - return self.cmd( 'route add -host ' + ip + ' dev ' + intf ) + return self.cmd( 'route add -host', ip, 'dev', intf ) def setDefaultRoute( self, intf=None ): """Set the default route to go through intf. @@ -430,8 +418,8 @@ class Node( object ): results[ name ] = result return result - def config( self, mac=None, ip=None, ifconfig=None, - defaultRoute=None, **params): + def config( self, mac=None, ip=None, ifconfig=None, + defaultRoute=None, **_params ): """Configure Node according to (optional) parameters: mac: MAC address for default interface ip: IP address for default interface @@ -440,7 +428,7 @@ class Node( object ): the parent class's config(**params)""" # If we were overriding this method, we would call # the superclass config method here as follows: - # r = Parent.config( **params ) + # r = Parent.config( **_params ) r = {} self.setParam( r, 'setMAC', mac=mac ) self.setParam( r, 'setIP', ip=ip ) @@ -473,6 +461,24 @@ class Node( object ): return '%s: IP=%s intfs=%s pid=%s' % ( self.name, self.IP(), ','.join( self.intfNames() ), self.pid ) + # Automatic class setup support + + isSetup = False + + @classmethod + def checkSetup( cls ): + "Make sure our class and superclasses are set up" + while cls and not getattr( cls, 'isSetup', True ): + cls.setup() + cls.isSetup = True + # Make pylint happy + cls = getattr( type( cls ), '__base__', None ) + + @classmethod + def setup( cls ): + "Make sure our class dependencies are available" + pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet') + class Host( Node ): "A host is simply a Node" @@ -484,7 +490,7 @@ class CPULimitedHost( Host ): "CPU limited host" def __init__( self, *args, **kwargs ): - Node.__init__( self, *args, **kwargs ) + Host.__init__( self, *args, **kwargs ) # Create a cgroup and move shell into it self.cgroup = 'cpu,cpuacct:/' + self.name errFail( 'cgcreate -g ' + self.cgroup ) @@ -510,6 +516,7 @@ class CPULimitedHost( Host ): return nvalue def cgroupGet( self, param, resource='cpu' ): + "Return value of cgroup parameter" cmd = 'cgget -r %s.%s /%s' % ( resource, param, self.name ) return quietRun( cmd ).split()[ -1 ] @@ -544,7 +551,7 @@ class CPULimitedHost( Host ): return pstr, qstr, period, quota # BL comment: - # This may not be the right API, + # This may not be the right API, # since it doesn't specify CPU bandwidth in "absolute" # units the way link bandwidth is specified. # We should use MIPS or SPECINT or something instead. @@ -578,7 +585,7 @@ class CPULimitedHost( Host ): self.chrt( prio=20 ) info( '(%s %d/%dus) ' % ( sched, quota, period ) ) - def config( self, cpu=None, sched=None, **params ): + def config( self, cpu=None, **params ): """cpu: desired overall system CPU fraction params: parameters for Node.config()""" r = Node.config( self, **params ) @@ -665,8 +672,8 @@ class UserSwitch( Switch ): pathCheck( 'ofdatapath', 'ofprotocol', moduleName='the OpenFlow reference user switch (openflow.org)' ) - @staticmethod - def setup(): + @classmethod + def setup( cls ): "Ensure any dependencies are loaded; if not, try to load them." if not os.path.exists( '/dev/net/tun' ): moduleDeps( add=TUN ) @@ -684,7 +691,7 @@ class UserSwitch( Switch ): if self.inNamespace: intfs = intfs[ :-1 ] self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + - ' punix:/tmp/' + self.name + ' -d ' + self.dpid + + ' punix:/tmp/' + self.name + ' -d ' + self.dpid + ' --no-slicing ' + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) self.cmd( 'ofprotocol unix:/tmp/' + self.name + @@ -716,8 +723,8 @@ class OVSLegacyKernelSwitch( Switch ): " in the root namespace.\n" ) exit( 1 ) - @staticmethod - def setup(): + @classmethod + def setup( cls ): "Ensure any dependencies are loaded; if not, try to load them." pathCheck( 'ovs-dpctl', 'ovs-openflowd', moduleName='Open vSwitch (openvswitch.org)') @@ -741,7 +748,7 @@ class OVSLegacyKernelSwitch( Switch ): controller = controllers[ 0 ] self.cmd( 'ovs-openflowd ' + self.dp + ' tcp:%s:%d' % ( controller.IP(), controller.port ) + - ' --fail=secure ' + self.opts + + ' --fail=secure ' + self.opts + ' --datapath-id=' + self.dpid + ' 1>' + ofplog + ' 2>' + ofplog + '&' ) self.execed = False @@ -766,26 +773,34 @@ class OVSSwitch( Switch ): # dpid, which is a 64-bit numerical value used by # the openflow protocol. self.dp = name - - @staticmethod - def setup(): + if self.inNamespace: + error( "OVSSwitch currently only works" + " in the root namespace.\n" ) + exit( 1 ) + + @classmethod + def setup( cls ): "Make sure Open vSwitch is installed and working" - pathCheck( 'ovs-vsctl', + pathCheck( 'ovs-vsctl', moduleName='Open vSwitch (openvswitch.org)') moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' ) if exitcode: - error( out + err + + error( out + err + 'ovs-vsctl exited with code %d\n' % exitcode + '*** Error connecting to ovs-db with ovs-vsctl\n' 'Make sure that Open vSwitch is installed, ' 'that ovsdb-server is running, and that\n' '"ovs-vsctl show" works correctly.\n' - 'You may wish to try "service openvswitch-switch start".\n' ) + 'You may wish to try ' + '"service openvswitch-switch start".\n' ) exit( 1 ) def start( self, controllers ): "Start up a new OVS OpenFlow switch using ovs-vsctl" + if self.inNamespace: + raise Exception( + 'OVS kernel switch does not work in a namespace' ) # Annoyingly, --if-exists option seems not to work self.cmd( 'ovs-vsctl del-br ', self.dp ) self.cmd( 'ovs-vsctl add-br', self.dp ) @@ -800,7 +815,8 @@ class OVSSwitch( Switch ): self.cmd( 'ovs-vsctl add-port', self.dp, intf ) self.cmd( 'ifconfig', intf, 'up' ) # Add controllers - clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) for c in controllers ] ) + clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) + for c in controllers ] ) self.cmd( 'ovs-vsctl set-controller', self.dp, clist ) def stop( self ): diff --git a/mininet/topo.py b/mininet/topo.py index 6b4e572..3de788c 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,7 +16,7 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph -from util import netParse, ipStr +from mininet.util import netParse, ipStr class NodeID(object): '''Topo node identifier.''' @@ -116,7 +116,7 @@ class Topo(object): per-node/link classes and parameters per-topo classes per-network classes""" - + def __init__(self, node=None, switch=None, link=None ): """Create Topo object. node: default node/host class (optional) @@ -364,7 +364,7 @@ class Topo(object): # BL: may wish to rethink this or just use dicts.. return self.node_info[ dpid ] - + class SingleSwitchTopo(Topo): '''Single switch connected to k hosts.''' diff --git a/mininet/util.py b/mininet/util.py index 79b4a12..450472a 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -47,10 +47,11 @@ def oldQuietRun( *cmd ): break return out + # This is a bit complicated, but it enables us to # monitor commount output as it is happening -def errRun( *cmd, **kwargs ): +def errRun( *cmd, **kwargs ): """Run a command and return stdout, stderr and return code cmd: string or list of command and args stderr: STDOUT to merge stderr with stdout @@ -80,7 +81,10 @@ def errRun( *cmd, **kwargs ): poller.register( popen.stderr, POLLIN ) while True: readable = poller.poll() + # Tell pylint to ignore unused variable event + # pylint: disable-msg=W0612 for fd, event in readable: + # pylint: enable-msg=W0612 f = fdtofile[ fd ] data = f.read( 1024 ) if echo: @@ -91,7 +95,7 @@ def errRun( *cmd, **kwargs ): err += data returncode = popen.poll() if returncode is not None: - break + break return out, err, returncode def errFail( *cmd, **kwargs ): @@ -186,13 +190,13 @@ def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ): # IP and Mac address formatting and parsing -def _colonHex( val, bytes ): +def _colonHex( val, bytecount ): """Generate colon-hex string. val: input as unsigned int - bytes: number of bytes to convert + bytescount: number of bytes to convert returns: chStr colon-hex string""" pieces = [] - for i in range( bytes - 1, -1, -1 ): + for i in range( bytecount - 1, -1, -1 ): piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 ) pieces.append( '%02x' % piece ) chStr = ':'.join( pieces ) @@ -204,14 +208,14 @@ def macColonHex( mac ): returns: macStr MAC colon-hex string""" return _colonHex( mac, 6 ) -def ipStr( ip, defaultNet=10 ): +def ipStr( ip ): """Generate IP address string from an unsigned int. ip: unsigned int of form w << 24 | x << 16 | y << 8 | z returns: ip address string w.x.y.z, or 10.x.y.z if w==0""" w = ( ip >> 24 ) & 0xff w = 10 if w == 0 else w - x = ( ip >> 16 ) & 0xff - y = ( ip >> 8 ) & 0xff + x = ( ip >> 16 ) & 0xff + y = ( ip >> 8 ) & 0xff z = ip & 0xff return "%i.%i.%i.%i" % ( w, x, y, z ) @@ -270,6 +274,7 @@ def fixLimits(): def natural( text ): "To sort sanely/alphabetically: sorted( l, key=natural )" def num( s ): + "Convert text segment to int if necessary" return int( s ) if s.isdigit() else text return [ num( s ) for s in re.split( r'(\d+)', text ) ] @@ -286,8 +291,7 @@ def numCores(): def custom( cls, **params ): "Returns customized constructor for class cls." def customized( *args, **kwargs): + "Customized constructor" kwargs.update( params ) return cls( *args, **kwargs ) return customized - - From 8856d284c0b58e1db0b5e809405850b7dd56bb44 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 11 Mar 2012 19:44:04 -0700 Subject: [PATCH 058/250] Fix CLI commands. --- bin/mn | 2 +- mininet/cli.py | 33 +++++++------ mininet/link.py | 10 ++++ mininet/net.py | 20 ++++---- mininet/node.py | 124 +++++++++++++++++++++++++++++++----------------- 5 files changed, 122 insertions(+), 67 deletions(-) diff --git a/bin/mn b/bin/mn index a07833a..b82ba12 100755 --- a/bin/mn +++ b/bin/mn @@ -208,7 +208,7 @@ class MininetRunner( object ): 'remote controller' ) opts.add_option( '--innamespace', action='store_true', default=False, help='sw and ctrl in namespace?' ) - opts.add_option( '--listenport', type='int', default=6634, + opts.add_option( '--listenport', type='int', default=6635, help='base port for passive switch listening' ) opts.add_option( '--nolistenport', action='store_true', default=False, help="don't use passive listening port") diff --git a/mininet/cli.py b/mininet/cli.py index 53cd0c0..058a330 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -109,15 +109,20 @@ class CLI( Cmd ): nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] ) output( 'available nodes are: \n%s\n' % nodes ) + @staticmethod + def dump_connections( node ): + "Helper method: dump connections to node" + for intf in node.intfList(): + if intf.link: + intfs = [ intf.link.intf1, intf.link.intf2 ] + intfs.remove( intf ) + output( ' %s' % intfs[ 0 ].node ) + def do_net( self, _line ): "List network connections." - for switch in self.mn.switches: - output( switch.name, '<->' ) - for intf in switch.intfs.values(): - # Ugly, but pylint wants it - name = switch.connection.get( intf, - ( None, 'Unknown ' ) )[ 1 ] - output( ' %s' % name ) + for node in self.nodelist: + output( node.name, '<->' ) + self.dump_connections( node ) output( '\n' ) def do_sh( self, line ): @@ -195,12 +200,12 @@ class CLI( Cmd ): "List interfaces." for node in self.nodelist: output( '%s: %s\n' % - ( node.name, ' '.join( sorted( node.intfs.values() ) ) ) ) + ( node.name, ','.join( node.intfNames() ) ) ) def do_dump( self, _line ): "Dump node info." for node in self.nodelist: - output( '%s\n' % node ) + output( '%s\n' % repr( node ) ) def do_link( self, line ): "Bring link(s) between two nodes up or down." @@ -275,16 +280,12 @@ class CLI( Cmd ): def do_dpctl( self, line ): "Run dpctl command on all switches." args = line.split() - if len(args) == 0: + if len(args) < 1: error( 'usage: dpctl command [arg1] [arg2] ...\n' ) return - if not self.mn.listenPort: - error( "can't run dpctl w/no passive listening port\n") - return for sw in self.mn.switches: output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' ) - output( sw.cmd( 'dpctl ' + ' '.join(args) + - ' tcp:127.0.0.1:%i' % sw.listenPort ) ) + output( sw.dpctl( *args ) ) def default( self, line ): """Called on an input line when the command prefix is not recognized. @@ -293,6 +294,8 @@ class CLI( Cmd ): corresponding IP addrs.""" first, args, line = self.parseline( line ) + if not args: + return if args and len(args) > 0 and args[ -1 ] == '\n': args = args[ :-1 ] rest = args.split( ' ' ) diff --git a/mininet/link.py b/mininet/link.py index c07b040..e0bd36e 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -104,6 +104,14 @@ class Intf( object ): self.ifconfig( 'up' ) return "UP" in self.ifconfig() + def rename( self, newname ): + "Rename interface" + self.ifconfig( 'down' ) + result = self.cmd( 'ip link set', self.name, 'name', newname ) + self.name = newname + self.ifconfig( 'up' ) + return result + # The reason why we configure things in this way is so # That the parameters can be listed and documented in # the config method. @@ -145,6 +153,8 @@ class Intf( object ): self.setParam( r, 'setIP', ip=ip ) self.setParam( r, 'isUp', up=up ) self.setParam( r, 'ifconfig', ifconfig=ifconfig ) + self.updateIP() + self.updateMAC() return r def delete( self ): diff --git a/mininet/net.py b/mininet/net.py index 7384700..be93247 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -484,10 +484,11 @@ class Mininet( object ): servout = '' while server.lastPid is None: servout += server.monitor() - while 'Connected' not in client.cmd( - 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()): - output('waiting for iperf to start up...') - sleep(.5) + 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...') + sleep(.5) cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' + bwArgs ) debug( 'Client output: %s\n' % cliout ) @@ -512,15 +513,18 @@ class Mininet( object ): elif dst not in self.nameToNode: error( 'dst not in network: %s\n' % dst ) else: - srcNode, dstNode = self.nameToNode[ src ], self.nameToNode[ dst ] - connections = srcNode.connectionsTo( dstNode ) + if type( src ) is str: + src = self.nameToNode[ src ] + if type( dst ) is str: + dst = self.nameToNode[ dst ] + connections = src.connectionsTo( dst ) if len( connections ) == 0: error( 'src and dst not connected: %s %s\n' % ( src, dst) ) for srcIntf, dstIntf in connections: - result = srcNode.cmd( 'ifconfig', srcIntf, status ) + result = srcIntf.ifconfig( status ) if result: error( 'link src status change failed: %s\n' % result ) - result = dstNode.cmd( 'ifconfig', dstIntf, status ) + result = dstIntf.ifconfig( status ) if result: error( 'link dst status change failed: %s\n' % result ) diff --git a/mininet/node.py b/mininet/node.py index 304a67a..b1b8164 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -54,7 +54,7 @@ from mininet.log import info, error, warn, debug from mininet.util import quietRun, errRun, errFail, moveIntf, isShellBuiltin from mininet.util import numCores from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN -from mininet.link import Link +from mininet.link import Link, Intf class Node( object ): """A virtual network node is simply a shell in a network namespace. @@ -316,16 +316,19 @@ class Node( object ): else: return intf - def linksTo( self, node): - "Return [ link1, link2...] for all links from self to node." + def connectionsTo( self, node): + "Return [ intf1, intf2... ] for all intfs that connect self to node." # We could optimize this if it is important - links = [] - for intf in self.intfs: + connections = [] + for intf in self.intfList(): link = intf.link - nodes = ( link.intf1.node, link.intf2.node ) - if self in nodes and node in nodes: - links.append( link ) - return links + if link: + node1, node2 = link.intf1.node, link.intf2.node + if node1 == self and node2 == node: + connections += [ ( intf, link.intf2 ) ] + elif node1 == node and node2 == self: + connections += [ ( intf, link.intf1 ) ] + return connections def deleteIntfs( self ): "Delete all of our interfaces." @@ -418,8 +421,8 @@ class Node( object ): results[ name ] = result return result - def config( self, mac=None, ip=None, ifconfig=None, - defaultRoute=None, **_params ): + def config( self, mac=None, ip=None, + defaultRoute=None, lo='up', **_params ): """Configure Node according to (optional) parameters: mac: MAC address for default interface ip: IP address for default interface @@ -432,8 +435,9 @@ class Node( object ): r = {} self.setParam( r, 'setMAC', mac=mac ) self.setParam( r, 'setIP', ip=ip ) - self.setParam( r, 'ifconfig', ifconfig=ifconfig ) self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute ) + # This should be examined + self.cmd( 'ifconfig lo ' + lo ) return r def configDefault( self, **moreParams ): @@ -457,9 +461,16 @@ class Node( object ): "The names of our interfaces sorted by port number" return [ str( i ) for i in self.intfList() ] + def __repr__( self ): + "More informative string representation" + intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) + for i in self.intfList() ] ) ) + return '<%s %s: %s pid=%s> ' % ( + self.__class__.__name__, self.name, intfs, self.pid ) + def __str__( self ): - return '%s: IP=%s intfs=%s pid=%s' % ( - self.name, self.IP(), ','.join( self.intfNames() ), self.pid ) + "Abbreviated string representation" + return self.name # Automatic class setup support @@ -634,9 +645,8 @@ class Switch( Node ): self.dpid = dpid if dpid else self.defaultDpid() self.opts = opts self.listenPort = listenPort - if self.listenPort: - self.opts += ' --listen=ptcp:%i ' % self.listenPort - self.controlIntf = None + if not self.inNamespace: + self.controlIntf = Intf( 'lo', self ) def defaultDpid( self ): "Derive dpid from switch name, s1 -> 1" @@ -646,11 +656,11 @@ class Switch( Node ): return dpid def defaultIntf( self ): - "Return control interface, if any" - if not self.inNamespace: - error( "error: tried to access control interface of " - " switch %s in root namespace" % self.name ) - return self.controlIntf + "Return control interface" + if self.controlIntf: + return self.controlIntf + else: + return Node.defaultIntf( self ) def sendCmd( self, *cmd, **kwargs ): """Send command to Node. @@ -662,6 +672,13 @@ class Switch( Node ): error( '*** Error: %s has execed and cannot accept commands' % self.name ) + def __repr__( self ): + "More informative string representation" + intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) + for i in self.intfList() ] ) ) + return '<%s %s: %s pid=%s> ' % ( + self.__class__.__name__, self.name, intfs, self.pid ) + class UserSwitch( Switch ): "User-space switch." @@ -671,6 +688,8 @@ class UserSwitch( Switch ): Switch.__init__( self, name, **kwargs ) pathCheck( 'ofdatapath', 'ofprotocol', moduleName='the OpenFlow reference user switch (openflow.org)' ) + if self.listenPort: + self.opts += ' --listen=ptcp:%i ' % self.listenPort @classmethod def setup( cls ): @@ -678,6 +697,13 @@ class UserSwitch( Switch ): if not os.path.exists( '/dev/net/tun' ): moduleDeps( add=TUN ) + def dpctl( self, *args ): + "Run dpctl command" + if not self.listenPort: + return "can't run dpctl w/no passive listening port" + return self.cmdPrint( 'dpctl ' + ' '.join( args ) + + ' tcp:127.0.0.1:%i' % self.listenPort ) + def start( self, controllers ): """Start OpenFlow reference user datapath. Log to /tmp/sN-{ofd,ofp}.log. @@ -736,7 +762,7 @@ class OVSLegacyKernelSwitch( Switch ): quietRun( 'ifconfig lo up' ) # Delete local datapath if it exists; # then create a new one monitoring the given interfaces - quietRun( 'ovs-dpctl del-dp ' + self.dp ) + self.cmd( 'ovs-dpctl del-dp ' + self.dp ) self.cmd( 'ovs-dpctl add-dp ' + self.dp ) ports = sorted( self.ports.values() ) if len( ports ) != ports[ -1 ] + 1 - self.portBase: @@ -768,15 +794,6 @@ class OVSSwitch( Switch ): name: name for switch defaultMAC: default MAC as unsigned int; random value if None""" Switch.__init__( self, name, **params ) - # self.dp is the text name for the datapath that - # we use for ovs-vsctl. This is different from the - # dpid, which is a 64-bit numerical value used by - # the openflow protocol. - self.dp = name - if self.inNamespace: - error( "OVSSwitch currently only works" - " in the root namespace.\n" ) - exit( 1 ) @classmethod def setup( cls ): @@ -796,32 +813,47 @@ class OVSSwitch( Switch ): '"service openvswitch-switch start".\n' ) exit( 1 ) + def dpctl( self, *args ): + "Run ovs-dpctl command" + return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] ) + + def attach( self, intf ): + "Connect a data port" + self.cmd( 'ovs-vsctl add-port', self, intf ) + self.cmd( 'ifconfig', intf, 'up' ) + + def detach( self, intf ): + "Disconnect a data port" + self.cmd( 'ovs-vsctl del-port', self, intf ) + def start( self, controllers ): "Start up a new OVS OpenFlow switch using ovs-vsctl" if self.inNamespace: - raise Exception( + raise Exception( 'OVS kernel switch does not work in a namespace' ) + # We should probably call config instead, but this + # requires some rethinking... + self.cmd( 'ifconfig lo up' ) # Annoyingly, --if-exists option seems not to work - self.cmd( 'ovs-vsctl del-br ', self.dp ) - self.cmd( 'ovs-vsctl add-br', self.dp ) - self.cmd( 'ovs-vsctl set-fail-mode', self.dp, 'secure' ) - ports = sorted( self.ports.values() ) - intfs = [ self.intfs[ port ] for port in ports ] + self.cmd( 'ovs-vsctl del-br', self ) + self.cmd( 'ovs-vsctl add-br', self ) + self.cmd( 'ovs-vsctl set-fail-mode', self, 'secure' ) # XXX: Ugly check - we should probably fix this! + ports = sorted( self.ports.values() ) if ports and ( len( ports ) != ports[ -1 ] + 1 - self.portBase ): raise Exception( 'only contiguous, one-indexed port ranges ' 'supported: %s' % self.intfs ) - for intf in intfs: - self.cmd( 'ovs-vsctl add-port', self.dp, intf ) - self.cmd( 'ifconfig', intf, 'up' ) + 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 ] ) - self.cmd( 'ovs-vsctl set-controller', self.dp, clist ) + self.cmd( 'ovs-vsctl set-controller', self, clist ) def stop( self ): "Terminate OVS switch." - self.cmd( 'ovs-vsctl del-br', self.dp ) + self.cmd( 'ovs-vsctl del-br', self ) OVSKernelSwitch = OVSSwitch @@ -865,6 +897,12 @@ class Controller( Node ): ip = self.ip return ip + def __repr__( self ): + "More informative string representation" + return '<%s %s: %s:%s pid=%s> ' % ( + self.__class__.__name__, self.name, + self.IP(), self.port, self.pid ) + class OVSController( Controller ): "Open vSwitch controller" From d7e5dfc5b60fabbcd213707eed214469e15ddcac Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 12 Mar 2012 00:20:26 -0700 Subject: [PATCH 059/250] Minor tweaks: specify port, new repr() --- mininet/link.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index e0bd36e..6bf619b 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -33,7 +33,7 @@ class Intf( object ): "Basic interface object that can configure itself." - def __init__( self, name, node=None, link=None, **kwargs ): + def __init__( self, name, node=None, port=None, link=None, **kwargs ): """name: interface name (e.g. h1-eth0) node: owning node (where this intf most likely lives) link: parent link if we're part of a link @@ -43,7 +43,7 @@ class Intf( object ): self.link = link self.mac, self.ip, self.prefixLen = None, None, None # Add to node (and move ourselves if necessary ) - node.addIntf( self ) + node.addIntf( self, port=port ) self.config( **kwargs ) def cmd( self, *args, **kwargs ): @@ -163,6 +163,9 @@ class Intf( object ): # Does it help to sleep to let things run? sleep( 0.001 ) + def __repr__( self ): + return '<%s %s>' % ( self.__class__.__name__, self.name ) + def __str__( self ): return self.name @@ -339,8 +342,10 @@ class Link( object ): if not params2: params2 = {} - intf1 = cls1( name=intfName1, node=node1, link=self, **params1 ) - intf2 = cls2( name=intfName2, node=node2, link=self, **params2 ) + intf1 = cls1( name=intfName1, node=node1, port=port1, + link=self, **params1 ) + intf2 = cls2( name=intfName2, node=node2, port=port2, + link=self, **params2 ) # All we are is dust in the wind, and our two interfaces self.intf1, self.intf2 = intf1, intf2 From 14c19260814036c40026c20339d2d9a6976d1318 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 12 Mar 2012 00:20:48 -0700 Subject: [PATCH 060/250] Use port 0 for control interface on switches. --- mininet/node.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index b1b8164..5960076 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -646,7 +646,7 @@ class Switch( Node ): self.opts = opts self.listenPort = listenPort if not self.inNamespace: - self.controlIntf = Intf( 'lo', self ) + self.controlIntf = Intf( 'lo', self, port=0 ) def defaultDpid( self ): "Derive dpid from switch name, s1 -> 1" @@ -700,8 +700,8 @@ class UserSwitch( Switch ): def dpctl( self, *args ): "Run dpctl command" if not self.listenPort: - return "can't run dpctl w/no passive listening port" - return self.cmdPrint( 'dpctl ' + ' '.join( args ) + + return "can't run dpctl without passive listening port" + return self.cmd( 'dpctl ' + ' '.join( args ) + ' tcp:127.0.0.1:%i' % self.listenPort ) def start( self, controllers ): @@ -712,10 +712,7 @@ class UserSwitch( Switch ): ofdlog = '/tmp/' + self.name + '-ofd.log' ofplog = '/tmp/' + self.name + '-ofp.log' self.cmd( 'ifconfig lo up' ) - ports = sorted( self.ports.values() ) - intfs = [ str( self.intfs[ p ] ) for p in ports ] - if self.inNamespace: - intfs = intfs[ :-1 ] + intfs = [ str( i ) for i in self.intfList() if not i.IP() ] self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' punix:/tmp/' + self.name + ' -d ' + self.dpid + ' --no-slicing ' + @@ -764,11 +761,7 @@ class OVSLegacyKernelSwitch( Switch ): # then create a new one monitoring the given interfaces self.cmd( 'ovs-dpctl del-dp ' + self.dp ) self.cmd( 'ovs-dpctl add-dp ' + self.dp ) - ports = sorted( self.ports.values() ) - if len( ports ) != ports[ -1 ] + 1 - self.portBase: - raise Exception( 'only contiguous, one-indexed port ranges ' - 'supported: %s' % self.intfs ) - intfs = [ self.intfs[ port ] for port in ports ] + intfs = [ str( i ) for i in self.intfList() if not i.IP() ] self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) ) # Run protocol daemon controller = controllers[ 0 ] @@ -838,11 +831,6 @@ class OVSSwitch( Switch ): self.cmd( 'ovs-vsctl del-br', self ) self.cmd( 'ovs-vsctl add-br', self ) self.cmd( 'ovs-vsctl set-fail-mode', self, 'secure' ) - # XXX: Ugly check - we should probably fix this! - ports = sorted( self.ports.values() ) - if ports and ( len( ports ) != ports[ -1 ] + 1 - self.portBase ): - raise Exception( 'only contiguous, one-indexed port ranges ' - 'supported: %s' % self.intfs ) for intf in self.intfList(): if not intf.IP(): self.attach( intf ) From bf9c6ab7b4bcdabbcd154c95546c9808b54664fd Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 12 Mar 2012 00:29:55 -0700 Subject: [PATCH 061/250] Clarify comments and finally remove ControllerParams definition. --- mininet/node.py | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 5960076..10a3b45 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -607,12 +607,14 @@ class CPULimitedHost( Host ): # Some important things to note: # -# The "IP" address which we assign to the switch is not +# The "IP" address which setIP() assigns to the switch is not # an "IP address for the switch" in the sense of IP routing. -# Rather, it is the IP address for a control interface if -# (and only if) you happen to be running the switch in a -# namespace, which is something we currently don't support -# for OVS! +# Rather, it is the IP address for the control interface, +# on the control network, and it is only relevant to the +# controller. If you are running in the root namespace +# (which is the only way to run OVS at the moment), the +# control interface is the loopback interface, and you +# normally never want to change its IP address! # # In general, you NEVER want to attempt to use Linux's # network stack (i.e. ifconfig) to "assign" an IP address or @@ -620,16 +622,8 @@ class CPULimitedHost( Host ): # the IP and MAC addresses in the controller by specifying # packets that you want to receive or send. The "MAC" address # reported by ifconfig for a switch data port is essentially -# meaningless. -# -# So, I'm trying changing the API to make it -# impossible to try this, since it will not work, since nobody -# ever makes separate control networks in Mininet, and indeed -# we don't even support running OVS in a namespace. -# -# Note if we have a separate control network, then it does -# make sense to have s1-eth0 as s1's control network interface, -# and we should set controlIntf accordingly. +# meaningless. It is important to understand this if you +# want to create a functional router using OpenFlow. class Switch( Node ): """A Switch is a Node that is running (or has execed?) @@ -898,20 +892,6 @@ class OVSController( Controller ): Controller.__init__( self, name, command=command, **kwargs ) -# BL: This really seems to be poorly specified, -# so it's going to go away! - -class ControllerParams( object ): - "Container for controller IP parameters." - - def __init__( self, ip, prefixLen ): - """Init. - ip: string, controller IP address - prefixLen: prefix length, e.g. 8 for /8, covering 16M""" - self.ip = ip - self.prefixLen = prefixLen - - class NOX( Controller ): "Controller to run a NOX application." From 318ae55e35279f41349abc3f610a9c80e107750c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 00:10:11 -0700 Subject: [PATCH 062/250] Allow sendCmd( [ cmd, arg1, ... ] ) --- mininet/node.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 10a3b45..ad73ef5 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -194,8 +194,13 @@ class Node( object ): printPid: print command's PID?""" assert not self.waiting printPid = kwargs.get( 'printPid', True ) - if len( args ) > 0: + # Allow sendCmd( [ list ] ) + if len( args ) == 1 and type( args[ 0 ] ) is list: + cmd = args[ 0 ] + # Allow sendCmd( cmd, arg1, arg2... ) + elif len( args ) > 0: cmd = args + # Convert to string if not isinstance( cmd, str ): cmd = ' '.join( [ str( c ) for c in cmd ] ) if not re.search( r'\w', cmd ): From 5a8bb489510da602fe7712378ae618f6c87ec0d6 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 00:17:30 -0700 Subject: [PATCH 063/250] Attempt at revised/simplified topo class: - keys are strings - metadata is simply a dict - buildFromTopo greatly simplified --- mininet/net.py | 98 +++++------ mininet/topo.py | 431 ++++++++++++--------------------------------- mininet/topolib.py | 29 ++- mininet/util.py | 23 ++- 4 files changed, 188 insertions(+), 393 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index be93247..344f777 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -97,7 +97,7 @@ from mininet.log import info, error, debug, output from mininet.node import Host, OVSKernelSwitch, Controller from mininet.link import Link from mininet.util import quietRun, fixLimits -from mininet.util import macColonHex, ipStr, ipParse, netParse +from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd from mininet.term import cleanUpScreens, makeTerms class Mininet( object ): @@ -131,6 +131,8 @@ class Mininet( object ): self.link = link self.intf = intf self.ipBase = ipBase + self.ipBaseNum, self.prefixLen = netParse( self.ipBase ) + self.nextIP = 1 # start for address allocation self.inNamespace = inNamespace self.xterms = xterms self.cleanup = cleanup @@ -143,7 +145,6 @@ class Mininet( object ): self.controllers = [] self.nameToNode = {} # name to Node (Host/Switch) objects - self.idToNode = {} # dpid to Node (Host/Switch) objects self.terms = [] # list of spawned xterm processes @@ -153,35 +154,39 @@ class Mininet( object ): if topo and build: self.build() - # BL Note: - # The specific items for host/switch/etc. should probably be - # handled in the node classes rather than here!! - - def addHost( self, name, host=None, **params ): + def addHost( self, name, cls=None, **params ): """Add host. name: name of host to add - host: custom host constructor (optional) + cls: custom host class/constructor (optional) params: parameters for host returns: added host""" - if not host: - host = self.host - h = host( name, **params) + # Default IP and MAC addresses + defaults = { 'ip': ipAdd( self.nextIP, + ipBaseNum=self.ipBaseNum, + prefixLen=self.prefixLen ) } + if self.autoSetMacs: + defaults[ 'mac'] = macColonHex( self.nextIP ) + self.nextIP += 1 + defaults.update( params ) + if not cls: + cls = self.host + h = cls( name, **defaults ) self.hosts.append( h ) self.nameToNode[ name ] = h return h - def addSwitch( self, name, switch=None, **params ): + def addSwitch( self, name, cls=None, **params ): """Add switch. name: name of switch to add - switch: custom switch constructor (optional) + cls: custom switch class/constructor (optional) returns: added switch side effect: increments listenPort ivar .""" defaults = { 'listenPort': self.listenPort, 'inNamespace': self.inNamespace } defaults.update( params ) - if not switch: - switch = self.switch - sw = self.switch( name, **defaults ) + if not cls: + cls = self.switch + sw = cls( name, **defaults ) if not self.inNamespace and self.listenPort: self.listenPort += 1 self.switches.append( sw ) @@ -199,6 +204,15 @@ class Mininet( object ): self.nameToNode[ name ] = controller_new return controller_new + def addLink( self, src, dst, srcPort=None, dstPort=None, + cls=None, **params ): + "Add a link from topo" + if self.intf and not 'intf' in params: + params[ 'intf' ] = self.intf + if not cls: + cls = self.link + return cls( src, dst, srcPort, dstPort, **params ) + def configHosts( self ): "Configure a set of hosts." for host in self.hosts: @@ -215,40 +229,6 @@ class Mininet( object ): At the end of this function, everything should be connected and up.""" - ipBaseNum, prefixLen = netParse( self.ipBase ) - - if not topo: - topo = self.topo() - - def addNode( prefix, addMethod, nodeId ): - "Add a host or a switch from topo" - name = prefix + topo.name( nodeId ) - ni = topo.nodeInfo( nodeId ) - # Default IP and MAC addresses - defaults = { 'ip': topo.ip( nodeId, - ipBaseNum=ipBaseNum, - prefixLen=prefixLen ) } - if self.autoSetMacs: - defaults[ 'mac'] = macColonHex( nodeId ) - defaults.update( ni.params ) - node = addMethod( name, cls=ni.cls, **defaults ) - self.idToNode[ nodeId ] = node - info( name + ' ' ) - - def addLink( srcId, dstId, link=None ): - "Add a link from topo" - src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] - srcPort, dstPort = topo.port( srcId, dstId ) - ei = topo.edgeInfo( srcId, dstId ) - link = getattr( ei, 'cls', link ) - params = ei.params - if self.intf and not 'intf' in params: - params[ 'intf' ] = self.intf - if not link: - link = self.link - info( '(%s, %s) ' % ( src.name, dst.name ) ) - link( src, dst, srcPort, dstPort, **params ) - # Possibly we should clean up here and/or validate # the topo if self.cleanup: @@ -262,16 +242,22 @@ class Mininet( object ): self.addController( 'c0' ) info( '*** Adding hosts:\n' ) - for hostId in sorted( topo.hosts() ): - addNode( 'h', self.addHost, hostId ) + for hostName in topo.hosts(): + self.addHost( hostName, **topo.nodeInfo( hostName ) ) + info( hostName + ' ' ) info( '\n*** Adding switches:\n' ) - for switchId in sorted( topo.switches() ): - addNode( 's', self.addSwitch, switchId ) + for switchName in topo.switches(): + self.addSwitch( switchName, **topo.nodeInfo( switchName) ) + info( switchName + ' ' ) info( '\n*** Adding links:\n' ) - for srcId, dstId in sorted( topo.edges() ): - addLink( srcId, dstId ) + for srcName, dstName in topo.links(sort=True): + src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ] + srcPort, dstPort = topo.port( srcName, dstName ) + self.addLink( src, dst, srcPort, dstPort, + **topo.linkInfo( srcName, dstName ) ) + info( '(%s, %s) ' % ( src.name, dst.name ) ) info( '\n' ) diff --git a/mininet/topo.py b/mininet/topo.py index 3de788c..6ef0276 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,146 +16,73 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph -from mininet.util import netParse, ipStr - -class NodeID(object): - '''Topo node identifier.''' - - def __init__(self, dpid = None): - '''Init. - - @param dpid dpid - ''' - # DPID-compatible hashable identifier: opaque 64-bit unsigned int - self.dpid = dpid - - def __str__(self): - '''String conversion. - - @return str dpid as string - ''' - return str(self.dpid) - - def name_str(self): - '''Name conversion. - - @return name name as string - ''' - return str(self.dpid) - - def ip_str(self, ipBase=None, prefixLen=8, ipBaseNum=0x0a000000): - '''Name conversion. - ipBase: optional base IP address string - prefixLen: optional IP prefix length - ipBaseNum: option base IP address as int - @return ip ip as string - ''' - if ipBase: - ipnum, prefixLen = netParse( ipBase ) - else: - ipBaseNum = ipBaseNum - # Ugly but functional - assert self.dpid < ( 1 << ( 32 - prefixLen ) ) - mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 ) - ipnum = self.dpid + ( ipBaseNum & mask ) - return ipStr( ipnum ) - - -class Node( object ): - '''Node-specific vertex metadata for a Topo object.''' - - def __init__(self, connected=False, admin_on=True, - power_on=True, fault=False, is_switch=True, - cls=None, **params ): - '''Init. - - @param connected actively connected to controller - @param admin_on administratively on or off - @param power_on powered on or off - @param fault fault seen on node - @param is_switch switch or host - @param cls node class (e.g. Host, Switch) - @param params node parameters - ''' - self.connected = connected - self.admin_on = admin_on - self.power_on = power_on - self.fault = fault - self.is_switch = is_switch - # BL: Above should mostly be deleted and replaced by the following - # is_switch is a bit annoying if we are already specifying - # a switch class!! Except that if cls is not specified, - # then Mininet() knows whether to create a switch or a host - # node and can call its own constructors... - self.cls = cls - self.params = params - - -class Edge(object): - '''Edge-specific metadata for a StructuredTopo graph.''' - - def __init__(self, admin_on=True, power_on=True, fault=False, - cls=None, **params): - '''Init. - - @param admin_on administratively on or off; defaults to True - @param power_on powered on or off; defaults to True - @param fault fault seen on edge; defaults to False - ''' - self.admin_on = admin_on - self.power_on = power_on - self.fault = fault - # Above should be deleted and replaced by the following - self.cls = cls - self.params = params - +from mininet.util import netParse, ipStr, irange, natural, naturalSeq class Topo(object): - """Data center network representation for structured multi-trees. - Note that the order of precedence is: - per-node/link classes and parameters - per-topo classes - per-network classes""" + "Data center network representation for structured multi-trees." - def __init__(self, node=None, switch=None, link=None ): - """Create Topo object. - node: default node/host class (optional) - switch: default switch class (optional) - link: default link class (optional) - ipBase: default IP address base (optional)""" + def __init__(self, hopts=None, sopts=None, lopts=None): + """Topo object: + hinfo: default host options + sopts: default switch options + lopts: default link options""" self.g = Graph() - self.node_info = {} # dpids hash to Node objects - self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects + 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 lopts + self.lopts = {} if lopts is None else lopts self.ports = {} # ports[src][dst] is port on src that connects to dst - self.id_gen = NodeID # class used to generate dpid - self.node = node - self.switch = switch - self.link = link - def add_node(self, dpid, node=None): - '''Add Node to graph. + def add_node(self, name, *args, **opts): + """Add Node to graph. + add_node('name', dict) add_node('name', **opts) + name: name + args: dict of node options + opts: node options""" + self.g.add_node(name) + if args and type(args[0]) is dict: + opts = args[0] + self.node_info[name] = opts + return name - @param dpid dpid - @param node Node object - ''' - self.g.add_node(dpid) - if not node: - node = Node( link=self.link ) - self.node_info[dpid] = node + def add_host(self, name, *args, **opts): + """Convenience method: Add host to graph. + add_host('name', dict) add_host('name', **opts) + name: name + args: dict of node options + opts: node options""" + if not opts and self.hopts: + opts = self.hopts + return self.add_node(name, *args, **opts) - def add_edge(self, src, dst, edge=None): - '''Add edge (Node, Node) to graph. + def add_switch(self, name, **opts): + """Convenience method: Add switch to graph. + add_switch('name', dict) add_switch('name', **opts) + name: name + args: dict of node options + opts: node options""" + if not opts and self.sopts: + opts = self.sopts + result = self.add_node(name, is_switch=True, **opts) + return result - @param src src dpid - @param dst dst dpid - @param edge Edge object - ''' - src, dst = tuple(sorted([src, dst])) + def add_link(self, src, dst, *args, **opts): + """Add link (Node, Node) to topo. + add_link(src, dst, dict) add_link(src, dst, **opts) + src: src name + dst: dst name + args: dict of node options + params: link parameters""" + src, dst = sorted([src, dst], key=naturalSeq) self.g.add_edge(src, dst) - if not edge: - edge = Edge( cls=self.link ) - self.edge_info[(src, dst)] = edge + if args and type(args[0]) is dict: + opts = args[0] + if not opts and self.sopts: + opts = self.sopts + self.link_info[(src, dst)] = opts self.add_port(src, dst) + return src, dst def add_port(self, src, dst): '''Generate port mapping for new edge. @@ -175,131 +102,48 @@ class Topo(object): if src not in self.ports[dst]: # num outlinks self.ports[dst][src] = len(self.ports[dst]) + dst_base - - def node_enabled(self, dpid): - '''Is node connected, admin on, powered on, and fault-free? - - @param dpid dpid - - @return bool node is enabled - ''' - ni = self.node_info[dpid] - return ni.connected and ni.admin_on and ni.power_on and not ni.fault - - def nodes_enabled(self, dpids, enabled = True): - '''Return subset of enabled nodes - - @param dpids list of dpids - @param enabled only return enabled nodes? - - @return dpids filtered list of dpids - ''' - if enabled: - return [n for n in dpids if self.node_enabled(n)] + + def nodes(self, sort=True): + "Return nodes in graph" + if sort: + return sorted( self.g.nodes(), key=natural ) else: - return dpids - - def nodes(self, enabled = True): - '''Return graph nodes. - - @param enabled only return enabled nodes? - - @return dpids list of dpids - ''' - return self.nodes_enabled(self.g.nodes(), enabled) - - def nodes_str(self, dpids): - '''Return string of custom-encoded nodes. - - @param dpids list of dpids - - @return str string - ''' - return [str(self.id_gen(dpid = dpid)) for dpid in dpids] + return self.g.nodes() def is_switch(self, n): '''Returns true if node is a switch.''' - return self.node_info[n].is_switch + info = self.node_info[n] + return info and info['is_switch'] - def switches(self, enabled = True): + def switches(self, sort=True): '''Return switches. - - @param enabled only return enabled nodes? - + sort: sort switches alphabetically @return dpids list of dpids ''' - nodes = [n for n in self.g.nodes() if self.is_switch(n)] - return self.nodes_enabled(nodes, enabled) + return [n for n in self.nodes(sort) if self.is_switch(n)] - def hosts(self, enabled = True): + def hosts(self, sort=True): '''Return hosts. - - @param enabled only return enabled nodes? - + sort: sort hosts alphabetically @return dpids list of dpids ''' + return [n for n in self.nodes(sort) if not self.is_switch(n)] - def is_host(n): - '''Returns true if node is a host.''' - return not self.node_info[n].is_switch - - nodes = [n for n in self.g.nodes() if is_host(n)] - return self.nodes_enabled(nodes, enabled) - - def edge_enabled(self, edge): - '''Is edge admin on, powered on, and fault-free? - - @param edge (src, dst) dpid tuple - - @return bool edge is enabled + def links(self, sort=True): + '''Return links. + sort: sort links alphabetically + @return links list of name pairs ''' - src, dst = edge - src, dst = tuple(sorted([src, dst])) - ei = self.edge_info[tuple(sorted([src, dst]))] - return ei.admin_on and ei.power_on and not ei.fault - - def edges_enabled(self, edges, enabled = True): - '''Return subset of enabled edges - - @param edges list of edges - @param enabled only return enabled edges? - - @return edges filtered list of edges - ''' - if enabled: - return [e for e in edges if self.edge_enabled(e)] + if not sort: + return self.g.edges() else: - return edges - - def edges(self, enabled = True): - '''Return edges. - - @param enabled only return enabled edges? - - @return edges list of dpid pairs - ''' - return self.edges_enabled(self.g.edges(), enabled) - - def edges_str(self, dpid_pairs): - '''Return string of custom-encoded node pairs. - - @param dpid_pairs list of dpid pairs (src, dst) - - @return str string - ''' - edges = [] - for pair in dpid_pairs: - src, dst = pair - src = str(self.id_gen(dpid = src)) - dst = str(self.id_gen(dpid = dst)) - edges.append((src, dst)) - return edges + return sorted( self.g.edges(), key=naturalSeq ) def port(self, src, dst): '''Get port number. - @param src source switch DPID - @param dst destination switch DPID + @param src source switch name + @param dst destination switch name @return tuple (src_port, dst_port): src_port: port on source switch leading to the destination switch dst_port: port on destination switch leading to the source switch @@ -308,84 +152,41 @@ class Topo(object): assert dst in self.ports and src in self.ports[dst] return (self.ports[src][dst], self.ports[dst][src]) - def edgeInfo( self, src, dst ): - "Return edge metadata" - # BL: Perhaps this should be rethought or we should just use the - # dicts... - return self.edge_info[ ( src, dst ) ] + def linkInfo( self, src, dst ): + "Return link metadata" + src, dst = sorted((src, dst), key=naturalSeq) + return self.link_info[(src, dst)] - def enable_edges(self): - '''Enable all edges in the network graph. + def nodeInfo( self, name ): + "Return metadata (dict) for node" + info = self.node_info[ name ] + return info if info is not None else {} - Set admin on, power on, and fault off. - ''' - for e in self.g.edges(): - src, dst = e - ei = self.edge_info[tuple(sorted([src, dst]))] - ei.admin_on = True - ei.power_on = True - ei.fault = False - - def enable_nodes(self): - '''Enable all nodes in the network graph. - - Set connected on, admin on, power on, and fault off. - ''' - for node in self.g.nodes(): - ni = self.node_info[node] - ni.connected = True - ni.admin_on = True - ni.power_on = True - ni.fault = False - - def enable_all(self): - '''Enable all nodes and edges in the network graph.''' - self.enable_nodes() - self.enable_edges() - - def name(self, dpid): - '''Get string name of node ID. - - @param dpid DPID of host or switch - @return name_str string name with no dashes - ''' - return self.id_gen(dpid = dpid).name_str() - - def ip(self, dpid, **params): - '''Get IP dotted-decimal string of node ID. - @param dpid DPID of host or switch - @param params: params to pass to ip_str - @return ip_str - ''' - return self.id_gen(dpid = dpid).ip_str(**params) - - def nodeInfo( self, dpid ): - "Return metadata for node" - # BL: may wish to rethink this or just use dicts.. - return self.node_info[ dpid ] + def setNodeInfo( self, name, info ): + self.node_info[ name ] = info + @staticmethod + def sorted( items ): + "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, enable_all = True): + def __init__(self, k=2, **opts): '''Init. @param k number of hosts @param enable_all enables all nodes and switches? ''' - super(SingleSwitchTopo, self).__init__() + super(SingleSwitchTopo, self).__init__(**opts) self.k = k - self.add_node(1, Node()) - hosts = range(2, k + 2) - for h in hosts: - self.add_node(h, Node(is_switch = False)) - self.add_edge(h, 1, Edge()) - - if enable_all: - self.enable_all() + switch = self.add_switch('s1') + for h in irange(1, k): + host = self.add_host('h%s' % h) + self.add_link(host, switch) class SingleSwitchReversedTopo(SingleSwitchTopo): @@ -422,27 +223,23 @@ class SingleSwitchReversedTopo(SingleSwitchTopo): class LinearTopo(Topo): - '''Linear topology of k switches, with one host per switch.''' + "Linear topology of k switches, with one host per switch." - def __init__(self, k = 2, enable_all = True): - '''Init. + def __init__(self, k=2, **opts): + """Init. + k: number of switches (and hosts) + hconf: host configuration options + lconf: link configuration options""" - @param k number of switches (and hosts too) - @param enable_all enables all nodes and switches? - ''' - super(LinearTopo, self).__init__() + super(LinearTopo, self).__init__(**opts) self.k = k - switches = range(1, k + 1) - for s in switches: - h = s + k - self.add_node(s, Node()) - self.add_node(h, Node(is_switch = False)) - self.add_edge(s, h, Edge()) - for s in switches: - if s != k: - self.add_edge(s, s + 1, Edge()) - - if enable_all: - self.enable_all() + lastSwitch = None + for i in irange(1, k): + host = self.add_host('h%s' % i) + switch = self.add_switch('s%s' % i) + self.add_link( host, switch) + if lastSwitch: + self.add_link( switch, lastSwitch) + lastSwitch = switch diff --git a/mininet/topolib.py b/mininet/topolib.py index d42a42a..a8de9d8 100644 --- a/mininet/topolib.py +++ b/mininet/topolib.py @@ -1,6 +1,6 @@ "Library of potentially useful topologies for Mininet" -from mininet.topo import Topo, Node +from mininet.topo import Topo from mininet.net import Mininet class TreeTopo( Topo ): @@ -8,36 +8,27 @@ class TreeTopo( Topo ): def __init__( self, depth=1, fanout=2 ): super( TreeTopo, self ).__init__() - # Numbering: h1..N, sN+1..M - hostCount = fanout ** depth + # Numbering: h1..N, s1..M self.hostNum = 1 - self.switchNum = hostCount + 1 + self.switchNum = 1 # Build topology self.addTree( depth, fanout ) - # Consider all switches and hosts 'on' - self.enable_all() - - # It is OK that i is "unused" in the for loop. - # pylint: disable-msg=W0612 def addTree( self, depth, fanout ): """Add a subtree starting with node n. returns: last node added""" isSwitch = depth > 0 if isSwitch: - num = self.switchNum + node = self.add_switch( 's%s' % self.switchNum ) self.switchNum += 1 - else: - num = self.hostNum - self.hostNum += 1 - self.add_node( num, Node( is_switch=isSwitch ) ) - if isSwitch: - for i in range( 0, fanout ): + for _ in range( fanout ): child = self.addTree( depth - 1, fanout ) - self.add_edge( num, child ) - return num + self.add_link( node, child ) + else: + node = self.add_host( 'h%s' % self.hostNum ) + self.hostNum += 1 + return node - # pylint: enable-msg=W0612 def TreeNet( depth=1, fanout=2, **kwargs ): "Convenience function for creating tree networks." diff --git a/mininet/util.py b/mininet/util.py index 450472a..11a30a1 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -224,6 +224,18 @@ def ipNum( w, x, y, z ): returns: w << 24 | x << 16 | y << 8 | z""" return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z +def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ): + """Return IP address string from ints + i: int to be added to ipbase + prefixLen: optional IP prefix length + ipBaseNum: option base IP address as int + returns IP address as string""" + # Ugly but functional + assert i < ( 1 << ( 32 - prefixLen ) ) + mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 ) + ipnum = i + ( ipBaseNum & mask ) + return ipStr( ipnum ) + def ipParse( ip ): "Parse an IP address and return an unsigned int." args = [ int( arg ) for arg in ip.split( '.' ) ] @@ -275,9 +287,13 @@ def natural( text ): "To sort sanely/alphabetically: sorted( l, key=natural )" def num( s ): "Convert text segment to int if necessary" - return int( s ) if s.isdigit() else text + return int( s ) if s.isdigit() else s return [ num( s ) for s in re.split( r'(\d+)', text ) ] +def naturalSeq( t ): + "Natural sort key function for sequences" + return [ natural( x ) for x in t ] + def numCores(): "Returns number of CPU cores based on /proc/cpuinfo" if hasattr( numCores, 'ncores' ): @@ -288,6 +304,11 @@ def numCores(): return 0 return numCores.ncores +def irange(start, end): + """Inclusive range from start to end (vs. Python insanity.) + irange(1,5) -> 1, 2, 3, 4, 5""" + return range( start, end + 1 ) + def custom( cls, **params ): "Returns customized constructor for class cls." def customized( *args, **kwargs): From ff56881946020a6fe7a203de1fe4d75a3a0ea20d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 15:43:43 -0700 Subject: [PATCH 064/250] Add TCLink for simplified tc-limited link creation. --- bin/mn | 14 +++++++------- mininet/link.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/bin/mn b/bin/mn index b82ba12..3785195 100755 --- a/bin/mn +++ b/bin/mn @@ -22,7 +22,7 @@ from mininet.log import lg, LEVELS, info, warn from mininet.net import Mininet, MininetWithControlNet from mininet.node import Host, CPULimitedHost, Controller, OVSController, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch -from mininet.link import Intf, TCIntf +from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo from mininet.util import makeNumeric, custom @@ -74,9 +74,9 @@ CONTROLLERS = { 'ref': Controller, 'remote': RemoteController, 'none': lambda name: None } -INTFDEF = 'default' -INTFS = { 'default': Intf, - 'tc': TCIntf } +LINKDEF = 'default' +LINKS = { 'default': Link, + 'tc': TCLink } # optional tests to run @@ -182,7 +182,7 @@ class MininetRunner( object ): addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' ) addDictOption( opts, HOSTS, HOSTDEF, 'host' ) addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' ) - addDictOption( opts, INTFS, INTFDEF, 'intf' ) + addDictOption( opts, LINKS, LINKDEF, 'link' ) addDictOption( opts, TOPOS, TOPODEF, 'topo' ) opts.add_option( '--clean', '-c', action='store_true', @@ -246,7 +246,7 @@ class MininetRunner( object ): switch = customNode( SWITCHES, self.options.switch ) host = customNode( HOSTS, self.options.host ) controller = customNode( CONTROLLERS, self.options.controller ) - intf = customNode( INTFS, self.options.intf ) + link = customNode( LINKS, self.options.link ) if self.validate: self.validate( self.options ) @@ -262,7 +262,7 @@ class MininetRunner( object ): listenPort = self.options.listenport mn = Net( topo=topo, switch=switch, host=host, controller=controller, - intf=intf, + link=link, ipBase=ipBase, inNamespace=inNamespace, xterms=xterms, autoSetMacs=mac, diff --git a/mininet/link.py b/mininet/link.py index 6bf619b..09971d4 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -371,3 +371,14 @@ class Link( object ): def __str__( self ): return '%s<->%s' % ( self.intf1, self.intf2 ) + +class TCLink( Link ): + "Link with symmetric TC interfaces configured via opts" + def __init__( self, node1, node2, port1=None, port2=None, + intfName1=None, intfName2=None, **params ): + Link.__init__( self, node1, node2, port1=None, port2=None, + intfName1=None, intfName2=None, + cls1=TCIntf, + cls2=TCIntf, + params1=params, + params2=params) From e52d0ee1ded99467f3b964261a7ea08fb90a90e6 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 15:44:50 -0700 Subject: [PATCH 065/250] Fix to work with new Topo class. --- examples/linearbandwidth.py | 59 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/examples/linearbandwidth.py b/examples/linearbandwidth.py index 4eb6371..42b3eb9 100755 --- a/examples/linearbandwidth.py +++ b/examples/linearbandwidth.py @@ -6,9 +6,9 @@ using both kernel and user datapaths. We construct a network of N hosts and N-1 switches, connected as follows: -h1 <-> sN+1 <-> sN+2 .. sN+N-1 - | | | - h2 h3 hN +h1 <-> s1 <-> s2 .. sN-1 + | | | + h2 h3 hN WARNING: by default, the reference controller only supports 16 switches, so this test WILL NOT WORK unless you have recompiled @@ -23,42 +23,40 @@ of switches, this example demonstrates: """ +from mininet.net import Mininet +from mininet.node import UserSwitch, OVSKernelSwitch +from mininet.topo import Topo +from mininet.log import lg +from mininet.util import irange + import sys flush = sys.stdout.flush -from mininet.net import Mininet -# from mininet.node import KernelSwitch -from mininet.node import UserSwitch, OVSKernelSwitch -from mininet.topo import Topo, Node -from mininet.log import lg - class LinearTestTopo( Topo ): "Topology for a string of N hosts and N-1 switches." - def __init__( self, N ): + def __init__( self, N, **params ): - # Add default members to class. - super( LinearTestTopo, self ).__init__() + # Initialize topology + Topo.__init__( self, **params ) - # Create switch and host nodes - hosts = range( 1, N + 1 ) - switches = range( N + 1 , N + N ) - for h in hosts: - self.add_node( h, Node( is_switch=False ) ) - for s in switches: - self.add_node( s, Node( is_switch=True ) ) + # Create switches and hosts + hosts = [ self.add_host( 'h%s' % h ) + for h in irange( 1, N ) ] + switches = [ self.add_switch( 's%s' % s ) + for s in irange( 1, N - 1 ) ] # Wire up switches - for s in switches[ :-1 ]: - self.add_edge( s, s + 1 ) + last = None + for switch in switches: + if last: + self.add_link( last, switch ) + last = switch # Wire up hosts - self.add_edge( hosts[ 0 ], switches[ 0 ] ) - for h in hosts[ 1: ]: - self.add_edge( h, h + N - 1 ) - - # Consider all switches and hosts 'on' - self.enable_all() + self.add_link( hosts[ 0 ], switches[ 0 ] ) + for host, switch in zip( hosts[ 1: ], switches ): + self.add_link( host, switch ) def linearBandwidthTest( lengths ): @@ -69,15 +67,16 @@ def linearBandwidthTest( lengths ): switchCount = max( lengths ) hostCount = switchCount + 1 - switches = { # 'reference kernel': KernelSwitch, - 'reference user': UserSwitch, + switches = { 'reference user': UserSwitch, 'Open vSwitch kernel': OVSKernelSwitch } + topo = LinearTestTopo( hostCount ) + for datapath in switches.keys(): print "*** testing", datapath, "datapath" Switch = switches[ datapath ] results[ datapath ] = [] - net = Mininet( topo=LinearTestTopo( hostCount ), switch=Switch ) + net = Mininet( topo=topo, switch=Switch ) net.start() print "*** testing basic connectivity" for n in lengths: From 8bebd3775982dc9b8af603b0d71b5a658a7d9755 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 15:45:46 -0700 Subject: [PATCH 066/250] Fix is_switch() to always succeed + whitespace edits. --- mininet/topo.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index 6ef0276..e80517a 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,7 +16,7 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph -from mininet.util import netParse, ipStr, irange, natural, naturalSeq +from mininet.util import irange, natural, naturalSeq class Topo(object): "Data center network representation for structured multi-trees." @@ -38,7 +38,7 @@ class Topo(object): """Add Node to graph. add_node('name', dict) add_node('name', **opts) name: name - args: dict of node options + args: dict of node options opts: node options""" self.g.add_node(name) if args and type(args[0]) is dict: @@ -50,7 +50,7 @@ class Topo(object): """Convenience method: Add host to graph. add_host('name', dict) add_host('name', **opts) name: name - args: dict of node options + args: dict of node options opts: node options""" if not opts and self.hopts: opts = self.hopts @@ -60,7 +60,7 @@ class Topo(object): """Convenience method: Add switch to graph. add_switch('name', dict) add_switch('name', **opts) name: name - args: dict of node options + args: dict of node options opts: node options""" if not opts and self.sopts: opts = self.sopts @@ -102,7 +102,7 @@ class Topo(object): if src not in self.ports[dst]: # num outlinks self.ports[dst][src] = len(self.ports[dst]) + dst_base - + def nodes(self, sort=True): "Return nodes in graph" if sort: @@ -113,7 +113,7 @@ class Topo(object): def is_switch(self, n): '''Returns true if node is a switch.''' info = self.node_info[n] - return info and info['is_switch'] + return info and info.get('is_switch', False) def switches(self, sort=True): '''Return switches. @@ -163,6 +163,7 @@ class Topo(object): return info if info is not None else {} def setNodeInfo( self, name, info ): + "Set metadata (dict) for node" self.node_info[ name ] = info @staticmethod From efc991547ee0a283425373aa4dcf6c68ba3d5717 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 15:46:54 -0700 Subject: [PATCH 067/250] Add warning in defaultIntf() if host has no interfaces. Possibly this should be in intf() instead, as intf() is assumed to always succeed. --- mininet/node.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index ad73ef5..9c9dbef 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -310,16 +310,19 @@ class Node( object ): ports = self.intfs.keys() if ports: return self.intfs[ min( ports ) ] + else: + warn( '*** defaultIntf: warning:', self.name, + 'has no interfaces\n' ) def intf( self, intf='' ): - """Return our interface object with given name,x + """Return our interface object with given name, or default intf if name is empty""" if not intf: return self.defaultIntf() elif type( intf) is str: return self.nameToIntf[ intf ] else: - return intf + return None def connectionsTo( self, node): "Return [ intf1, intf2... ] for all intfs that connect self to node." From 9005ce325562bc3cf4e08413bce3c76479a2b72e Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 15:48:06 -0700 Subject: [PATCH 068/250] Whitespace fixes. --- mininet/net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 344f777..ab731be 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -169,7 +169,7 @@ class Mininet( object ): self.nextIP += 1 defaults.update( params ) if not cls: - cls = self.host + cls = self.host h = cls( name, **defaults ) self.hosts.append( h ) self.nameToNode[ name ] = h @@ -204,7 +204,7 @@ class Mininet( object ): self.nameToNode[ name ] = controller_new return controller_new - def addLink( self, src, dst, srcPort=None, dstPort=None, + def addLink( self, src, dst, srcPort=None, dstPort=None, cls=None, **params ): "Add a link from topo" if self.intf and not 'intf' in params: From f85c1cefb4a64a0724aa71f6a114ea572e62434c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 15:49:39 -0700 Subject: [PATCH 069/250] Use upstream OVS packages. --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index ae45548..fcc027a 100755 --- a/util/install.sh +++ b/util/install.sh @@ -70,7 +70,7 @@ DRIVERS_DIR=/lib/modules/${KERNEL_NAME}/kernel/drivers/net OVS_RELEASE=1.4.0 OVS_PACKAGE_LOC=https://github.com/downloads/mininet/mininet -OVS_BUILDSUFFIX=-2 +OVS_BUILDSUFFIX=-ignore # was -2 OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar OVS_SRC=~/openvswitch OVS_TAG=v$OVS_RELEASE From ea7c3260175efdb79220ee91b008beae73ed4490 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 15:50:44 -0700 Subject: [PATCH 070/250] Ignore emacs autosaves. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 993ea4a..66c0b7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ mnexec *.pyc *~ +\#*\# mininet.egg-info build/* dist/* From b684ff78444cc6de013d3cbb42552e03f3ce38cf Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 16:23:17 -0700 Subject: [PATCH 071/250] Fix convenience configuration methods. --- mininet/node.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 9c9dbef..b05b142 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -374,13 +374,13 @@ class Node( object ): # Convenience and configuration methods - def setMAC( self, mac, intf=''): + def setMAC( self, mac, intf=None ): """Set the MAC address for an interface. intf: intf or intf name mac: MAC address as string""" return self.intf( intf ).setMAC( mac ) - def setIP( self, ip, prefixLen=8, intf='' ): + def setIP( self, ip, prefixLen=8, intf=None ): """Set the IP address for an interface. intf: interface name ip: IP address as a string @@ -392,13 +392,11 @@ class Node( object ): def IP( self, intf=None ): "Return IP address of a node or specific interface." - i = self.intf( intf ) - return self.intf( i ).IP() if i else None + return self.intf( intf ).IP() def MAC( self, intf=None ): "Return MAC address of a node or specific interface." - i = self.intf( intf ) - return self.intf( i ).MAC() if i else None + return self.intf( intf ).IP() def intfIsUp( self, intf=None ): "Check if an interface is up." From 41245f508700602f054942e1063774b3fcc7e84e Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 21 Mar 2012 17:27:40 -0700 Subject: [PATCH 072/250] Add getNodeByName for hifi compatibility. --- mininet/net.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mininet/net.py b/mininet/net.py index ab731be..f56daa5 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -203,6 +203,12 @@ class Mininet( object ): self.controllers.append( controller_new ) self.nameToNode[ name ] = controller_new return controller_new + + # BL: is this better than just using nameToNode[] ? + # Should it have a better name? + def getNodeByName( self, nodeName ): + "Return node with given name" + return self.nameToNode[ nodeName ] def addLink( self, src, dst, srcPort=None, dstPort=None, cls=None, **params ): From 595427842cb592c12319110650c086abb081ff00 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 21 Mar 2012 17:28:00 -0700 Subject: [PATCH 073/250] Make CPULimitedHost method sig friendlier, and make 'cfs' default sched. --- mininet/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index b05b142..3dc9cff 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -506,14 +506,14 @@ class CPULimitedHost( Host ): "CPU limited host" - def __init__( self, *args, **kwargs ): - Host.__init__( self, *args, **kwargs ) + def __init__( self, name, sched='cfs', **kwargs ): + Host.__init__( self, name, **kwargs ) # Create a cgroup and move shell into it self.cgroup = 'cpu,cpuacct:/' + self.name errFail( 'cgcreate -g ' + self.cgroup ) errFail( 'cgclassify -g %s %s' % ( self.cgroup, self.pid ) ) self.period_us = kwargs.get( 'period_us', 10000 ) - self.sched = kwargs.get( 'sched', 'rt' ) + self.sched = sched def cleanup( self ): "Clean up our cgroup" From 1aec55d9609e0ec4eb884353657af9bfba6b5fa0 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 21 Mar 2012 22:39:46 -0700 Subject: [PATCH 074/250] Workaround: reapply tc config after OVS destroys it. --- mininet/node.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 3dc9cff..b806da4 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -54,7 +54,7 @@ from mininet.log import info, error, warn, debug from mininet.util import quietRun, errRun, errFail, moveIntf, isShellBuiltin from mininet.util import numCores from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN -from mininet.link import Link, Intf +from mininet.link import Link, Intf, TCIntf class Node( object ): """A virtual network node is simply a shell in a network namespace. @@ -810,10 +810,19 @@ class OVSSwitch( Switch ): "Run ovs-dpctl command" return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] ) + @staticmethod + def TCReapply( intf ): + """Unfortunately OVS and Mininet are fighting + over tc queuing disciplines. As a quick hack/ + workaround, we clear OVS's and reapply our own.""" + if type( intf ) is TCIntf: + intf.config( **intf.params ) + def attach( self, intf ): "Connect a data port" self.cmd( 'ovs-vsctl add-port', self, intf ) self.cmd( 'ifconfig', intf, 'up' ) + self.TCReapply( intf ) def detach( self, intf ): "Disconnect a data port" From 0b7c277ec217db90fb3b11d10dab9f18bd3be7fb Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 21 Mar 2012 23:07:40 -0700 Subject: [PATCH 075/250] Save parameters for future reference (e.g. OVS/tc workaround.) --- examples/limit.py | 2 +- mininet/link.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/limit.py b/examples/limit.py index 4514514..8f608e0 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -34,7 +34,7 @@ def testCpuLimit( net, cpu ): h1.cmd( 'kill %1') h2.cmd( 'kill %1') -def limit( bw=1, cpu=.3 ): +def limit( bw=1, cpu=.4 ): """Example/test of link and CPU bandwidth limits bw: interface bandwidth limit in Mbps cpu: cpu limit as fraction of overall CPU time""" diff --git a/mininet/link.py b/mininet/link.py index 09971d4..3c12be8 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -33,7 +33,7 @@ class Intf( object ): "Basic interface object that can configure itself." - def __init__( self, name, node=None, port=None, link=None, **kwargs ): + def __init__( self, name, node=None, port=None, link=None, **params ): """name: interface name (e.g. h1-eth0) node: owning node (where this intf most likely lives) link: parent link if we're part of a link @@ -44,7 +44,9 @@ class Intf( object ): self.mac, self.ip, self.prefixLen = None, None, None # Add to node (and move ourselves if necessary ) node.addIntf( self, port=port ) - self.config( **kwargs ) + # Save params for future reference + self.params = params + self.config( **params ) def cmd( self, *args, **kwargs ): "Run a command in our owning node" From ba8d4f9bd6e01f8324459569db2eaadf2a6d1875 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 21 Mar 2012 23:31:20 -0700 Subject: [PATCH 076/250] Add verySimpleLimit() for debugging. --- examples/limit.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/limit.py b/examples/limit.py index 8f608e0..64d3dea 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -34,21 +34,35 @@ def testCpuLimit( net, cpu ): h1.cmd( 'kill %1') h2.cmd( 'kill %1') -def limit( bw=1, cpu=.4 ): +def limit( bw=10, cpu=.4 ): """Example/test of link and CPU bandwidth limits bw: interface bandwidth limit in Mbps cpu: cpu limit as fraction of overall CPU time""" - intf = custom( TCIntf, bw=1 ) + intf = custom( TCIntf, bw=bw ) myTopo = TreeTopo( depth=1, fanout=2 ) for sched in 'rt', 'cfs': print '*** Testing with', sched, 'bandwidth limiting' - host = custom( CPULimitedHost, sched=sched, cpu=cpu ) + host = custom( git CPULimitedHost, sched=sched, cpu=cpu ) net = Mininet( topo=myTopo, intf=intf, host=host ) net.start() testLinkLimit( net, bw=bw ) testCpuLimit( net, cpu=cpu ) net.stop() +def verySimpleLimit( bw=150 ): + intf = custom( TCIntf, bw=bw ) + net = Mininet( intf=intf ) + h1, h2 = net.addHost( 'h1' ), net.addHost( 'h2' ) + net.addLink( h1, h2 ) + net.start() + net.pingAll() + net.iperf() + h1.cmdPrint( 'tc -s qdisc ls dev', h1.defaultIntf() ) + h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() ) + h1.cmdPrint( 'tc -s qdisc ls dev', h1.defaultIntf() ) + h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() ) + net.stop() + if __name__ == '__main__': setLogLevel( 'info' ) - limit() + verySimpleLimit() From d1b29d58dfc8853d56cdffd612a39fedb2a03ce6 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 22 Mar 2012 14:43:27 -0700 Subject: [PATCH 077/250] Fix printing pid for background tasks. --- mininet/node.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index b806da4..62a7040 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -206,15 +206,17 @@ class Node( object ): if not re.search( r'\w', cmd ): # Replace empty commands with something harmless cmd = 'echo -n' - if len( cmd ) > 0 and cmd[ -1 ] == '&': - separator = '&' - cmd = cmd[ :-1 ] - else: - separator = ';' - if printPid and not isShellBuiltin( cmd ): - cmd = 'mnexec -p ' + cmd - self.write( cmd + separator + ' printf "\\177" \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 + self.write( cmd + '\n' ) self.lastPid = None self.waiting = True From f89d9a4d6bbbbcd659f46a5615d93ed0101ef74f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 22 Mar 2012 14:43:52 -0700 Subject: [PATCH 078/250] Fix typo inadvertently saved in editor. --- examples/limit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/limit.py b/examples/limit.py index 64d3dea..79e0178 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -42,7 +42,7 @@ def limit( bw=10, cpu=.4 ): myTopo = TreeTopo( depth=1, fanout=2 ) for sched in 'rt', 'cfs': print '*** Testing with', sched, 'bandwidth limiting' - host = custom( git CPULimitedHost, sched=sched, cpu=cpu ) + host = custom( CPULimitedHost, sched=sched, cpu=cpu ) net = Mininet( topo=myTopo, intf=intf, host=host ) net.start() testLinkLimit( net, bw=bw ) From 4deb735425a19e46a311b0351dec5f64dd52653c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 22 Mar 2012 14:44:20 -0700 Subject: [PATCH 079/250] Simple cpu limiting example. --- examples/cpu.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 examples/cpu.py diff --git a/examples/cpu.py b/examples/cpu.py new file mode 100755 index 0000000..0465967 --- /dev/null +++ b/examples/cpu.py @@ -0,0 +1,81 @@ +#!/usr/bin/python + +""" +cpu.py: test iperf bandwidth for varying cpu limtis +""" + +from mininet.net import Mininet +from mininet.node import CPULimitedHost +from mininet.topolib import TreeTopo +from mininet.util import custom +from mininet.log import setLogLevel, output + +from time import sleep + +def waitListening(client, server, port): + "Wait until server is listening on port" + if not client.cmd('which telnet'): + raise Exception('Could not find telnet') + cmd = ('sh -c "echo A | telnet -e A %s %s"' % + (server.IP(), port)) + while 'Connected' not in client.cmd(cmd): + output('waiting for', server, + 'to listen on port', port, '\n') + sleep(.5) + + +def bwtest( cpuLimits, period_us=10000, seconds=5 ): + """Example/test of link and CPU bandwidth limits + cpu: cpu limit as fraction of overall CPU time""" + + topo = TreeTopo( depth=1, fanout=2 ) + + results = {} + + for sched in 'rt', 'cfs': + print '*** Testing with', sched, 'bandwidth limiting' + for cpu in cpuLimits: + host = custom( CPULimitedHost, sched=sched, + period_us=period_us, + cpu=cpu ) + net = Mininet( topo=topo, host=host ) + net.start() + net.pingAll() + hosts = [ net.getNodeByName( h ) for h in topo.hosts() ] + client, server = hosts[ 0 ], hosts[ -1 ] + server.cmd( 'iperf -s -p 5001 &' ) + waitListening( client, server, 5001 ) + result = client.cmd( 'iperf -yc -t %s -c %s' % ( + seconds, server.IP() ) ).split( ',' ) + bps = float( result[ -1 ] ) + server.cmdPrint( 'kill %iperf' ) + net.stop() + updated = results.get( sched, [] ) + updated += [ ( cpu, bps ) ] + results[ sched ] = updated + + return results + + +def dump( results ): + "Dump results" + + format = '%s\t%s\t%s' + + print + print format % ( 'sched', 'cpu', 'client MB/s' ) + print + + for sched in sorted( results.keys() ): + entries = results[ sched ] + for cpu, bps in entries: + pct = '%.2f%%' % ( cpu * 100 ) + mbps = bps / 1e6 + print format % ( sched, pct, mbps ) + + +if __name__ == '__main__': + setLogLevel( 'info' ) + limits = [ .4, .3, .2, .1 ] + results = bwtest( limits ) + dump( results ) From a5af91d0d39b09aaf8cdea194da9b2f5f17b3637 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 22 Mar 2012 19:08:09 -0700 Subject: [PATCH 080/250] Have errFail report cmd and stderr as well as exit code. --- mininet/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/util.py b/mininet/util.py index 11a30a1..bd61fae 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -102,8 +102,8 @@ def errFail( *cmd, **kwargs ): "Run a command using errRun and raise exception on nonzero exit" out, err, ret = errRun( *cmd, **kwargs ) if ret: - raise Exception( "errFail: failed with return code %s" - % ret ) + raise Exception( "errFail: %s failed with return code %s: %s" + % ( cmd, ret, err ) ) return out, err, ret def quietRun( cmd, **kwargs ): From 28833d864cf9ca63592a5cf1cf7d1f025cb644ac Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 22 Mar 2012 19:08:45 -0700 Subject: [PATCH 081/250] Retry deleting cgroup for the moment because it seems flaky. Ultimately we may wish to create a mininet/ cgroup and do a recursive delete at the end. --- mininet/node.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 62a7040..eac1809 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -52,7 +52,7 @@ from subprocess import Popen, PIPE, STDOUT from mininet.log import info, error, warn, debug from mininet.util import quietRun, errRun, errFail, moveIntf, isShellBuiltin -from mininet.util import numCores +from mininet.util import numCores, retry from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN from mininet.link import Link, Intf, TCIntf @@ -139,6 +139,10 @@ class Node( object ): def cleanup( self ): "Help python collect its garbage." + if not self.inNamespace: + for intfName in self.intfNames(): + if self.name in intfName: + quietRun( 'ip link del ' + intfName ) self.shell = None def read( self, maxbytes=1024 ): @@ -517,12 +521,6 @@ class CPULimitedHost( Host ): self.period_us = kwargs.get( 'period_us', 10000 ) self.sched = sched - def cleanup( self ): - "Clean up our cgroup" - Host.cleanup( self ) - debug( '*** deleting cgroup', self.cgroup, '\n' ) - errFail( 'cgdelete -r ' + self.cgroup ) - def cgroupSet( self, param, value, resource='cpu' ): "Set a cgroup parameter and return its value" cmd = 'cgset -r %s.%s=%s /%s' % ( @@ -540,6 +538,16 @@ class CPULimitedHost( Host ): resource, param, self.name ) return quietRun( cmd ).split()[ -1 ] + def cgroupDel( self ): + "Clean up our cgroup" + # info( '*** deleting cgroup', self.cgroup, '\n' ) + out, err, exitcode = errRun( 'cgdelete -r ' + self.cgroup ) + return exitcode != 0 + + def cleanup( self ): + "Clean up our cgroup" + retry( retries=3, delaySecs=1, fn=self.cgroupDel ) + def chrt( self, prio=20 ): "Set RT scheduling priority" quietRun( 'chrt -p %s %s' % ( prio, self.pid ) ) From 8dcefd5ff949be8752b7c15d5e69bcd9543524ca Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 23 Mar 2012 13:24:25 -0700 Subject: [PATCH 082/250] Fix OVS legacy switch. --- mininet/node.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index eac1809..9a87468 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -662,7 +662,7 @@ class Switch( Node ): "Derive dpid from switch name, s1 -> 1" dpid = int( re.findall( '\d+', self.name )[ 0 ] ) dpid = hex( dpid )[ 2: ] - dpid = '0' * ( 12 - len( dpid ) ) + dpid + dpid = '0' * ( 16 - len( dpid ) ) + dpid return dpid def defaultIntf( self ): @@ -749,7 +749,7 @@ class OVSLegacyKernelSwitch( Switch ): dp: netlink id (0, 1, 2, ...) defaultMAC: default MAC as unsigned int; random value if None""" Switch.__init__( self, name, **kwargs ) - self.dp = 'dp%i' % dp + self.dp = self.name self.intf = self.dp if self.inNamespace: error( "OVSKernelSwitch currently only works" @@ -763,6 +763,7 @@ class OVSLegacyKernelSwitch( Switch ): moduleName='Open vSwitch (openvswitch.org)') moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) + def start( self, controllers ): "Start up kernel datapath." ofplog = '/tmp/' + self.name + '-ofp.log' From 335ba99b60b0921c50cef0e56652fd2a89a321b6 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 23 Mar 2012 13:33:31 -0700 Subject: [PATCH 083/250] Add --switch ovsl for legacy OVS. --- bin/mn | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/mn b/bin/mn index 3785195..5b00148 100755 --- a/bin/mn +++ b/bin/mn @@ -20,8 +20,9 @@ from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info, warn from mininet.net import Mininet, MininetWithControlNet -from mininet.node import Host, CPULimitedHost, Controller, OVSController, NOX -from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch +from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, NOX, + RemoteController, UserSwitch, OVSKernelSwitch, + OVSLegacyKernelSwitch ) from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo @@ -60,7 +61,8 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), SWITCHDEF = 'ovsk' SWITCHES = { 'user': UserSwitch, - 'ovsk': OVSKernelSwitch } + 'ovsk': OVSKernelSwitch, + 'ovsl': OVSLegacyKernelSwitch } HOSTDEF = 'proc' HOSTS = { 'proc': Host, From 74ea006d928874144175079ddc9e6133e477db40 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 23 Mar 2012 18:17:08 -0700 Subject: [PATCH 084/250] Increase the quota and cpu fraction to get max cfs performance. --- examples/cpu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cpu.py b/examples/cpu.py index 0465967..c03aa2b 100755 --- a/examples/cpu.py +++ b/examples/cpu.py @@ -24,7 +24,7 @@ def waitListening(client, server, port): sleep(.5) -def bwtest( cpuLimits, period_us=10000, seconds=5 ): +def bwtest( cpuLimits, period_us=100000, seconds=5 ): """Example/test of link and CPU bandwidth limits cpu: cpu limit as fraction of overall CPU time""" @@ -76,6 +76,6 @@ def dump( results ): if __name__ == '__main__': setLogLevel( 'info' ) - limits = [ .4, .3, .2, .1 ] + limits = [ .45, .4, .3, .2, .1 ] results = bwtest( limits ) dump( results ) From beb05a71c84849d813bee389576d67b1f63f2ebb Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 23 Mar 2012 18:17:49 -0700 Subject: [PATCH 085/250] Move dumpNetConnections to util() because it's useful! --- mininet/cli.py | 15 ++------------- mininet/util.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/mininet/cli.py b/mininet/cli.py index 058a330..6beb047 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -33,7 +33,7 @@ import sys from mininet.log import info, output, error from mininet.term import makeTerms -from mininet.util import quietRun, isShellBuiltin +from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections class CLI( Cmd ): "Simple command-line interface to talk to nodes." @@ -109,21 +109,10 @@ class CLI( Cmd ): nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] ) output( 'available nodes are: \n%s\n' % nodes ) - @staticmethod - def dump_connections( node ): - "Helper method: dump connections to node" - for intf in node.intfList(): - if intf.link: - intfs = [ intf.link.intf1, intf.link.intf2 ] - intfs.remove( intf ) - output( ' %s' % intfs[ 0 ].node ) def do_net( self, _line ): "List network connections." - for node in self.nodelist: - output( node.name, '<->' ) - self.dump_connections( node ) - output( '\n' ) + dumpNodeConnections( self.nodelist ) def do_sh( self, line ): "Run an external shell command" diff --git a/mininet/util.py b/mininet/util.py index bd61fae..04a91cb 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -187,6 +187,31 @@ def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ): printError: if true, print error""" retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError ) +# Support for dumping network + +def dumpNodeConnections( nodes ): + "Dump connections to/from nodes." + + def dumpConnections( node ): + "Helper function: dump connections to node" + for intf in node.intfList(): + output( ' %s:' % intf ) + if intf.link: + intfs = [ intf.link.intf1, intf.link.intf2 ] + intfs.remove( intf ) + output( intfs[ 0 ] ) + else: + output( ' ' ) + + for node in nodes: + output( node.name ) + dumpConnections( node ) + output( '\n' ) + +def dumpNetConnections( net ): + "Dump connections in network" + nodes = net.controllers + net.switches + net.hosts + dumpNodeConnections( nodes ) # IP and Mac address formatting and parsing From 44af37bc2b10c67e8b550d21a1eb6eedfbc41b8c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 23 Mar 2012 18:18:48 -0700 Subject: [PATCH 086/250] Change default period to 100 ms, which seems to help cfs at least... rt is still somewhat broken. --- mininet/node.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 9a87468..af5cb09 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -518,7 +518,11 @@ class CPULimitedHost( Host ): self.cgroup = 'cpu,cpuacct:/' + self.name errFail( 'cgcreate -g ' + self.cgroup ) errFail( 'cgclassify -g %s %s' % ( self.cgroup, self.pid ) ) - self.period_us = kwargs.get( 'period_us', 10000 ) + # BL: Setting the correct period/quota is tricky, particularly + # for RT. RT allows very small quotas, but the overhead + # seems to be high. CFS has a mininimum quota of 1 ms, but + # still does better with larger period values. + self.period_us = kwargs.get( 'period_us', 100000 ) self.sched = sched def cgroupSet( self, param, value, resource='cpu' ): From e8146dd1301fba21d3566bc84a5056d6e0c11558 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 23 Mar 2012 18:36:13 -0700 Subject: [PATCH 087/250] Change to allow addLink() without specifying ports. --- mininet/net.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index f56daa5..5798b28 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -95,7 +95,7 @@ from time import sleep from mininet.cli import CLI from mininet.log import info, error, debug, output from mininet.node import Host, OVSKernelSwitch, Controller -from mininet.link import Link +from mininet.link import Link, Intf from mininet.util import quietRun, fixLimits from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd from mininet.term import cleanUpScreens, makeTerms @@ -104,7 +104,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=None, + controller=Controller, link=Link, intf=Intf, build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', inNamespace=False, autoSetMacs=False, autoStaticArp=False, listenPort=None ): @@ -203,21 +203,28 @@ class Mininet( object ): self.controllers.append( controller_new ) self.nameToNode[ name ] = controller_new return controller_new - + # BL: is this better than just using nameToNode[] ? # Should it have a better name? def getNodeByName( self, nodeName ): "Return node with given name" return self.nameToNode[ nodeName ] - def addLink( self, src, dst, srcPort=None, dstPort=None, + def addLink( self, node1, node2, port1=None, port2=None, cls=None, **params ): - "Add a link from topo" - if self.intf and not 'intf' in params: - params[ 'intf' ] = self.intf + """"Add a link from node1 to node2 + node1: source node + node2: dest node + port1: source port + port2: dest port + returns: link object""" + defaults = { 'port1': port1, + 'port2': port2, + 'intf': self.intf } + defaults.update( params ) if not cls: cls = self.link - return cls( src, dst, srcPort, dstPort, **params ) + return cls( node1, node2, **defaults ) def configHosts( self ): "Configure a set of hosts." @@ -260,9 +267,13 @@ class Mininet( object ): info( '\n*** Adding links:\n' ) for srcName, dstName in topo.links(sort=True): src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ] + params = topo.linkInfo( srcName, dstName ) srcPort, dstPort = topo.port( srcName, dstName ) - self.addLink( src, dst, srcPort, dstPort, - **topo.linkInfo( srcName, dstName ) ) + if not params: + params = {} + params.setdefault( 'port1', srcPort) + params.setdefault( 'port2', dstPort) + self.addLink( src, dst, **params ) info( '(%s, %s) ' % ( src.name, dst.name ) ) info( '\n' ) From 8139695d46c2f31fbea68bb55a52d47028f3cf93 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 23 Mar 2012 18:37:32 -0700 Subject: [PATCH 088/250] Use 's%s' for bw speedup; change burst to fix tbf and htb performance. --- mininet/link.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index 3c12be8..c1d3b73 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -189,21 +189,30 @@ class TCIntf( Intf ): elif bw is not None: # BL: this seems a bit brittle... if ( speedup > 0 and - self.node.name[0:2] == 'sw' ): + self.node.name[0:1] == 's' ): bw = speedup + # BL: As far as I can discern, + # burst is the max number of bytes we can send in 1 ms, but + # this may not actually be correct! Why 1 ms? + burst = bw * 1e6 / 8 * .001 if use_hfsc: cmds = [ '%s qdisc add dev %s root handle 1:0 hfsc default 1', - 'class add dev %s parent 1:0 classid 1:1 hfsc sc ' + '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] elif use_tbf: - latency_us = 10 * 1500 * 8 / bw + # was: latency_us = 10 * 1500 * 8 / bw + latency_us = 1000 cmds = ['%s qdisc add dev %s root handle 1: tbf ' + - 'rate %fMbit burst 15000 latency %fus' % - (bw, latency_us) ] + 'rate %fMbit burst %f latency %fus' % + ( bw, burst, latency_us ) ] else: + # This may not be correct - we should look more closely + # at the semantics of burst and cburst to make sure we + # are specifying the correct sizes. cmds = [ '%s qdisc add dev %s root handle 1:0 htb default 1', '%s class add dev %s parent 1:0 classid 1:1 htb ' + - 'rate %fMbit burst 15k' % bw ] + 'rate %fMbit burst %f cburst %f' % + ( bw, burst, burst ) ] parent = ' parent 1:1 ' # ECN or RED From 612b21cbe7c808648fce6d188fd80ee60e8a4cca Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 23 Mar 2012 18:38:49 -0700 Subject: [PATCH 089/250] Pass code check. --- bin/mn | 4 ++-- examples/cpu.py | 10 +++++----- examples/limit.py | 3 ++- mininet/cli.py | 1 - mininet/node.py | 9 ++++----- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/bin/mn b/bin/mn index 5b00148..e9d3fd5 100755 --- a/bin/mn +++ b/bin/mn @@ -20,8 +20,8 @@ from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info, warn from mininet.net import Mininet, MininetWithControlNet -from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, NOX, - RemoteController, UserSwitch, OVSKernelSwitch, +from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, + NOX, RemoteController, UserSwitch, OVSKernelSwitch, OVSLegacyKernelSwitch ) from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo diff --git a/examples/cpu.py b/examples/cpu.py index c03aa2b..34426bc 100755 --- a/examples/cpu.py +++ b/examples/cpu.py @@ -60,10 +60,10 @@ def bwtest( cpuLimits, period_us=100000, seconds=5 ): def dump( results ): "Dump results" - format = '%s\t%s\t%s' + fmt = '%s\t%s\t%s' print - print format % ( 'sched', 'cpu', 'client MB/s' ) + print fmt % ( 'sched', 'cpu', 'client MB/s' ) print for sched in sorted( results.keys() ): @@ -71,11 +71,11 @@ def dump( results ): for cpu, bps in entries: pct = '%.2f%%' % ( cpu * 100 ) mbps = bps / 1e6 - print format % ( sched, pct, mbps ) + print fmt % ( sched, pct, mbps ) if __name__ == '__main__': setLogLevel( 'info' ) limits = [ .45, .4, .3, .2, .1 ] - results = bwtest( limits ) - dump( results ) + out = bwtest( limits ) + dump( out ) diff --git a/examples/limit.py b/examples/limit.py index 79e0178..736ab41 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -50,6 +50,7 @@ def limit( bw=10, cpu=.4 ): net.stop() def verySimpleLimit( bw=150 ): + "Absurdly simple limiting test" intf = custom( TCIntf, bw=bw ) net = Mininet( intf=intf ) h1, h2 = net.addHost( 'h1' ), net.addHost( 'h2' ) @@ -62,7 +63,7 @@ def verySimpleLimit( bw=150 ): h1.cmdPrint( 'tc -s qdisc ls dev', h1.defaultIntf() ) h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() ) net.stop() - + if __name__ == '__main__': setLogLevel( 'info' ) verySimpleLimit() diff --git a/mininet/cli.py b/mininet/cli.py index 6beb047..52a6760 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -109,7 +109,6 @@ class CLI( Cmd ): nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] ) output( 'available nodes are: \n%s\n' % nodes ) - def do_net( self, _line ): "List network connections." dumpNodeConnections( self.nodelist ) diff --git a/mininet/node.py b/mininet/node.py index af5cb09..6eebc0d 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -219,8 +219,8 @@ class Node( object ): # print sentinel cmd += '; printf "\\177"' if printPid and not isShellBuiltin( cmd ): - cmd = 'mnexec -p ' + cmd - self.write( cmd + '\n' ) + cmd = 'mnexec -p ' + cmd + self.write( cmd + '\n' ) self.lastPid = None self.waiting = True @@ -545,7 +545,7 @@ class CPULimitedHost( Host ): def cgroupDel( self ): "Clean up our cgroup" # info( '*** deleting cgroup', self.cgroup, '\n' ) - out, err, exitcode = errRun( 'cgdelete -r ' + self.cgroup ) + _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup ) return exitcode != 0 def cleanup( self ): @@ -753,7 +753,7 @@ class OVSLegacyKernelSwitch( Switch ): dp: netlink id (0, 1, 2, ...) defaultMAC: default MAC as unsigned int; random value if None""" Switch.__init__( self, name, **kwargs ) - self.dp = self.name + self.dp = dp if dp else self.name self.intf = self.dp if self.inNamespace: error( "OVSKernelSwitch currently only works" @@ -767,7 +767,6 @@ class OVSLegacyKernelSwitch( Switch ): moduleName='Open vSwitch (openvswitch.org)') moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) - def start( self, controllers ): "Start up kernel datapath." ofplog = '/tmp/' + self.name + '-ofp.log' From 00d9b7803559f1669e0dcc69657e70b895ca43a7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 23 Mar 2012 18:41:25 -0700 Subject: [PATCH 090/250] Reinstate more complicated test. --- examples/limit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/limit.py b/examples/limit.py index 736ab41..6babe7b 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -66,4 +66,5 @@ def verySimpleLimit( bw=150 ): if __name__ == '__main__': setLogLevel( 'info' ) - verySimpleLimit() + limit() + From 2d924f8a6545bfe25a7a2d89c3ab844adc03ec88 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 25 Mar 2012 15:38:32 -0700 Subject: [PATCH 091/250] Add Mininet object to locals as 'net' --- mininet/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mininet/cli.py b/mininet/cli.py index 52a6760..79e55a4 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -46,6 +46,9 @@ class CLI( Cmd ): self.nodemap = {} # map names to Node objects for node in self.nodelist: self.nodemap[ node.name ] = node + # Local variable bindings for py command + self.locals = { 'net': mininet } + self.locals.update( self.nodemap ) # Attempt to handle input self.stdin = stdin self.inPoller = poll() @@ -124,7 +127,7 @@ class CLI( Cmd ): """Evaluate a Python expression. Node names may be used, e.g.: h1.cmd('ls')""" try: - result = eval( line, globals(), self.nodemap ) + result = eval( line, globals(), self.locals ) if not result: return elif isinstance( result, str ): From e1246c374152371604bc0c41178527084798b735 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 25 Mar 2012 15:39:18 -0700 Subject: [PATCH 092/250] Simplify port specification. For the moment, I've removed the ability to specify a dict of options without using **. This is a slightly unfortunate trade-off since it simplifies implementation at the expense of making the API slightly less convenient (if somewhat more consistent.) --- mininet/net.py | 6 +-- mininet/topo.py | 127 ++++++++++++++++++++---------------------------- 2 files changed, 54 insertions(+), 79 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 5798b28..135163b 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -269,11 +269,7 @@ class Mininet( object ): src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ] params = topo.linkInfo( srcName, dstName ) srcPort, dstPort = topo.port( srcName, dstName ) - if not params: - params = {} - params.setdefault( 'port1', srcPort) - params.setdefault( 'port2', dstPort) - self.addLink( src, dst, **params ) + self.addLink( src, dst, srcPort, dstPort, **params ) info( '(%s, %s) ' % ( src.name, dst.name ) ) info( '\n' ) diff --git a/mininet/topo.py b/mininet/topo.py index e80517a..8c738c2 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -30,83 +30,73 @@ class Topo(object): 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 lopts + self.sopts = {} if sopts is None else sopts self.lopts = {} if lopts is None else lopts self.ports = {} # ports[src][dst] is port on src that connects to dst - def add_node(self, name, *args, **opts): + def add_node(self, name, **opts): """Add Node to graph. - add_node('name', dict) add_node('name', **opts) name: name - args: dict of node options - opts: node options""" + opts: node options + returns: node name""" self.g.add_node(name) - if args and type(args[0]) is dict: - opts = args[0] self.node_info[name] = opts return name - def add_host(self, name, *args, **opts): + def add_host(self, name, **opts): """Convenience method: Add host to graph. - add_host('name', dict) add_host('name', **opts) - name: name - args: dict of node options - opts: node options""" + name: host name + opts: host options + returns: host name""" if not opts and self.hopts: opts = self.hopts - return self.add_node(name, *args, **opts) + return self.add_node(name, **opts) def add_switch(self, name, **opts): """Convenience method: Add switch to graph. - add_switch('name', dict) add_switch('name', **opts) - name: name - args: dict of node options - opts: node options""" + name: switch name + opts: switch options + returns: switch name""" if not opts and self.sopts: opts = self.sopts result = self.add_node(name, is_switch=True, **opts) return result - def add_link(self, src, dst, *args, **opts): - """Add link (Node, Node) to topo. - add_link(src, dst, dict) add_link(src, dst, **opts) - src: src name - dst: dst name - args: dict of node options - params: link parameters""" - src, dst = sorted([src, dst], key=naturalSeq) - self.g.add_edge(src, dst) - if args and type(args[0]) is dict: - opts = args[0] - if not opts and self.sopts: - opts = self.sopts - self.link_info[(src, dst)] = opts - self.add_port(src, dst) - return src, dst + def add_link(self, node1, node2, port1=None, port2=None, + *default, **opts): + """node1, node2: nodes to link together + port1, port2: ports (optional) + opts: link options (optional) + returns: link info key""" + if not opts and self.lopts: + opts = self.lopts + self.add_port(node1, node2, port1, port2) + key = tuple(self.sorted([node1, node2])) + self.link_info[key] = opts + self.g.add_edge(*key) + return key - def add_port(self, src, dst): + def add_port(self, src, dst, sport=None, dport=None): '''Generate port mapping for new edge. - - @param src source switch DPID - @param dst destination switch DPID + @param src source switch name + @param dst destination switch name ''' + self.ports.setdefault(src, {}) + self.ports.setdefault(dst, {}) + # New port: number of outlinks + base src_base = 1 if self.is_switch(src) else 0 dst_base = 1 if self.is_switch(dst) else 0 - if src not in self.ports: - self.ports[src] = {} - if dst not in self.ports[src]: - # num outlinks - self.ports[src][dst] = len(self.ports[src]) + src_base - if dst not in self.ports: - self.ports[dst] = {} - if src not in self.ports[dst]: - # num outlinks - self.ports[dst][src] = len(self.ports[dst]) + dst_base + if sport is None: + sport = len(self.ports[src]) + src_base + if dport is None: + dport = len(self.ports[dst]) + dst_base + self.ports[src][dst] = sport + self.ports[dst][src] = dport def nodes(self, sort=True): "Return nodes in graph" if sort: - return sorted( self.g.nodes(), key=natural ) + return self.sorted( self.g.nodes() ) else: return self.g.nodes() @@ -137,7 +127,8 @@ class Topo(object): if not sort: return self.g.edges() else: - return sorted( self.g.edges(), key=naturalSeq ) + links = [tuple(self.sorted(e)) for e in self.g.edges()] + return sorted( links, key=naturalSeq ) def port(self, src, dst): '''Get port number. @@ -154,9 +145,9 @@ class Topo(object): def linkInfo( self, src, dst ): "Return link metadata" - src, dst = sorted((src, dst), key=naturalSeq) + src, dst = self.sorted([src, dst]) return self.link_info[(src, dst)] - + def nodeInfo( self, name ): "Return metadata (dict) for node" info = self.node_info[ name ] @@ -197,31 +188,19 @@ class SingleSwitchReversedTopo(SingleSwitchTopo): Useful to verify that Mininet properly handles custom port numberings. ''' + def __init__(self, k=2, **opts): + '''Init. - def port(self, src, dst): - '''Get port number. - - @param src source switch DPID - @param dst destination switch DPID - @return tuple (src_port, dst_port): - src_port: port on source switch leading to the destination switch - dst_port: port on destination switch leading to the source switch + @param k number of hosts + @param enable_all enables all nodes and switches? ''' - if src == 1: - if dst in range(2, self.k + 2): - dst_index = dst - 2 - highest = self.k - 1 - return (highest - dst_index, 0) - else: - raise Exception('unexpected dst: %i' % dst) - elif src in range(2, self.k + 2): - if dst == 1: - raise Exception('unexpected dst: %i' % dst) - else: - src_index = src - 2 - highest = self.k - 1 - return (0, highest - src_index) - + super(SingleSwitchTopo, self).__init__(**opts) + self.k = k + switch = self.add_switch('s1') + for h in irange(1, k): + host = self.add_host('h%s' % h) + self.add_link(host, switch, + port1=0, port2=(k - h + 1)) class LinearTopo(Topo): "Linear topology of k switches, with one host per switch." From 26c61734dafde009ebe7f0c7dea05d8b9123c28c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 25 Mar 2012 15:41:02 -0700 Subject: [PATCH 093/250] Add cgroup and ethtool dependencies for mininet (w/hifi integration.) --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index fcc027a..1393be4 100755 --- a/util/install.sh +++ b/util/install.sh @@ -123,7 +123,7 @@ function kernel_clean { function mn_deps { echo "Installing Mininet dependencies" $install gcc make screen psmisc xterm ssh iperf iproute \ - python-setuptools python-networkx + python-setuptools python-networkx cgroup-bin ethtool if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then echo "Upgrading networkx to avoid deprecation warning" From 2ec866d2c59e6aba6aa82ee127aa0e262b8be002 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 25 Mar 2012 20:18:35 -0700 Subject: [PATCH 094/250] TCLink: pass correct parameters to superclass. --- mininet/link.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index c1d3b73..0aa05cd 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -387,8 +387,8 @@ class TCLink( Link ): "Link with symmetric TC interfaces configured via opts" def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None, **params ): - Link.__init__( self, node1, node2, port1=None, port2=None, - intfName1=None, intfName2=None, + Link.__init__( self, node1, node2, port1=port1, port2=port2, + intfName1=intfName1, intfName2=intfName2, cls1=TCIntf, cls2=TCIntf, params1=params, From 3f61ea710467f6bfe9505dde31878461776c0a83 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 25 Mar 2012 22:10:04 -0700 Subject: [PATCH 095/250] Restore deleted deleteIntfs in OVSSwitch.stop() --- mininet/node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mininet/node.py b/mininet/node.py index 6eebc0d..cee6b67 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -865,6 +865,7 @@ class OVSSwitch( Switch ): def stop( self ): "Terminate OVS switch." self.cmd( 'ovs-vsctl del-br', self ) + self.deleteIntfs() OVSKernelSwitch = OVSSwitch From e5653fb63bff7638b2899f39f95482393c9786a2 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 27 Mar 2012 00:31:37 -0700 Subject: [PATCH 096/250] Change back to match mininet-hifi, except for max_queue_len=1000. --- mininet/link.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index 0aa05cd..3aa071d 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -191,28 +191,24 @@ class TCIntf( Intf ): if ( speedup > 0 and self.node.name[0:1] == 's' ): bw = speedup - # BL: As far as I can discern, - # burst is the max number of bytes we can send in 1 ms, but - # this may not actually be correct! Why 1 ms? burst = bw * 1e6 / 8 * .001 + # This may not be correct - we should look more closely + # at the semantics of burst (and cburst) to make sure we + # are specifying the correct sizes. For now I have used + # the same settings we had in the mininet-hifi code. if use_hfsc: cmds = [ '%s qdisc add dev %s root handle 1:0 hfsc default 1', '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] elif use_tbf: - # was: latency_us = 10 * 1500 * 8 / bw - latency_us = 1000 + latency_us = 10 * 1500 * 8 / bw cmds = ['%s qdisc add dev %s root handle 1: tbf ' + - 'rate %fMbit burst %f latency %fus' % - ( bw, burst, latency_us ) ] + 'rate %fMbit burst 15000 latency %fus' % + ( bw, latency_us ) ] else: - # This may not be correct - we should look more closely - # at the semantics of burst and cburst to make sure we - # are specifying the correct sizes. cmds = [ '%s qdisc add dev %s root handle 1:0 htb default 1', '%s class add dev %s parent 1:0 classid 1:1 htb ' + - 'rate %fMbit burst %f cburst %f' % - ( bw, burst, burst ) ] + 'rate %fMbit burst 15k' % bw ] parent = ' parent 1:1 ' # ECN or RED From ece14ff4b5152c0f64312283fc29c6747212c469 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 31 Mar 2012 20:48:12 -0700 Subject: [PATCH 097/250] Check out CS244 branch for class. --- util/vm/install-mininet-vm.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/vm/install-mininet-vm.sh b/util/vm/install-mininet-vm.sh index 9347307..0c9aa1a 100644 --- a/util/vm/install-mininet-vm.sh +++ b/util/vm/install-mininet-vm.sh @@ -20,8 +20,8 @@ sudo apt-get update sudo apt-get -y install git-core openssh-server git clone git://github.com/mininet/mininet cd mininet -# Currently ovs-1.4-compat; will change to testing or master -git checkout -b 1.4 origin/devel/ovs-1.4-compat +# Check out branch for cs244 +git checkout -b cs244 origin/class/cs244 cd time mininet/util/install.sh if ! grep NOX_CORE_DIR .bashrc; then From 1dd3de0d04517ed3d1366070c6ba08e9117c8b40 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 31 Mar 2012 21:29:09 -0700 Subject: [PATCH 098/250] Remove unused burst. --- mininet/link.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mininet/link.py b/mininet/link.py index 3aa071d..74d9720 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -191,7 +191,6 @@ class TCIntf( Intf ): if ( speedup > 0 and self.node.name[0:1] == 's' ): bw = speedup - burst = bw * 1e6 / 8 * .001 # This may not be correct - we should look more closely # at the semantics of burst (and cburst) to make sure we # are specifying the correct sizes. For now I have used From 78606a35c9ef1933e71a41d2ec5d8598abb7f77b Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 31 Mar 2012 21:29:27 -0700 Subject: [PATCH 099/250] Removed unused param in add_link. --- mininet/topo.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index 8c738c2..9ee0a02 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -62,8 +62,8 @@ class Topo(object): result = self.add_node(name, is_switch=True, **opts) return result - def add_link(self, node1, node2, port1=None, port2=None, - *default, **opts): + def add_link(self, node1, node2, port1=None, port2=None, + **opts): """node1, node2: nodes to link together port1, port2: ports (optional) opts: link options (optional) @@ -147,7 +147,7 @@ class Topo(object): "Return link metadata" src, dst = self.sorted([src, dst]) return self.link_info[(src, dst)] - + def nodeInfo( self, name ): "Return metadata (dict) for node" info = self.node_info[ name ] @@ -181,7 +181,7 @@ class SingleSwitchTopo(Topo): self.add_link(host, switch) -class SingleSwitchReversedTopo(SingleSwitchTopo): +class SingleSwitchReversedTopo(Topo): '''Single switch connected to k hosts, with reversed ports. The lowest-numbered host is connected to the highest-numbered port. @@ -194,12 +194,12 @@ class SingleSwitchReversedTopo(SingleSwitchTopo): @param k number of hosts @param enable_all enables all nodes and switches? ''' - super(SingleSwitchTopo, self).__init__(**opts) + super(SingleSwitchReversedTopo, self).__init__(**opts) self.k = k switch = self.add_switch('s1') for h in irange(1, k): host = self.add_host('h%s' % h) - self.add_link(host, switch, + self.add_link(host, switch, port1=0, port2=(k - h + 1)) class LinearTopo(Topo): From c1a6ae2b486ef09b8b1bc35284b9bd5e7217f423 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 31 Mar 2012 21:29:56 -0700 Subject: [PATCH 100/250] Remove blank line. --- examples/limit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/limit.py b/examples/limit.py index 6babe7b..fbda428 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -67,4 +67,3 @@ def verySimpleLimit( bw=150 ): if __name__ == '__main__': setLogLevel( 'info' ) limit() - From 5d6fda932d48c0c3c2ff1e089563401a4924bc30 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 31 Mar 2012 21:30:16 -0700 Subject: [PATCH 101/250] Add openvswitch-datapath-dkms if no datapath installed. --- util/install.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/util/install.sh b/util/install.sh index 1393be4..b6d94ab 100755 --- a/util/install.sh +++ b/util/install.sh @@ -237,6 +237,12 @@ function ovs { # Otherwise try distribution's OVS packages if [ "$DIST" = "Ubuntu" ] && [ `echo "$RELEASE >= 11.10" | bc` = 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 return fi From d776bd3a4f338b917c6ba3cfb667bc0823e6577d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 2 Apr 2012 16:39:37 -0700 Subject: [PATCH 102/250] Add handle 10: to netem for hifi compat, reconfiguration. --- mininet/link.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mininet/link.py b/mininet/link.py index 74d9720..9f46dcb 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -245,7 +245,8 @@ class TCIntf( Intf ): 'limit %d' % max_queue_size if max_queue_size is not None else '' ) if netemargs: - cmds = [ '%s qdisc add dev %s ' + parent + ' netem ' + + cmds = [ '%s qdisc add dev %s ' + parent + + ' handle 10: netem ' + netemargs ] return cmds From a9c28885f3bddf5c59263af6cd799e1fb34b4fc9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Apr 2012 17:22:28 -0700 Subject: [PATCH 103/250] Bring up loopback interface when configuring hosts. --- mininet/net.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mininet/net.py b/mininet/net.py index 135163b..d1dafab 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -235,6 +235,9 @@ class Mininet( object ): # BL: do we want to do this here or not? # May not make sense if we have CPU lmiting... # quietRun( 'renice +18 -p ' + repr( host.pid ) ) + # This may not be the right place to do this, but + # it needs to be done somewhere. + host.cmd( 'ifconfig lo up' ) info( '\n' ) def buildFromTopo( self, topo=None ): From 1bb990357fbcd1478c4eb37a982512572a027ec8 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Apr 2012 17:32:55 -0700 Subject: [PATCH 104/250] Added multipoll and multiping examples. --- examples/README | 9 +++++ examples/multiping.py | 94 +++++++++++++++++++++++++++++++++++++++++++ examples/multipoll.py | 83 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100755 examples/multiping.py create mode 100755 examples/multipoll.py diff --git a/examples/README b/examples/README index 984c9a3..696a34e 100644 --- a/examples/README +++ b/examples/README @@ -35,6 +35,15 @@ miniedit.py: This example demonstrates creating a network via a graphical editor. +multiping.py: + +This example demonstrates one method for +monitoring output from multiple hosts, using node.monitor(). + +multipoll.py: + +This example demonstrates monitoring output files from multiple hosts. + multitest.py: This example creates a network and runs multiple tests on it. diff --git a/examples/multiping.py b/examples/multiping.py new file mode 100755 index 0000000..6729204 --- /dev/null +++ b/examples/multiping.py @@ -0,0 +1,94 @@ +#!/usr/bin/python + +""" +multiping.py: monitor multiple sets of hosts using ping + +This demonstrates how one may send a simple shell script to +multiple hosts and monitor their output interactively for a period= +of time. +""" + +from mininet.net import Mininet +from mininet.node import Node +from mininet.topo import SingleSwitchTopo +from mininet.log import setLogLevel + +from select import poll, POLLIN +from time import time + +def chunks( l, n ): + "Divide list l into chunks of size n - thanks Stackoverflow" + return [ l[ i : i + n ] for i in range( 0, len( l ), n ) ] + +def startpings( host, targetips ): + "Tell host to repeatedly ping targets" + + targetips.append( '10.0.0.200' ) + + targetips = ' '.join( targetips ) + + # BL: Not sure why loopback intf isn't up! + host.cmd( 'ifconfig lo up' ) + + + # Simple ping loop + cmd = ( 'while true; do ' + ' for ip in %s; do ' % targetips + + ' echo -n %s "->" $ip ' % host.IP() + + ' `ping -c1 -w 1 $ip | grep packets` ;' + ' sleep 1;' + ' done; ' + 'done &' ) + + print ( '*** Host %s (%s) will be pinging ips: %s' % + ( host.name, host.IP(), targetips ) ) + + host.cmd( cmd ) + +def multiping( netsize, chunksize, seconds): + "Ping subsets of size chunksize in net of size netsize" + + # Create network and identify subnets + topo = SingleSwitchTopo( netsize ) + net = Mininet( topo=topo ) + net.start() + hosts = net.hosts + subnets = chunks( hosts, chunksize ) + + # Create polling object + fds = [ host.stdout.fileno() for host in hosts ] + poller = poll() + for fd in fds: + poller.register( fd, POLLIN ) + + # Start pings + for subnet in subnets: + ips = [ host.IP() for host in subnet ] + for host in subnet: + startpings( host, ips ) + + # Monitor output + endTime = time() + seconds + while time() < endTime: + readable = poller.poll(1000) + for fd, _mask in readable: + node = Node.outToNode[ fd ] + print '%s:' % node.name, node.monitor().strip() + + # Stop pings + for host in hosts: + host.cmd( 'kill %while' ) + + net.stop() + + +if __name__ == '__main__': + setLogLevel( 'info' ) + multiping( netsize=20, chunksize=4, seconds=10 ) + + + + + + + \ No newline at end of file diff --git a/examples/multipoll.py b/examples/multipoll.py new file mode 100755 index 0000000..a3ffcc6 --- /dev/null +++ b/examples/multipoll.py @@ -0,0 +1,83 @@ +#!/usr/bin/python + +""" +Simple example of sending output to multiple files and +monitoring them +""" + +from mininet.topo import SingleSwitchTopo +from mininet.net import Mininet +from mininet.log import setLogLevel +from mininet.util import quietRun + +from time import time, sleep +from select import poll, POLLIN +from subprocess import Popen, PIPE + +def monitorFiles( outfiles, seconds, timeoutms ): + devnull = open( '/dev/null', 'w' ) + tails, fdToFile, fdToHost = {}, {}, {} + for h, outfile in outfiles.iteritems(): + tail = Popen( [ 'tail', '-f', outfile ], + stdout=PIPE, stderr=devnull ) + fd = tail.stdout.fileno() + tails[ h ] = tail + fdToFile[ fd ] = tail.stdout + fdToHost[ fd ] = h + # Prepare to poll output files + readable = poll() + for t in tails.values(): + readable.register( t.stdout.fileno(), POLLIN ) + # Run until a set number of seconds have elapsed + endTime = time() + seconds + while time() < endTime: + fdlist = readable.poll(timeoutms) + if fdlist: + for fd, _flags in fdlist: + f = fdToFile[ fd ] + host = fdToHost[ fd ] + # Wait for a line of output + line = f.readline().strip() + yield host, line + else: + # If we timed out, return nothing + yield None, '' + for t in tails.values(): + t.terminate() + devnull.close() # Not really necessary + + +def monitorTest( N=3, seconds=3 ): + "Run pings and monitor multiple hosts" + topo = SingleSwitchTopo( N ) + net = Mininet( topo ) + net.start() + hosts = net.hosts + print "Starting test..." + server = hosts[ 0 ] + outfiles, errfiles = {}, {} + for h in hosts: + # Create and/or erase output files + outfiles[ h ] = '/tmp/%s.out' % h.name + errfiles[ h ] = '/tmp/%s.err' % h.name + h.cmd( 'echo >', outfiles[ h ] ) + h.cmd( 'echo >', errfiles[ h ] ) + # Start pings + h.cmdPrint('ping', server.IP(), + '>', outfiles[ h ], + '2>', errfiles[ h ], + '&' ) + print "Monitoring output for", seconds, "seconds" + for h, line in monitorFiles( outfiles, seconds, timeoutms=500 ): + if h: + print '%s: %s' % ( h.name, line ) + for h in hosts: + h.cmd('kill %ping') + net.stop() + + +if __name__ == '__main__': + setLogLevel('info') + monitorTest() + + From 32f7847bca4bd2a7f49142622551e6978455de45 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Apr 2012 17:50:59 -0700 Subject: [PATCH 105/250] Change doxypy.py to doxypy. --- util/doxify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/doxify.py b/util/doxify.py index 62e40ac..f9f60ad 100755 --- a/util/doxify.py +++ b/util/doxify.py @@ -82,7 +82,7 @@ if __name__ == '__main__': fixLines( infile.readlines(), outfid ) infile.close() os.close( outfid ) - call( [ 'doxypy.py', outname ] ) + call( [ 'doxypy', outname ] ) From d08d101eba147ce8d91ccad21c52443c08e80753 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Apr 2012 18:49:39 -0700 Subject: [PATCH 106/250] Added simpleperf.py to examples. --- examples/README | 4 ++++ examples/simpleperf.py | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 examples/simpleperf.py diff --git a/examples/README b/examples/README index 696a34e..3a05f9b 100644 --- a/examples/README +++ b/examples/README @@ -54,6 +54,10 @@ These two examples demonstrate how to create a network by using the lowest- level Mininet functions. Generally the higher-level API is easier to use, but scratchnet shows what is going on behind the scenes. +simpleperf.py: + +A simple example of configuring network and CPU bandwidth limits. + sshd.py: This example shows how to run an sshd process in each host, allowing diff --git a/examples/simpleperf.py b/examples/simpleperf.py new file mode 100644 index 0000000..11b3c17 --- /dev/null +++ b/examples/simpleperf.py @@ -0,0 +1,45 @@ +#!/usr/bin/python + +""" +Simple example of setting network and CPU parameters +""" + +from mininet.topo import Topo +from mininet.net import Mininet +from mininet.node import CPULimitedHost +from mininet.link import TCLink +from mininet.util import dumpNodeConnections +from mininet.log import setLogLevel + +class SingleSwitchTopo(Topo): + "Single switch connected to n hosts." + def __init__(self, n=2, **opts): + Topo.__init__(self, **opts) + switch = self.add_switch('s1') + for h in range(n+1): + # Each host gets 50%/n of system CPU + host = self.add_host('h%s' % (h + 1), + cpu=.5/n) + # 10 Mbps, 5ms delay, 10% loss + self.add_link(host, switch, + bw=10, delay='5ms', loss=10, use_htb=True) + +def perfTest(): + "Create network and run simple performance test" + topo = SingleSwitchTopo(n=4) + net = Mininet(topo=topo, + host=CPULimitedHost, link=TCLink) + net.start() + print "Dumping host connections" + dumpNodeConnections(net.hosts) + print "Testing network connectivity" + net.pingAll() + print "Testing bandwidth between h1 and h4" + h1 = net.getNodeByName('h1') + h4 = net.getNodeByName('h4') + net.iperf((h1, h4)) + net.stop() + +if __name__ == '__main__': + setLogLevel('info') + perfTest() From a4338de38c4ad19bfe75c338b92b29e166547801 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 5 Apr 2012 20:57:29 -0700 Subject: [PATCH 107/250] Fix error message. --- bin/mn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/mn b/bin/mn index e9d3fd5..29f938c 100755 --- a/bin/mn +++ b/bin/mn @@ -44,8 +44,8 @@ def customNode( constructors, argStr ): if not newargs: return constructor( name, *args, **params ) if args: - warn( 'warning: %s replacing %s with %s\n', - constructor, args, newargs ) + warn( 'warning: %s replacing %s with %s\n' % ( + constructor, args, newargs ) ) return constructor( name, *newargs, **params ) return customized From 50202e124661d9bb5d862ce0415cae112f7d6a70 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 5 Apr 2012 21:38:02 -0700 Subject: [PATCH 108/250] Off by one... I dislike range() --- examples/simpleperf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simpleperf.py b/examples/simpleperf.py index 11b3c17..12ca736 100644 --- a/examples/simpleperf.py +++ b/examples/simpleperf.py @@ -16,7 +16,7 @@ class SingleSwitchTopo(Topo): def __init__(self, n=2, **opts): Topo.__init__(self, **opts) switch = self.add_switch('s1') - for h in range(n+1): + for h in range(n): # Each host gets 50%/n of system CPU host = self.add_host('h%s' % (h + 1), cpu=.5/n) From a7648e78fbcbc4dfcb56a2aa824362a0393a935a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 8 Apr 2012 17:22:01 -0700 Subject: [PATCH 109/250] Add mountCgroups() and tweak/correct fixLimits() --- mininet/util.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mininet/util.py b/mininet/util.py index 04a91cb..8fe3f36 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -305,8 +305,19 @@ def makeNumeric( s ): def fixLimits(): "Fix ridiculously small resource limits." - setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) ) - setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) ) + setrlimit( RLIMIT_NPROC, ( 8192, 8192 ) ) + setrlimit( RLIMIT_NOFILE, ( 16384, 16384 ) ) + +def mountCgroups(): + "Make sure cgroups file system is mounted" + mounts = quietRun( 'mount' ) + cgdir = '/sys/fs/cgroup' + csdir = cgdir + '/cpuset' + if 'cgroups on %s' % cgdir not in mounts: + raise Exception( "cgroups not mounted on " + cgdir ) + if 'cpuset on %s' % csdir not in mounts: + errRun( 'mkdir -p', csdir ) + errRun( 'mount -t cgroup -ocpuset cpuset', csdir ) def natural( text ): "To sort sanely/alphabetically: sorted( l, key=natural )" From 197b083fbc6f31f6452ea438314de5072b323bf9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 8 Apr 2012 17:22:30 -0700 Subject: [PATCH 110/250] Add static cpu (and memory) assignment. --- bin/mn | 7 ++++++- mininet/net.py | 16 ++++++++++++---- mininet/node.py | 43 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/bin/mn b/bin/mn index 29f938c..16fdc94 100755 --- a/bin/mn +++ b/bin/mn @@ -221,6 +221,9 @@ class MininetRunner( object ): opts.add_option( '--prefixlen', type='int', default=8, help='prefix length (e.g. /8) for automatic ' 'network configuration' ) + opts.add_option( '--pin', action='store_true', + default=False, help="pin hosts to CPU cores " + "(requires --host cfs or --host rt)" ) self.options, self.args = opts.parse_args() @@ -259,6 +262,7 @@ class MininetRunner( object ): xterms = self.options.xterms mac = self.options.mac arp = self.options.arp + pin = self.options.pin listenPort = None if not self.options.nolistenport: listenPort = self.options.listenport @@ -268,7 +272,8 @@ class MininetRunner( object ): ipBase=ipBase, inNamespace=inNamespace, xterms=xterms, autoSetMacs=mac, - autoStaticArp=arp, listenPort=listenPort ) + autoStaticArp=arp, autoPinCpus=pin, + listenPort=listenPort ) if self.options.pre: CLI( mn, script=self.options.pre ) diff --git a/mininet/net.py b/mininet/net.py index d1dafab..82ec2b0 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -96,7 +96,7 @@ from mininet.cli import CLI from mininet.log import info, error, debug, output from mininet.node import Host, OVSKernelSwitch, Controller from mininet.link import Link, Intf -from mininet.util import quietRun, fixLimits +from mininet.util import quietRun, fixLimits, numCores from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd from mininet.term import cleanUpScreens, makeTerms @@ -107,7 +107,8 @@ class Mininet( object ): controller=Controller, link=Link, intf=Intf, build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', inNamespace=False, - autoSetMacs=False, autoStaticArp=False, listenPort=None ): + autoSetMacs=False, autoStaticArp=False, autoPinCpus=False, + listenPort=None ): """Create Mininet object. topo: Topo (topology) object or None switch: default Switch class @@ -120,8 +121,9 @@ class Mininet( object ): xterms: if build now, spawn xterms? cleanup: if build now, cleanup before creating? inNamespace: spawn switches and controller in net namespaces? - autoSetMacs: set MAC addrs from topo dpid? + autoSetMacs: set MAC addrs automatically like IP addresses? autoStaticArp: set all-pairs static MAC addrs? + autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)? listenPort: base listening port to open; will be incremented for each additional switch in the net if inNamespace=False""" self.topo = topo @@ -138,6 +140,9 @@ class Mininet( object ): self.cleanup = cleanup self.autoSetMacs = autoSetMacs self.autoStaticArp = autoStaticArp + self.autoPinCpus = autoPinCpus + self.numCores = numCores() + self.nextCore = 0 # next core for pinning hosts to CPUs self.listenPort = listenPort self.hosts = [] @@ -166,6 +171,9 @@ class Mininet( object ): prefixLen=self.prefixLen ) } if self.autoSetMacs: defaults[ 'mac'] = macColonHex( self.nextIP ) + if self.autoPinCpus: + defaults[ 'cores' ] = self.nextCore + self.nextCore = ( self.nextCore + 1 ) % self.numCores self.nextIP += 1 defaults.update( params ) if not cls: @@ -230,7 +238,7 @@ class Mininet( object ): "Configure a set of hosts." for host in self.hosts: info( host.name + ' ' ) - host.configDefault( defaultRoute=host.defaultIntf ) + host.configDefault( defaultRoute=host.defaultIntf() ) # You're low priority, dude! # BL: do we want to do this here or not? # May not make sense if we have CPU lmiting... diff --git a/mininet/node.py b/mininet/node.py index cee6b67..e8c3806 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -51,8 +51,8 @@ import select from subprocess import Popen, PIPE, STDOUT from mininet.log import info, error, warn, debug -from mininet.util import quietRun, errRun, errFail, moveIntf, isShellBuiltin -from mininet.util import numCores, retry +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 @@ -514,10 +514,15 @@ class CPULimitedHost( Host ): def __init__( self, name, sched='cfs', **kwargs ): Host.__init__( self, name, **kwargs ) + # Initialize class if necessary + if not CPULimitedHost.inited: + CPULimitedHost.init() # Create a cgroup and move shell into it - self.cgroup = 'cpu,cpuacct:/' + self.name + self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name errFail( 'cgcreate -g ' + self.cgroup ) - errFail( 'cgclassify -g %s %s' % ( self.cgroup, self.pid ) ) + # We don't add ourselves to a cpuset because you must + # specify the cpu and memory placement first + errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) ) # BL: Setting the correct period/quota is tricky, particularly # for RT. RT allows very small quotas, but the overhead # seems to be high. CFS has a mininimum quota of 1 ms, but @@ -540,7 +545,7 @@ class CPULimitedHost( Host ): "Return value of cgroup parameter" cmd = 'cgget -r %s.%s /%s' % ( resource, param, self.name ) - return quietRun( cmd ).split()[ -1 ] + return int( quietRun( cmd ).split()[ -1 ] ) def cgroupDel( self ): "Clean up our cgroup" @@ -616,15 +621,41 @@ class CPULimitedHost( Host ): self.chrt( prio=20 ) info( '(%s %d/%dus) ' % ( sched, quota, period ) ) - def config( self, cpu=None, **params ): + def setCPUs( self, cores ): + "Specify (real) cores that our cgroup can run on" + if type( cores ) is list: + cores = ','.join( [ str( c ) for c in cores ] ) + self.cgroupSet( resource='cpuset', param='cpus', + value= cores ) + # Memory placement is probably not relevant, but we + # must specify it anyway + self.cgroupSet( resource='cpuset', param='mems', + value= cores ) + # We have to do this here after we've specified + # cpus and mems + errFail( 'cgclassify -g cpuset:/%s %s' % ( + self.name, self.pid ) ) + + def config( self, cpu=None, cores=None, **params ): """cpu: desired overall system CPU fraction + cores: (real) core(s) this host can run on params: parameters for Node.config()""" r = Node.config( self, **params ) # Was considering cpu={'cpu': cpu , 'sched': sched}, but # that seems redundant self.setParam( r, 'setCPUFrac', cpu=cpu ) + self.setParam( r, 'setCPUs', cores=cores ) return r + inited = False + + @classmethod + def init( cls ): + "Initialization for CPULimitedHost class" + mountCgroups() + cls.inited = True + + # Some important things to note: # # The "IP" address which setIP() assigns to the switch is not From 149a1f56390e3b73d2014bda9e2502c49f950adf Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 8 Apr 2012 17:30:14 -0700 Subject: [PATCH 111/250] Apparently errRun isn't as flexible as I thought... --- mininet/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/util.py b/mininet/util.py index 8fe3f36..6016cd3 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -316,8 +316,8 @@ def mountCgroups(): if 'cgroups on %s' % cgdir not in mounts: raise Exception( "cgroups not mounted on " + cgdir ) if 'cpuset on %s' % csdir not in mounts: - errRun( 'mkdir -p', csdir ) - errRun( 'mount -t cgroup -ocpuset cpuset', csdir ) + errRun( 'mkdir -p ' + csdir ) + errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir ) def natural( text ): "To sort sanely/alphabetically: sorted( l, key=natural )" From 548580d817e80d80719e3cfb4cf7e842bd63fe1c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 9 Apr 2012 23:54:17 +0000 Subject: [PATCH 112/250] Allow lists of nodes to be passed to getNodeByName ....which should perhaps be renamed!!! --- mininet/net.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 82ec2b0..d7a673d 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -214,9 +214,11 @@ class Mininet( object ): # BL: is this better than just using nameToNode[] ? # Should it have a better name? - def getNodeByName( self, nodeName ): - "Return node with given name" - return self.nameToNode[ nodeName ] + def getNodeByName( self, *args ): + "Return node(s) with given name(s)" + if len( args ) == 1: + return self.nameToNode[ args[ 0 ] ] + return [ self.nameToNode[ n ] for n in args ] def addLink( self, node1, node2, port1=None, port2=None, cls=None, **params ): From 350fdbfe5032c25d01c0436ee98fe9d75503af89 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 10 Apr 2012 00:02:56 +0000 Subject: [PATCH 113/250] Allow modules (node.py) to be 1500 lines. Maybe reduce this someday. --- .pylint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylint b/.pylint index de4ac44..1912748 100644 --- a/.pylint +++ b/.pylint @@ -264,7 +264,7 @@ int-import-graph= max-line-length=80 # Maximum number of lines in a module -max-module-lines=1000 +max-module-lines=1500 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). From 92b601aba9852471413d5654d94b188de805755f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 10 Apr 2012 00:12:03 +0000 Subject: [PATCH 114/250] Allow fail-mode to be set. Probably we should have a generic mechanism to specify OVS options. --- mininet/node.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index e8c3806..9e1e478 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -827,11 +827,12 @@ class OVSLegacyKernelSwitch( Switch ): class OVSSwitch( Switch ): "Open vSwitch switch. Depends on ovs-vsctl." - def __init__( self, name, **params ): + def __init__( self, name, failMode='secure', **params ): """Init. name: name for switch - defaultMAC: default MAC as unsigned int; random value if None""" + failMode: controller loss behavior (secure|open)""" Switch.__init__( self, name, **params ) + self.failMode = failMode @classmethod def setup( cls ): @@ -884,7 +885,7 @@ class OVSSwitch( Switch ): # Annoyingly, --if-exists option seems not to work self.cmd( 'ovs-vsctl del-br', self ) self.cmd( 'ovs-vsctl add-br', self ) - self.cmd( 'ovs-vsctl set-fail-mode', self, 'secure' ) + self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode ) for intf in self.intfList(): if not intf.IP(): self.attach( intf ) From 7cb340b7c998c230e42a2a4fccfc2f5eab814ac5 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 10 Apr 2012 00:12:37 +0000 Subject: [PATCH 115/250] Pass code check. --- examples/multiping.py | 22 ++++++------------ examples/multipoll.py | 8 +++---- examples/simpleperf.py | 53 +++++++++++++++++++++--------------------- 3 files changed, 36 insertions(+), 47 deletions(-) diff --git a/examples/multiping.py b/examples/multiping.py index 6729204..ac8aed5 100755 --- a/examples/multiping.py +++ b/examples/multiping.py @@ -26,20 +26,19 @@ def startpings( host, targetips ): targetips.append( '10.0.0.200' ) targetips = ' '.join( targetips ) - + # BL: Not sure why loopback intf isn't up! host.cmd( 'ifconfig lo up' ) - # Simple ping loop cmd = ( 'while true; do ' - ' for ip in %s; do ' % targetips + + ' for ip in %s; do ' % targetips + ' echo -n %s "->" $ip ' % host.IP() + ' `ping -c1 -w 1 $ip | grep packets` ;' ' sleep 1;' ' done; ' 'done &' ) - + print ( '*** Host %s (%s) will be pinging ips: %s' % ( host.name, host.IP(), targetips ) ) @@ -47,7 +46,7 @@ def startpings( host, targetips ): def multiping( netsize, chunksize, seconds): "Ping subsets of size chunksize in net of size netsize" - + # Create network and identify subnets topo = SingleSwitchTopo( netsize ) net = Mininet( topo=topo ) @@ -60,13 +59,13 @@ def multiping( netsize, chunksize, seconds): poller = poll() for fd in fds: poller.register( fd, POLLIN ) - + # Start pings for subnet in subnets: ips = [ host.IP() for host in subnet ] for host in subnet: startpings( host, ips ) - + # Monitor output endTime = time() + seconds while time() < endTime: @@ -78,17 +77,10 @@ def multiping( netsize, chunksize, seconds): # Stop pings for host in hosts: host.cmd( 'kill %while' ) - + net.stop() if __name__ == '__main__': setLogLevel( 'info' ) multiping( netsize=20, chunksize=4, seconds=10 ) - - - - - - - \ No newline at end of file diff --git a/examples/multipoll.py b/examples/multipoll.py index a3ffcc6..f670827 100755 --- a/examples/multipoll.py +++ b/examples/multipoll.py @@ -8,13 +8,13 @@ monitoring them from mininet.topo import SingleSwitchTopo from mininet.net import Mininet from mininet.log import setLogLevel -from mininet.util import quietRun -from time import time, sleep +from time import time from select import poll, POLLIN from subprocess import Popen, PIPE def monitorFiles( outfiles, seconds, timeoutms ): + "Monitor set of files and return [(host, line)...]" devnull = open( '/dev/null', 'w' ) tails, fdToFile, fdToHost = {}, {}, {} for h, outfile in outfiles.iteritems(): @@ -63,7 +63,7 @@ def monitorTest( N=3, seconds=3 ): h.cmd( 'echo >', outfiles[ h ] ) h.cmd( 'echo >', errfiles[ h ] ) # Start pings - h.cmdPrint('ping', server.IP(), + h.cmdPrint('ping', server.IP(), '>', outfiles[ h ], '2>', errfiles[ h ], '&' ) @@ -79,5 +79,3 @@ def monitorTest( N=3, seconds=3 ): if __name__ == '__main__': setLogLevel('info') monitorTest() - - diff --git a/examples/simpleperf.py b/examples/simpleperf.py index 12ca736..05cb2ae 100644 --- a/examples/simpleperf.py +++ b/examples/simpleperf.py @@ -12,34 +12,33 @@ from mininet.util import dumpNodeConnections from mininet.log import setLogLevel class SingleSwitchTopo(Topo): - "Single switch connected to n hosts." - def __init__(self, n=2, **opts): - Topo.__init__(self, **opts) - switch = self.add_switch('s1') - for h in range(n): - # Each host gets 50%/n of system CPU - host = self.add_host('h%s' % (h + 1), - cpu=.5/n) - # 10 Mbps, 5ms delay, 10% loss - self.add_link(host, switch, - bw=10, delay='5ms', loss=10, use_htb=True) + "Single switch connected to n hosts." + def __init__(self, n=2, **opts): + Topo.__init__(self, **opts) + switch = self.add_switch('s1') + for h in range(n): + # Each host gets 50%/n of system CPU + host = self.add_host('h%s' % (h + 1), + cpu=.5 / n) + # 10 Mbps, 5ms delay, 10% loss + self.add_link(host, switch, + bw=10, delay='5ms', loss=10, use_htb=True) def perfTest(): - "Create network and run simple performance test" - topo = SingleSwitchTopo(n=4) - net = Mininet(topo=topo, - host=CPULimitedHost, link=TCLink) - net.start() - print "Dumping host connections" - dumpNodeConnections(net.hosts) - print "Testing network connectivity" - net.pingAll() - print "Testing bandwidth between h1 and h4" - h1 = net.getNodeByName('h1') - h4 = net.getNodeByName('h4') - net.iperf((h1, h4)) - net.stop() + "Create network and run simple performance test" + topo = SingleSwitchTopo(n=4) + net = Mininet(topo=topo, + host=CPULimitedHost, link=TCLink) + net.start() + print "Dumping host connections" + dumpNodeConnections(net.hosts) + print "Testing network connectivity" + net.pingAll() + print "Testing bandwidth between h1 and h4" + h1, h4 = net.getNodeByName('h1', 'h4') + net.iperf((h1, h4)) + net.stop() if __name__ == '__main__': - setLogLevel('info') - perfTest() + setLogLevel('info') + perfTest() From 5507550c53bd23e300159dbf9998fbc1e0d4b80f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 11 Apr 2012 13:20:18 -0700 Subject: [PATCH 116/250] Fix wireshark dissector install on 11.10 --- util/install.sh | 59 +++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/util/install.sh b/util/install.sh index b6d94ab..1140695 100755 --- a/util/install.sh +++ b/util/install.sh @@ -145,12 +145,11 @@ function mn_deps { # The following will cause a full OF install, covering: # -user switch -# -dissector # 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 and OpenFlow WireShark dissector..." + echo "Installing OpenFlow reference implementation..." cd ~/ $install git-core autoconf automake autotools-dev pkg-config \ make gcc libtool libc6-dev @@ -166,25 +165,6 @@ function of { make sudo make install - # Install dissector: - $install wireshark libgtk2.0-dev - cd ~/openflow/utilities/wireshark_dissectors/openflow - make - sudo make install - - # The OpenFlow wireshark plugin does not install to the correct dir. - # The correct way would be to fix the install script. - # For now, just copy it to the global WS plugin dir. - # Tested on Ubuntu 11.04. - if [ -e /var/packet-openflow.so ]; then - WS_DIR=`ls -d /usr/lib/wireshark/libwireshark* | head -1` - sudo cp /var/packet-openflow.so $WS_DIR/plugins/ - fi - - # Copy coloring rules: OF is white-on-blue: - mkdir -p ~/.wireshark - cp ~/mininet/util/colorfilters ~/.wireshark - # Remove avahi-daemon, which may cause unwanted discovery packets to be # sent during tests, near link status changes: $remove avahi-daemon @@ -199,6 +179,38 @@ function of { cd ~ } +function wireshark { + echo "Installing Wireshark dissector..." + + sudo apt-get install -y wireshark libgtk2.0-dev + + if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" != "10.04" ]; then + # Install newer version + sudo apt-get install -y scons mercurial libglib2.0-dev + sudo apt-get install -y libwiretap-dev libwireshark-dev + cd ~ + hg clone https://bitbucket.org/onlab/of-dissector + cd of-dissector/src + export WIRESHARK=/usr/include/wireshark + scons + # libwireshark0/ on 11.04; libwireshark1/ on later + WSDIR=`ls -d /usr/lib/wireshark/libwireshark* | head -1` + WSPLUGDIR=$WSDIR/plugins/ + sudo cp openflow.so $WSPLUGDIR + echo "Copied openflow plugin to $WSPLUGDIR" + else + # Install older version from reference source + cd ~/openflow/utilities/wireshark_dissectors/openflow + make + sudo make install + fi + + # Copy coloring rules: OF is white-on-blue: + mkdir -p ~/.wireshark + cp ~/mininet/util/colorfilters ~/.wireshark +} + + # Install Open vSwitch # Instructions derived from OVS INSTALL, INSTALL.OpenFlow and README files. function ovs { @@ -428,6 +440,7 @@ function all { kernel mn_deps of + wireshark ovs nox oftest @@ -487,6 +500,7 @@ function usage { printf -- ' -r: remove existing Open vSwitch packages\n' >&2 printf -- ' -t: install o(T)her stuff\n' >&2 printf -- ' -v: install open (V)switch\n' >&2 + printf -- ' -w: install OpenFlow (w)ireshark dissector\n' >&2 printf -- ' -x: install NO(X) OpenFlow controller\n' >&2 printf -- ' -y: install (A)ll packages\n' >&2 @@ -497,7 +511,7 @@ if [ $# -eq 0 ] then all else - while getopts 'abcdfhkmnrtvx' OPTION + while getopts 'abcdfhkmnrtvwx' OPTION do case $OPTION in a) all;; @@ -512,6 +526,7 @@ else r) remove_ovs;; t) other;; v) ovs;; + w) wireshark;; x) nox;; ?) usage;; esac From 669e420cc43d7e9f750da9bfa1aea206d6f7c25f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 13 Apr 2012 07:38:35 +0000 Subject: [PATCH 117/250] Add default value mems=0 for memory placement. --- mininet/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 9e1e478..90d289b 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -621,16 +621,16 @@ class CPULimitedHost( Host ): self.chrt( prio=20 ) info( '(%s %d/%dus) ' % ( sched, quota, period ) ) - def setCPUs( self, cores ): + def setCPUs( self, cores, mems=0 ): "Specify (real) cores that our cgroup can run on" if type( cores ) is list: cores = ','.join( [ str( c ) for c in cores ] ) self.cgroupSet( resource='cpuset', param='cpus', - value= cores ) + value=cores ) # Memory placement is probably not relevant, but we # must specify it anyway self.cgroupSet( resource='cpuset', param='mems', - value= cores ) + value=mems) # We have to do this here after we've specified # cpus and mems errFail( 'cgclassify -g cpuset:/%s %s' % ( From e78e8fb56a661207bf58ef2de0ce80a1850eedb7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 10 Apr 2012 22:12:03 -0700 Subject: [PATCH 118/250] Add support for attaching to network namespace using setns(2) --- mnexec.c | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/mnexec.c b/mnexec.c index 19f81f0..acad6b4 100644 --- a/mnexec.c +++ b/mnexec.c @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include void usage(char *name) { @@ -22,15 +25,26 @@ void usage(char *name) "-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" - "-p: print ^A + pid\n", name); + "-p: print ^A + pid\n" + "-a pid: attach to pid's network namespace\n", + name); +} + + +int setns(int fd, int nstype) +{ + return syscall(308, fd, nstype); } int main(int argc, char *argv[]) { char c; int fd; - - while ((c = getopt(argc, argv, "+cdnp")) != -1) + char path[PATH_MAX]; + int nsid; + int pid; + + while ((c = getopt(argc, argv, "+cdnpa:")) != -1) switch(c) { case 'c': /* close file descriptors except stdin/out/error */ @@ -64,6 +78,20 @@ int main(int argc, char *argv[]) printf("\001%d\n", getpid()); fflush(stdout); break; + case 'a': + /* Attach to pid's network namespace */ + pid = atoi(optarg); + sprintf(path, "/proc/%d/ns/net", pid ); + nsid = open(path, O_RDONLY); + if (nsid < 0) { + perror(path); + return 1; + } + if (setns(nsid, 0) != 0) { + perror("setns"); + return 1; + } + break; default: usage(argv[0]); break; From 089e8130e4ceca6082515530f2968f72fe73404d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 11 Apr 2012 13:11:02 -0700 Subject: [PATCH 119/250] Add popen() to regular hosts (cpu limited in progress) --- mininet/net.py | 4 ++++ mininet/node.py | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index d7a673d..827d72f 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -220,6 +220,10 @@ class Mininet( object ): return self.nameToNode[ args[ 0 ] ] return [ self.nameToNode[ n ] for n in args ] + def get( self, *args ): + "Convenience alias for getNodeByName" + return self.getNodeByName( *args ) + def addLink( self, node1, node2, port1=None, port2=None, cls=None, **params ): """"Add a link from node1 to node2 diff --git a/mininet/node.py b/mininet/node.py index 90d289b..7539b63 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -282,6 +282,37 @@ class Node( object ): cmd: string""" return self.cmd( *args, **{ 'verbose': True } ) + def popen( self, *args, **kwargs ): + """Return a Popen() object in our namespace + args: Popen() args, single list, or string + kwargs: Popen() keyword args""" + defaults = { 'stdout': PIPE, 'stderr': PIPE, + 'mncmd': [ 'mnexec', '-a' ] } + defaults.update( kwargs ) + if len( args ) == 1: + if type( args[ 0 ] ) is list: + # popen([cmd, arg1, arg2...]) + cmd = args[ 0 ] + elif type( args[ 0 ] ) is str: + # popen("cmd arg1 arg2...") + cmd = args[ 0 ].split() + elif len( args ) > 0: + # popen( cmd, arg1, arg2... ) + cmd = args + # Attach to our namespace using mnexec -a + mncmd = defaults[ 'mncmd' ] + del defaults[ 'mncmd' ] + cmd = mncmd + [ str( self.pid ) ] + cmd + return Popen( cmd, **defaults ) + + def pexec( self, *args, **kwargs ): + """Execute a command using popen + returns: out, err, exitcode""" + popen = self.popen( *args, **kwargs) + out, err = popen.communicate() + exitcode = popen.wait() + return out, err, exitcode + # Interface management, configuration, and routing # BL notes: This might be a bit redundant or over-complicated. @@ -529,6 +560,7 @@ class CPULimitedHost( Host ): # still does better with larger period values. self.period_us = kwargs.get( 'period_us', 100000 ) self.sched = sched + self.rtprio = 20 def cgroupSet( self, param, value, resource='cpu' ): "Set a cgroup parameter and return its value" @@ -553,13 +585,24 @@ class CPULimitedHost( Host ): _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup ) return exitcode != 0 + def popen( self, *args, **kwargs ): + """Return a Popen() object in node's namespace + args: Popen() args, single list, or string + kwargs: Popen() keyword args""" + # Tell mnexec to execute command in our cgroup + mncmd = [ 'mnexec', '-a', str( self.pid ), + '-c', self.cgroup ] + if self.sched == 'rt': + mncmd = [ 'chrt', self.rtprio ] + mncmd + return Host.popen( self, *args, mncmd=mncmd, **kwargs ) + def cleanup( self ): "Clean up our cgroup" retry( retries=3, delaySecs=1, fn=self.cgroupDel ) - def chrt( self, prio=20 ): + def chrt( self ): "Set RT scheduling priority" - quietRun( 'chrt -p %s %s' % ( prio, self.pid ) ) + quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) ) result = quietRun( 'chrt -p %s' % self.pid ) firstline = result.split( '\n' )[ 0 ] lastword = firstline.split( ' ' )[ -1 ] From df600200a7242c674316d5048ccce7fef80ac6b9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 12 Apr 2012 23:24:23 -0700 Subject: [PATCH 120/250] CPULimiteHost.popen(): set cgroup and (optionally) RT priority --- mininet/node.py | 11 +++---- mnexec.c | 76 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 7539b63..a901b7a 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -287,7 +287,8 @@ class Node( object ): args: Popen() args, single list, or string kwargs: Popen() keyword args""" defaults = { 'stdout': PIPE, 'stderr': PIPE, - 'mncmd': [ 'mnexec', '-a' ] } + 'mncmd': + [ 'mnexec', '-a', str( self.pid ) ] } defaults.update( kwargs ) if len( args ) == 1: if type( args[ 0 ] ) is list: @@ -302,7 +303,7 @@ class Node( object ): # Attach to our namespace using mnexec -a mncmd = defaults[ 'mncmd' ] del defaults[ 'mncmd' ] - cmd = mncmd + [ str( self.pid ) ] + cmd + cmd = mncmd + cmd return Popen( cmd, **defaults ) def pexec( self, *args, **kwargs ): @@ -591,9 +592,9 @@ class CPULimitedHost( Host ): kwargs: Popen() keyword args""" # Tell mnexec to execute command in our cgroup mncmd = [ 'mnexec', '-a', str( self.pid ), - '-c', self.cgroup ] + '-g', self.name ] if self.sched == 'rt': - mncmd = [ 'chrt', self.rtprio ] + mncmd + mncmd += [ '-r', str( self.rtprio ) ] return Host.popen( self, *args, mncmd=mncmd, **kwargs ) def cleanup( self ): @@ -661,7 +662,7 @@ class CPULimitedHost( Host ): self.cgroupSet( qstr, quota ) if sched == 'rt': # Set RT priority if necessary - self.chrt( prio=20 ) + self.chrt() info( '(%s %d/%dus) ' % ( sched, quota, period ) ) def setCPUs( self, cores, mems=0 ): diff --git a/mnexec.c b/mnexec.c index acad6b4..ebd479c 100644 --- a/mnexec.c +++ b/mnexec.c @@ -7,6 +7,8 @@ * - detaching from a controlling tty using setsid * - running in a network namespace * - printing out the pid of a process so we can identify it later + * - attaching to a namespace and cgroup + * - setting RT scheduling * * Partially based on public domain setsid(1) */ @@ -17,16 +19,21 @@ #include #include #include +#include +#include +#include void usage(char *name) { printf("Execution utility for Mininet.\n" - "usage: %s [-cdnp]\n" + "usage: %s [-cdnp] [-a pid] [-g group] [-r rtprio] cmd args...\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" "-p: print ^A + pid\n" - "-a pid: attach to pid's network namespace\n", + "-a pid: attach to pid's network namespace\n" + "-g group: add to cgroup\n" + "-r rtprio: run with SCHED_RR (usually requires -g)\n", name); } @@ -36,6 +43,47 @@ int setns(int fd, int nstype) return syscall(308, fd, nstype); } +/* Validate alphanumeric path foo1/bar2/baz */ +void validate(char *path) +{ + char *s; + for (s=path; *s; s++) { + if (!isalnum(*s) && *s != '/') { + fprintf(stderr, "invalid path: %s\n", path); + exit(1); + } + } +} + +/* Add our pid to cgroup */ +int cgroup(char *gname) +{ + static char path[PATH_MAX]; + static char *groups[] = { + "cpu", "cpuacct", "cpuset", NULL + }; + char **gptr; + pid_t pid = getpid(); + int count = 0; + validate(gname); + for (gptr = groups; *gptr; gptr++) { + FILE *f; + snprintf(path, PATH_MAX, "/sys/fs/cgroup/%s/%s/tasks", + *gptr, gname); + f = fopen(path, "w"); + if (f) { + count++; + fprintf(f, "%d\n", pid); + fclose(f); + } + } + if (!count) { + fprintf(stderr, "cgroup: could not add to cgroup %s\n", + gname); + exit(1); + } +} + int main(int argc, char *argv[]) { char c; @@ -43,8 +91,8 @@ int main(int argc, char *argv[]) char path[PATH_MAX]; int nsid; int pid; - - while ((c = getopt(argc, argv, "+cdnpa:")) != -1) + static struct sched_param sp; + while ((c = getopt(argc, argv, "+cdnpa:g:r:")) != -1) switch(c) { case 'c': /* close file descriptors except stdin/out/error */ @@ -92,16 +140,28 @@ int main(int argc, char *argv[]) return 1; } break; + case 'g': + /* Attach to cgroup */ + cgroup(optarg); + break; + case 'r': + /* Set RT scheduling priority */ + sp.sched_priority = atoi(optarg); + if (sched_setscheduler(getpid(), SCHED_RR, &sp) < 0) { + perror("sched_setscheduler"); + return 1; + } + break; default: usage(argv[0]); break; } if (optind < argc) { - execvp(argv[optind], &argv[optind]); - perror(argv[optind]); - return 1; - } + execvp(argv[optind], &argv[optind]); + perror(argv[optind]); + return 1; + } usage(argv[0]); } From 5ca91f9cede0770cf2ff2f084d3e982d6a191bc1 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 12 Apr 2012 23:30:40 -0700 Subject: [PATCH 121/250] White space edits for code check. --- mininet/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index a901b7a..238e9bf 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -287,7 +287,7 @@ class Node( object ): args: Popen() args, single list, or string kwargs: Popen() keyword args""" defaults = { 'stdout': PIPE, 'stderr': PIPE, - 'mncmd': + 'mncmd': [ 'mnexec', '-a', str( self.pid ) ] } defaults.update( kwargs ) if len( args ) == 1: @@ -591,7 +591,7 @@ class CPULimitedHost( Host ): args: Popen() args, single list, or string kwargs: Popen() keyword args""" # Tell mnexec to execute command in our cgroup - mncmd = [ 'mnexec', '-a', str( self.pid ), + mncmd = [ 'mnexec', '-a', str( self.pid ), '-g', self.name ] if self.sched == 'rt': mncmd += [ '-r', str( self.rtprio ) ] From 237a3c54cfb0a3c2e320bd5148c860b8cdcd002b Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 12 Apr 2012 23:33:41 -0700 Subject: [PATCH 122/250] Begin test/example for popen(). --- examples/popen.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100755 examples/popen.py diff --git a/examples/popen.py b/examples/popen.py new file mode 100755 index 0000000..d9ddd7a --- /dev/null +++ b/examples/popen.py @@ -0,0 +1,30 @@ +#!/usr/bin/python + +""" +This example tests the Host.popen()/pexec() interface +""" + +from mininet.net import Mininet +from mininet.node import CPULimitedHost +from mininet.topo import SingleSwitchTopo +from mininet.log import setLogLevel +# from mininet.cli import CLI +from mininet.util import custom + +def testpopen(sched='cfs'): + "Test popen() interface" + host = custom( CPULimitedHost, cpu=.2, sched=sched ) + net = Mininet( SingleSwitchTopo( 2 ), host=host ) + net.start() + h1 = net.get( 'h1' ) + # CLI(net) + out, err, code = h1.pexec( 'ifconfig' ) + print 'stdout:', out.strip() + print 'stderr:', err.strip() + print 'exit code:', code + net.stop() + +if __name__ == '__main__': + setLogLevel( 'info' ) + testpopen('rt') + testpopen('cfs') From 50cebe6753060c27848e94a42def9b78319084ff Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 13 Apr 2012 17:25:19 -0700 Subject: [PATCH 123/250] Add pmonitor() to make it easy to monitor popen objects. --- examples/popen.py | 36 ++++++++++++++++++++--------------- mininet/util.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/examples/popen.py b/examples/popen.py index d9ddd7a..332822f 100755 --- a/examples/popen.py +++ b/examples/popen.py @@ -1,30 +1,36 @@ #!/usr/bin/python """ -This example tests the Host.popen()/pexec() interface +This example monitors a number of hosts using host.popen() and +pmonitor() """ from mininet.net import Mininet from mininet.node import CPULimitedHost from mininet.topo import SingleSwitchTopo from mininet.log import setLogLevel -# from mininet.cli import CLI -from mininet.util import custom +from mininet.util import custom, pmonitor -def testpopen(sched='cfs'): - "Test popen() interface" - host = custom( CPULimitedHost, cpu=.2, sched=sched ) - net = Mininet( SingleSwitchTopo( 2 ), host=host ) +def monitorhosts( hosts=5, sched='cfs' ): + "Start a bunch of pings and monitor them using popen" + mytopo = SingleSwitchTopo( hosts ) + cpu = .5 / hosts + myhost = custom( CPULimitedHost, cpu=cpu, sched=sched ) + net = Mininet( topo=mytopo, host=myhost ) net.start() - h1 = net.get( 'h1' ) - # CLI(net) - out, err, code = h1.pexec( 'ifconfig' ) - print 'stdout:', out.strip() - print 'stderr:', err.strip() - print 'exit code:', code + # Start a bunch of pings + popens = {} + last = net.hosts[ -1 ] + for host in net.hosts: + popens[ host ] = host.popen( "ping -c5 %s" % last.IP() ) + last = host + # Monitor them and print output + for host, line in pmonitor( popens ): + if host: + print "<%s>: %s" % ( host.name, line.strip() ) + # Done net.stop() if __name__ == '__main__': setLogLevel( 'info' ) - testpopen('rt') - testpopen('cfs') + monitorhosts( hosts=5 ) diff --git a/mininet/util.py b/mininet/util.py index 6016cd3..e6ede1f 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -1,11 +1,14 @@ "Utility functions for Mininet." +from mininet.log import output, info, error + from time import sleep from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE from select import poll, POLLIN from subprocess import call, check_call, Popen, PIPE, STDOUT -from mininet.log import output, info, error import re +from fcntl import fcntl, F_GETFL, F_SETFL +from os import O_NONBLOCK # Command execution support @@ -300,6 +303,49 @@ def makeNumeric( s ): else: return s +# Popen support + +def pmonitor(popens, timeoutms=500, readline=True, + readmax=1024 ): + """Monitor dict of hosts to popen objects + a line at a time + timeoutms: timeout for poll() + readline: return single line of output + yields: host, line/output (if any) + terminates: when all EOFs received""" + poller = poll() + fdToHost = {} + for host, popen in popens.iteritems(): + fd = popen.stdout.fileno() + fdToHost[ fd ] = host + poller.register( fd, POLLIN ) + if not readline: + # Use non-blocking reads + flags = fcntl( fd, F_GETFL ) + fcntl( fd, F_SETFL, flags | O_NONBLOCK ) + while True: + fds = poller.poll( timeoutms ) + if fds: + for fd, _event in fds: + host = fdToHost[ fd ] + popen = popens[ host ] + if readline: + # Attempt to read a line of output + # This blocks until we receive a newline! + line = popen.stdout.readline() + else: + line = popen.stdout.read( readmax ) + yield host, line + # Check for EOF + if not line: + popen.poll() + if popen.returncode is not None: + poller.unregister( fd ) + del popens[ host ] + if not popens: + return + else: + yield None, '' # Other stuff we use From 55cf19c4de6ff7bda7825e2537aa7e2a602a06bd Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 25 Apr 2012 14:21:39 -0700 Subject: [PATCH 124/250] Improve error handling for defaultDpid() I think it's worth considering how we want to specify dpids for switches. One way would be to have Mininet (optionally) pick them automatically. Another way, which I have currently implemented, is to intuit them from the name, for example s1 -> 1. The latter is slightly inefficient, but is convenient because it ensures that there is a logical mapping between switch names and dpids, which is very helpful for debugging an OpenFlow system! Probably we should just clarify that the easiest way to set a dpid is to include it in the switch name, but you can also pass it in as a custom parameter to the constructor. --- mininet/node.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 238e9bf..25721e5 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -727,7 +727,7 @@ class Switch( Node ): portBase = 1 # Switches start with port 1 in OpenFlow def __init__( self, name, dpid=None, opts='', listenPort=None, **params): - """dpid: dpid for switch (or None for default) + """dpid: dpid for switch (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 ) @@ -739,10 +739,15 @@ class Switch( Node ): def defaultDpid( self ): "Derive dpid from switch name, s1 -> 1" - dpid = int( re.findall( '\d+', self.name )[ 0 ] ) - dpid = hex( dpid )[ 2: ] - dpid = '0' * ( 16 - len( dpid ) ) + dpid - return dpid + try: + dpid = int( re.findall( '\d+', self.name )[ 0 ] ) + dpid = hex( dpid )[ 2: ] + dpid = '0' * ( 16 - 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 defaultIntf( self ): "Return control interface" From cfd381134f36e7eff61f9f323830a73898b25d7f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 9 May 2012 22:59:30 -0700 Subject: [PATCH 125/250] Fix errRun to not exit until all of stdout and stderr have been read. --- mininet/util.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mininet/util.py b/mininet/util.py index e6ede1f..271cad4 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -74,31 +74,33 @@ def errRun( *cmd, **kwargs ): # cmd goes to stderr, output goes to stdout info( cmd, '\n' ) popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell ) - # We use poll() because select() doesn't work with large fd numbers + # We use poll() because select() doesn't work with large fd numbers, + # and thus communicate() doesn't work either out, err = '', '' poller = poll() poller.register( popen.stdout, POLLIN ) fdtofile = { popen.stdout.fileno(): popen.stdout } + outDone, errDone = False, True if popen.stderr: fdtofile[ popen.stderr.fileno() ] = popen.stderr poller.register( popen.stderr, POLLIN ) - while True: + errDone = False + while not outDone or not errDone: readable = poller.poll() - # Tell pylint to ignore unused variable event - # pylint: disable-msg=W0612 for fd, event in readable: - # pylint: enable-msg=W0612 f = fdtofile[ fd ] data = f.read( 1024 ) if echo: output( data ) if f == popen.stdout: out += data + if data == '': + outDone = True elif f == popen.stderr: err += data - returncode = popen.poll() - if returncode is not None: - break + if data == '': + errDone = True + returncode = popen.wait() return out, err, returncode def errFail( *cmd, **kwargs ): From cece39e43963a17080e94217e0c0225217c520ed Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 May 2012 16:32:55 -0700 Subject: [PATCH 126/250] Fix poller to only check if stdin and node are readable. Thanks to James Zeng for pointing this out! --- mininet/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mininet/cli.py b/mininet/cli.py index 79e55a4..786cc5c 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -313,14 +313,15 @@ class CLI( Cmd ): nodePoller = poll() nodePoller.register( node.stdout ) bothPoller = poll() - bothPoller.register( self.stdin ) - bothPoller.register( node.stdout ) + bothPoller.register( self.stdin, POLLIN ) + bothPoller.register( node.stdout, POLLIN ) if self.isatty(): # Buffer by character, so that interactive # commands sort of work quietRun( 'stty -icanon min 1' ) while True: try: + print 'waiting for input' bothPoller.poll() # XXX BL: this doesn't quite do what we want. if False and self.inputFile: From 0eba655d2de18bf1949e85c65c8cba6d85906b55 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 May 2012 16:52:40 -0700 Subject: [PATCH 127/250] Fix RemoteController which was still using defaultIP rather than ip. --- mininet/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 25721e5..ae03d9d 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1031,14 +1031,14 @@ class NOX( Controller ): class RemoteController( Controller ): "Controller running outside of Mininet's control." - def __init__( self, name, defaultIP='127.0.0.1', + def __init__( self, name, ip='127.0.0.1', port=6633, **kwargs): """Init. name: name to give controller defaultIP: the IP address where the remote controller is listening port: the port where the remote controller is listening""" - Controller.__init__( self, name, defaultIP=defaultIP, port=port, + Controller.__init__( self, name, ip=ip, port=port, **kwargs ) def start( self ): From 0e8cca08694677fb546be163570914477c5c4a35 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 May 2012 16:54:03 -0700 Subject: [PATCH 128/250] Remove unnecessary and broken --ip option. --- bin/mn | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/mn b/bin/mn index 16fdc94..a1173ca 100755 --- a/bin/mn +++ b/bin/mn @@ -205,9 +205,6 @@ class MininetRunner( object ): opts.add_option( '--verbosity', '-v', type='choice', choices=LEVELS.keys(), default = 'info', help = '|'.join( LEVELS.keys() ) ) - opts.add_option( '--ip', type='string', default='127.0.0.1', - help='ip address as a dotted decimal string for a' - 'remote controller' ) opts.add_option( '--innamespace', action='store_true', default=False, help='sw and ctrl in namespace?' ) opts.add_option( '--listenport', type='int', default=6635, From 4c3ff8f1843341a5058acd6ca876ad54e8ea4b87 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 May 2012 22:23:31 -0700 Subject: [PATCH 129/250] Remove accidentally added debugging line. --- mininet/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mininet/cli.py b/mininet/cli.py index 786cc5c..4b83fcd 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -321,7 +321,6 @@ class CLI( Cmd ): quietRun( 'stty -icanon min 1' ) while True: try: - print 'waiting for input' bothPoller.poll() # XXX BL: this doesn't quite do what we want. if False and self.inputFile: From 0d94548a09015f7b882f240a1a30d8c132433a5d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 May 2012 22:36:20 -0700 Subject: [PATCH 130/250] Fix default dpid which should be 12 digits for reference user switch. --- mininet/node.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index ae03d9d..8a5e991 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -725,6 +725,7 @@ class Switch( Node ): an OpenFlow switch.""" portBase = 1 # Switches start with port 1 in OpenFlow + 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) @@ -742,7 +743,7 @@ class Switch( Node ): try: dpid = int( re.findall( '\d+', self.name )[ 0 ] ) dpid = hex( dpid )[ 2: ] - dpid = '0' * ( 16 - len( dpid ) ) + dpid + dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid return dpid except IndexError: raise Exception( 'Unable to derive default datapath ID - ' @@ -776,6 +777,8 @@ class Switch( Node ): class UserSwitch( Switch ): "User-space switch." + dpidLen = 12 + def __init__( self, name, **kwargs ): """Init. name: name for the switch""" From e8d60e0fcf005104771e3bdd33cace5c56011f2a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 10 May 2012 22:37:49 -0700 Subject: [PATCH 131/250] Pass code check. --- mininet/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mininet/util.py b/mininet/util.py index 271cad4..d801600 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -87,7 +87,7 @@ def errRun( *cmd, **kwargs ): errDone = False while not outDone or not errDone: readable = poller.poll() - for fd, event in readable: + for fd, _event in readable: f = fdtofile[ fd ] data = f.read( 1024 ) if echo: @@ -116,7 +116,7 @@ def quietRun( cmd, **kwargs ): return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ] # pylint: enable-msg=E1103 -# pylint: disable-msg=E1101,W0612 +# pylint: disable-msg=E1101 def isShellBuiltin( cmd ): "Return True if cmd is a bash builtin." @@ -129,7 +129,7 @@ def isShellBuiltin( cmd ): isShellBuiltin.builtIns = None -# pylint: enable-msg=E1101,W0612 +# pylint: enable-msg=E1101 # Interface management # From d75e39ac612c828069aaf8b9c6931ccc8f53bb39 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 13 May 2012 14:43:25 -0700 Subject: [PATCH 132/250] Change wireshark install to reflect new repository location. --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 1140695..c5fecde 100755 --- a/util/install.sh +++ b/util/install.sh @@ -189,7 +189,7 @@ function wireshark { sudo apt-get install -y scons mercurial libglib2.0-dev sudo apt-get install -y libwiretap-dev libwireshark-dev cd ~ - hg clone https://bitbucket.org/onlab/of-dissector + hg clone https://bitbucket.org/barnstorm/of-dissector cd of-dissector/src export WIRESHARK=/usr/include/wireshark scons From b97c1dbd56f1798367d5a4c589ccdd5d4d10d263 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 13 May 2012 15:11:41 -0700 Subject: [PATCH 133/250] Set dpid on OVSSwitch. --- mininet/node.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mininet/node.py b/mininet/node.py index 8a5e991..c12ae08 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -937,6 +937,8 @@ class OVSSwitch( Switch ): # Annoyingly, --if-exists option seems not to work self.cmd( 'ovs-vsctl del-br', self ) self.cmd( 'ovs-vsctl add-br', self ) + 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(): From 6e64deec0886dd47b0ccb65e6a85804c5edda1a0 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 13 May 2012 15:29:08 -0700 Subject: [PATCH 134/250] Fix typo. --- mininet/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/util.py b/mininet/util.py index d801600..af4b17d 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -52,7 +52,7 @@ def oldQuietRun( *cmd ): # This is a bit complicated, but it enables us to -# monitor commount output as it is happening +# monitor command output as it is happening def errRun( *cmd, **kwargs ): """Run a command and return stdout, stderr and return code From b0fb398833b5d3e1c2b50c23b693d472272d1fcb Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 14 May 2012 16:03:48 -0700 Subject: [PATCH 135/250] Patch/hacks to enable NOX destiny/classic to compile on Ubuntu 12.04 --- util/install.sh | 5 +- .../nox-patches/0002-nox-ubuntu12-hacks.patch | 175 ++++++++++++++++++ util/nox-patches/README | 3 +- 3 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 util/nox-patches/0002-nox-ubuntu12-hacks.patch diff --git a/util/install.sh b/util/install.sh index c5fecde..8186d87 100755 --- a/util/install.sh +++ b/util/install.sh @@ -343,7 +343,10 @@ function nox { # Apply patches git checkout -b tutorial-destiny - git am ~/mininet/util/nox-patches/*.patch + git am ~/mininet/util/nox-patches/*tutorial-port-nox-destiny*.patch + if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` -eq 1 ]; then + git am ~/mininet/util/nox-patches/*nox-ubuntu12-hacks.patch + fi # Build ./boot.sh diff --git a/util/nox-patches/0002-nox-ubuntu12-hacks.patch b/util/nox-patches/0002-nox-ubuntu12-hacks.patch new file mode 100644 index 0000000..77619bc --- /dev/null +++ b/util/nox-patches/0002-nox-ubuntu12-hacks.patch @@ -0,0 +1,175 @@ +From 166693d7cb640d4a41251b87e92c52d9c688196b Mon Sep 17 00:00:00 2001 +From: Bob Lantz +Date: Mon, 14 May 2012 15:30:44 -0700 +Subject: [PATCH] Hacks to get NOX classic/destiny to compile under Ubuntu + 12.04 + +Thanks to Srinivasu R. Kanduru for the initial patch. + +Apologies for the hacks - it is my hope that this will be fixed +upstream eventually. + +--- + config/ac_pkg_swig.m4 | 7 ++++--- + src/Make.vars | 2 +- + src/nox/coreapps/pyrt/deferredcallback.cc | 2 +- + src/nox/coreapps/pyrt/pyglue.cc | 2 +- + src/nox/coreapps/pyrt/pyrt.cc | 2 +- + src/nox/netapps/authenticator/auth.i | 2 ++ + src/nox/netapps/authenticator/flow_util.i | 1 + + src/nox/netapps/routing/routing.i | 2 ++ + .../switch_management/pyswitch_management.i | 2 ++ + src/nox/netapps/tests/tests.cc | 2 +- + src/nox/netapps/topology/pytopology.i | 2 ++ + 11 files changed, 18 insertions(+), 8 deletions(-) + +diff --git a/config/ac_pkg_swig.m4 b/config/ac_pkg_swig.m4 +index d12556e..9b608f2 100644 +--- a/config/ac_pkg_swig.m4 ++++ b/config/ac_pkg_swig.m4 +@@ -78,9 +78,10 @@ AC_DEFUN([AC_PROG_SWIG],[ + if test -z "$available_patch" ; then + [available_patch=0] + fi +- if test $available_major -ne $required_major \ +- -o $available_minor -ne $required_minor \ +- -o $available_patch -lt $required_patch ; then ++ major_done=`test $available_major -gt $required_major` ++ minor_done=`test $available_minor -gt $required_minor` ++ if test !$major_done -a !$minor_done \ ++ -a $available_patch -lt $required_patch ; then + AC_MSG_WARN([SWIG version >= $1 is required. You have $swig_version. You should look at http://www.swig.org]) + SWIG='' + else +diff --git a/src/Make.vars b/src/Make.vars +index d70d6aa..93b2879 100644 +--- a/src/Make.vars ++++ b/src/Make.vars +@@ -53,7 +53,7 @@ AM_LDFLAGS += -export-dynamic + endif + + # set python runtimefiles to be installed in the same directory as pkg +-pkglib_SCRIPTS = $(NOX_RUNTIMEFILES) $(NOX_PYBUILDFILES) ++pkgdata_SCRIPTS = $(NOX_RUNTIMEFILES) $(NOX_PYBUILDFILES) + BUILT_SOURCES = $(NOX_PYBUILDFILES) + + # Runtime-files build and clean rules +diff --git a/src/nox/coreapps/pyrt/deferredcallback.cc b/src/nox/coreapps/pyrt/deferredcallback.cc +index 3a40fa7..111a586 100644 +--- a/src/nox/coreapps/pyrt/deferredcallback.cc ++++ b/src/nox/coreapps/pyrt/deferredcallback.cc +@@ -69,7 +69,7 @@ DeferredCallback::get_instance(const Callback& c) + DeferredCallback* cb = new DeferredCallback(c); + + // flag as used in *_wrap.cc....correct? +- return SWIG_Python_NewPointerObj(cb, s, SWIG_POINTER_OWN | 0); ++ return SWIG_Python_NewPointerObj(m, cb, s, SWIG_POINTER_OWN | 0); + } + + bool +diff --git a/src/nox/coreapps/pyrt/pyglue.cc b/src/nox/coreapps/pyrt/pyglue.cc +index 48b9716..317fd04 100644 +--- a/src/nox/coreapps/pyrt/pyglue.cc ++++ b/src/nox/coreapps/pyrt/pyglue.cc +@@ -874,7 +874,7 @@ to_python(const Flow& flow) + if (!s) { + throw std::runtime_error("Could not find Flow SWIG type_info"); + } +- return SWIG_Python_NewPointerObj(f, s, SWIG_POINTER_OWN | 0); ++ return SWIG_Python_NewPointerObj(m, f, s, SWIG_POINTER_OWN | 0); + + // PyObject* dict = PyDict_New(); + // if (!dict) { +diff --git a/src/nox/coreapps/pyrt/pyrt.cc b/src/nox/coreapps/pyrt/pyrt.cc +index fbda461..8ec05d6 100644 +--- a/src/nox/coreapps/pyrt/pyrt.cc ++++ b/src/nox/coreapps/pyrt/pyrt.cc +@@ -776,7 +776,7 @@ Python_event_manager::create_python_context(const Context* ctxt, + pretty_print_python_exception()); + } + +- PyObject* pyctxt = SWIG_Python_NewPointerObj(p, s, 0); ++ PyObject* pyctxt = SWIG_Python_NewPointerObj(m, p, s, 0); + Py_INCREF(pyctxt); // XXX needed? + + //Py_DECREF(m); +diff --git a/src/nox/netapps/authenticator/auth.i b/src/nox/netapps/authenticator/auth.i +index 1de1a17..bfa04e2 100644 +--- a/src/nox/netapps/authenticator/auth.i ++++ b/src/nox/netapps/authenticator/auth.i +@@ -18,6 +18,8 @@ + + %module "nox.netapps.authenticator.pyauth" + ++// Hack to get it to compile -BL ++%include "std_list.i" + %{ + #include "core_events.hh" + #include "pyrt/pycontext.hh" +diff --git a/src/nox/netapps/authenticator/flow_util.i b/src/nox/netapps/authenticator/flow_util.i +index f67c3ef..2a314e2 100644 +--- a/src/nox/netapps/authenticator/flow_util.i ++++ b/src/nox/netapps/authenticator/flow_util.i +@@ -32,6 +32,7 @@ using namespace vigil::applications; + %} + + %include "common-defs.i" ++%include "std_list.i" + + %import "netinet/netinet.i" + %import "pyrt/event.i" +diff --git a/src/nox/netapps/routing/routing.i b/src/nox/netapps/routing/routing.i +index 44ccb3d..f9221a2 100644 +--- a/src/nox/netapps/routing/routing.i ++++ b/src/nox/netapps/routing/routing.i +@@ -17,6 +17,8 @@ + */ + %module "nox.netapps.routing.pyrouting" + ++// Hack to get it to compile -BL ++%include "std_list.i" + %{ + #include "pyrouting.hh" + #include "routing.hh" +diff --git a/src/nox/netapps/switch_management/pyswitch_management.i b/src/nox/netapps/switch_management/pyswitch_management.i +index 72bfed4..ad2c90d 100644 +--- a/src/nox/netapps/switch_management/pyswitch_management.i ++++ b/src/nox/netapps/switch_management/pyswitch_management.i +@@ -18,6 +18,8 @@ + + %module "nox.netapps.pyswitch_management" + ++// Hack to get it to compile -BL ++%include "std_list.i" + %{ + #include "switch_management_proxy.hh" + #include "pyrt/pycontext.hh" +diff --git a/src/nox/netapps/tests/tests.cc b/src/nox/netapps/tests/tests.cc +index 20e900d..f027028 100644 +--- a/src/nox/netapps/tests/tests.cc ++++ b/src/nox/netapps/tests/tests.cc +@@ -306,7 +306,7 @@ private: + throw runtime_error("Could not find PyContext SWIG type_info."); + } + +- PyObject* pyctxt = SWIG_Python_NewPointerObj(p, s, 0); ++ PyObject* pyctxt = SWIG_Python_NewPointerObj(m, p, s, 0); + assert(pyctxt); + + Py_DECREF(m); +diff --git a/src/nox/netapps/topology/pytopology.i b/src/nox/netapps/topology/pytopology.i +index 94a9f4b..7a8cd94 100644 +--- a/src/nox/netapps/topology/pytopology.i ++++ b/src/nox/netapps/topology/pytopology.i +@@ -18,6 +18,8 @@ + + %module "nox.netapps.topology" + ++// Hack to get it to compile -BL ++%include "std_list.i" + %{ + #include "pytopology.hh" + #include "pyrt/pycontext.hh" +-- +1.7.5.4 + diff --git a/util/nox-patches/README b/util/nox-patches/README index 5c8dce8..b74a668 100644 --- a/util/nox-patches/README +++ b/util/nox-patches/README @@ -1 +1,2 @@ -This patch adds the OpenFlow tutorial module source code to nox-destiny. +0001: This patch adds the OpenFlow tutorial module source code to nox-destiny. +0002: This patch hacks nox-destiny to compile on Ubuntu 12.04. From 79dcdc0491713eff97c65a2412779300552040a3 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 14 May 2012 16:58:36 -0700 Subject: [PATCH 136/250] Add libconfig-dev dependency for oflops. --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 8186d87..14e3732 100755 --- a/util/install.sh +++ b/util/install.sh @@ -383,7 +383,7 @@ function oftest { function cbench { echo "Installing cbench..." - $install libsnmp-dev libpcap-dev + $install libsnmp-dev libpcap-dev libconfig-dev cd ~/ git clone git://openflow.org/oflops.git cd oflops From 2f8dfe5810a297ebc4c64c44ce38337ff2aeac51 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 14 May 2012 17:29:24 -0700 Subject: [PATCH 137/250] Ignore error installing OVS controller, and disable its startup script. --- util/install.sh | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/util/install.sh b/util/install.sh index 14e3732..94893c5 100755 --- a/util/install.sh +++ b/util/install.sh @@ -213,16 +213,19 @@ function wireshark { # Install Open vSwitch # Instructions derived from OVS INSTALL, INSTALL.OpenFlow and README files. + function ovs { echo "Installing Open vSwitch..." # 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; then + 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` @@ -234,21 +237,11 @@ function ovs { # Annoyingly, things seem to be missing without this flag $pkginst --force-confmiss $pkg done - # Switch can run on its own, but - # Mininet should control the controller - 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 - echo "Done (hopefully) installing packages" - cd ~ - return + ovspresent=1 fi # Otherwise try distribution's OVS packages - if [ "$DIST" = "Ubuntu" ] && [ `echo "$RELEASE >= 11.10" | bc` = 1 ]; then + 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. @@ -256,10 +249,28 @@ function ovs { $install openvswitch-datapath-dkms fi if $install openvswitch-switch openvswitch-controller; then - return + echo "Ignoring error installing openvswitch-controller" fi + ovspresent=1 fi + # Switch can run on its own, but + # Mininet should control the controller + 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 ~ + return + fi + + # Otherwise attempt to install from source + $install pkg-config gcc make python-dev libssl-dev libtool if [ "$DIST" = "Debian" ]; then @@ -344,7 +355,7 @@ function nox { # Apply patches git checkout -b tutorial-destiny git am ~/mininet/util/nox-patches/*tutorial-port-nox-destiny*.patch - if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` -eq 1 ]; then + if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` = 1 ]; then git am ~/mininet/util/nox-patches/*nox-ubuntu12-hacks.patch fi From b91008345fcce629786c3fa3eb1a3769f0e806e4 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 16 May 2012 23:59:12 -0700 Subject: [PATCH 138/250] Fix pexec('echo foo', shell=True) --- mininet/node.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mininet/node.py b/mininet/node.py index c12ae08..d210e42 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -297,6 +297,8 @@ class Node( object ): elif type( args[ 0 ] ) is str: # popen("cmd arg1 arg2...") cmd = args[ 0 ].split() + else: + raise Exception( 'popen() requires a string or list' ) elif len( args ) > 0: # popen( cmd, arg1, arg2... ) cmd = args @@ -304,6 +306,9 @@ class Node( object ): mncmd = defaults[ 'mncmd' ] del defaults[ 'mncmd' ] cmd = mncmd + cmd + # Shell requires a string, not a list! + if defaults.get( 'shell', False ): + cmd = ' '.join( cmd ) return Popen( cmd, **defaults ) def pexec( self, *args, **kwargs ): From ae2ede7994edd4d1325390f63ceb83f06d3430a4 Mon Sep 17 00:00:00 2001 From: Nikhil Handigol Date: Thu, 17 May 2012 17:05:37 +0000 Subject: [PATCH 139/250] bug fix: link config --- mininet/link.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index 9f46dcb..04c95de 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -196,36 +196,35 @@ class TCIntf( Intf ): # are specifying the correct sizes. For now I have used # the same settings we had in the mininet-hifi code. if use_hfsc: - cmds = [ '%s qdisc add dev %s root handle 1:0 hfsc default 1', + cmds += [ '%s qdisc add dev %s root handle 1:0 hfsc default 1', '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] elif use_tbf: latency_us = 10 * 1500 * 8 / bw - cmds = ['%s qdisc add dev %s root handle 1: tbf ' + + cmds += ['%s qdisc add dev %s root handle 1: tbf ' + 'rate %fMbit burst 15000 latency %fus' % ( bw, latency_us ) ] else: - cmds = [ '%s qdisc add dev %s root handle 1:0 htb default 1', + cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1', '%s class add dev %s parent 1:0 classid 1:1 htb ' + 'rate %fMbit burst 15k' % bw ] parent = ' parent 1:1 ' # ECN or RED if enable_ecn: - cmds = [ '%s qdisc add dev %s' + parent + + cmds += [ '%s qdisc add dev %s' + parent + 'handle 10: red limit 1000000 ' + 'min 20000 max 25000 avpkt 1000 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1 ecn' % bw ] parent = ' parent 10: ' elif enable_red: - cmds = [ '%s qdisc add dev %s' + parent + + cmds += [ '%s qdisc add dev %s' + parent + 'handle 10: red limit 1000000 ' + 'min 20000 max 25000 avpkt 1000 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1' % bw ] parent = ' parent 10: ' - return cmds, parent @staticmethod From 9c6620d85d64f1a7408d13d0e43dea4b102819fd Mon Sep 17 00:00:00 2001 From: Nikhil Handigol Date: Thu, 17 May 2012 17:13:30 +0000 Subject: [PATCH 140/250] modified HTB to fix the perfect synchronization bug --- util/sch_htb-ofbuf/Makefile | 6 + util/sch_htb-ofbuf/README | 10 + util/sch_htb-ofbuf/sch_htb.c | 1644 ++++++++++++++++++++++++++++++++++ 3 files changed, 1660 insertions(+) create mode 100644 util/sch_htb-ofbuf/Makefile create mode 100644 util/sch_htb-ofbuf/README create mode 100644 util/sch_htb-ofbuf/sch_htb.c diff --git a/util/sch_htb-ofbuf/Makefile b/util/sch_htb-ofbuf/Makefile new file mode 100644 index 0000000..4bdfdc9 --- /dev/null +++ b/util/sch_htb-ofbuf/Makefile @@ -0,0 +1,6 @@ +obj-m = sch_htb.o +KVERSION = $(shell uname -r) +all: + make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules +clean: + make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean diff --git a/util/sch_htb-ofbuf/README b/util/sch_htb-ofbuf/README new file mode 100644 index 0000000..711ed77 --- /dev/null +++ b/util/sch_htb-ofbuf/README @@ -0,0 +1,10 @@ +Modified sch_htb implementation with ofbuf support. + +To compile, just type make. To use this module instead +of regular sch_htb, do: + +0. make +1. rmmod sch_htb +2. insmod ./sch_htb.ko + +To revert, just rmmod sch_htb. diff --git a/util/sch_htb-ofbuf/sch_htb.c b/util/sch_htb-ofbuf/sch_htb.c new file mode 100644 index 0000000..baead1c --- /dev/null +++ b/util/sch_htb-ofbuf/sch_htb.c @@ -0,0 +1,1644 @@ +#define OFBUF (1) +/* + * net/sched/sch_htb.c Hierarchical token bucket, feed tree version + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Martin Devera, + * + * Credits (in time order) for older HTB versions: + * Stef Coene + * HTB support at LARTC mailing list + * Ondrej Kraus, + * found missing INIT_QDISC(htb) + * Vladimir Smelhaus, Aamer Akhter, Bert Hubert + * helped a lot to locate nasty class stall bug + * Andi Kleen, Jamal Hadi, Bert Hubert + * code review and helpful comments on shaping + * Tomasz Wrona, + * created test case so that I was able to fix nasty bug + * Wilfried Weissmann + * spotted bug in dequeue code and helped with fix + * Jiri Fojtasek + * fixed requeue routine + * and many others. thanks. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* HTB algorithm. + Author: devik@cdi.cz + ======================================================================== + HTB is like TBF with multiple classes. It is also similar to CBQ because + it allows to assign priority to each class in hierarchy. + In fact it is another implementation of Floyd's formal sharing. + + Levels: + Each class is assigned level. Leaf has ALWAYS level 0 and root + classes have level TC_HTB_MAXDEPTH-1. Interior nodes has level + one less than their parent. +*/ + +static int htb_hysteresis __read_mostly = 0; /* whether to use mode hysteresis for speedup */ +#define HTB_VER 0x30011 /* major must be matched with number suplied by TC as version */ + +#if HTB_VER >> 16 != TC_HTB_PROTOVER +#error "Mismatched sch_htb.c and pkt_sch.h" +#endif + +/* Module parameter and sysfs export */ +module_param (htb_hysteresis, int, 0640); +MODULE_PARM_DESC(htb_hysteresis, "Hysteresis mode, less CPU load, less accurate"); + +/* used internaly to keep status of single class */ +enum htb_cmode { + HTB_CANT_SEND, /* class can't send and can't borrow */ + HTB_MAY_BORROW, /* class can't send but may borrow */ + HTB_CAN_SEND /* class can send */ +}; + +/* interior & leaf nodes; props specific to leaves are marked L: */ +struct htb_class { + struct Qdisc_class_common common; + /* general class parameters */ + struct gnet_stats_basic_packed bstats; + struct gnet_stats_queue qstats; + struct gnet_stats_rate_est rate_est; + struct tc_htb_xstats xstats; /* our special stats */ + int refcnt; /* usage count of this class */ + + /* topology */ + int level; /* our level (see above) */ + unsigned int children; + struct htb_class *parent; /* parent class */ + + int prio; /* these two are used only by leaves... */ + int quantum; /* but stored for parent-to-leaf return */ + + union { + struct htb_class_leaf { + struct Qdisc *q; + int deficit[TC_HTB_MAXDEPTH]; + struct list_head drop_list; + } leaf; + struct htb_class_inner { + struct rb_root feed[TC_HTB_NUMPRIO]; /* feed trees */ + struct rb_node *ptr[TC_HTB_NUMPRIO]; /* current class ptr */ + /* When class changes from state 1->2 and disconnects from + * parent's feed then we lost ptr value and start from the + * first child again. Here we store classid of the + * last valid ptr (used when ptr is NULL). + */ + u32 last_ptr_id[TC_HTB_NUMPRIO]; + } inner; + } un; + struct rb_node node[TC_HTB_NUMPRIO]; /* node for self or feed tree */ + struct rb_node pq_node; /* node for event queue */ + psched_time_t pq_key; + + int prio_activity; /* for which prios are we active */ + enum htb_cmode cmode; /* current mode of the class */ + + /* class attached filters */ + struct tcf_proto *filter_list; + int filter_cnt; + + /* token bucket parameters */ + struct qdisc_rate_table *rate; /* rate table of the class itself */ + struct qdisc_rate_table *ceil; /* ceiling rate (limits borrows too) */ + long buffer, cbuffer; /* token bucket depth/rate */ + psched_tdiff_t mbuffer; /* max wait time */ + long tokens, ctokens; /* current number of tokens */ + psched_time_t t_c; /* checkpoint time */ +}; + +struct htb_sched { + struct Qdisc_class_hash clhash; + struct list_head drops[TC_HTB_NUMPRIO];/* active leaves (for drops) */ + + /* self list - roots of self generating tree */ + struct rb_root row[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO]; + int row_mask[TC_HTB_MAXDEPTH]; + struct rb_node *ptr[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO]; + u32 last_ptr_id[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO]; + + /* self wait list - roots of wait PQs per row */ + struct rb_root wait_pq[TC_HTB_MAXDEPTH]; + + /* time of nearest event per level (row) */ + psched_time_t near_ev_cache[TC_HTB_MAXDEPTH]; + + int defcls; /* class where unclassified flows go to */ + + /* filters for qdisc itself */ + struct tcf_proto *filter_list; + + int rate2quantum; /* quant = rate / rate2quantum */ + psched_time_t now; /* cached dequeue time */ + struct qdisc_watchdog watchdog; + + /* non shaped skbs; let them go directly thru */ + struct sk_buff_head direct_queue; + int direct_qlen; /* max qlen of above */ + + long direct_pkts; + +#if OFBUF + /* overflow buffer */ + struct sk_buff_head ofbuf; + int ofbuf_queued; /* # packets queued in above */ +#endif + +#define HTB_WARN_TOOMANYEVENTS 0x1 + unsigned int warned; /* only one warning */ + struct work_struct work; +}; + +/* find class in global hash table using given handle */ +static inline struct htb_class *htb_find(u32 handle, struct Qdisc *sch) +{ + struct htb_sched *q = qdisc_priv(sch); + struct Qdisc_class_common *clc; + + clc = qdisc_class_find(&q->clhash, handle); + if (clc == NULL) + return NULL; + return container_of(clc, struct htb_class, common); +} + +/** + * htb_classify - classify a packet into class + * + * It returns NULL if the packet should be dropped or -1 if the packet + * should be passed directly thru. In all other cases leaf class is returned. + * We allow direct class selection by classid in priority. The we examine + * filters in qdisc and in inner nodes (if higher filter points to the inner + * node). If we end up with classid MAJOR:0 we enqueue the skb into special + * internal fifo (direct). These packets then go directly thru. If we still + * have no valid leaf we try to use MAJOR:default leaf. It still unsuccessful + * then finish and return direct queue. + */ +#define HTB_DIRECT ((struct htb_class *)-1L) + +static struct htb_class *htb_classify(struct sk_buff *skb, struct Qdisc *sch, + int *qerr) +{ + struct htb_sched *q = qdisc_priv(sch); + struct htb_class *cl; + struct tcf_result res; + struct tcf_proto *tcf; + int result; + + /* allow to select class by setting skb->priority to valid classid; + * note that nfmark can be used too by attaching filter fw with no + * rules in it + */ + if (skb->priority == sch->handle) + return HTB_DIRECT; /* X:0 (direct flow) selected */ + cl = htb_find(skb->priority, sch); + if (cl && cl->level == 0) + return cl; + + *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; + tcf = q->filter_list; + while (tcf && (result = tc_classify(skb, tcf, &res)) >= 0) { +#ifdef CONFIG_NET_CLS_ACT + switch (result) { + case TC_ACT_QUEUED: + case TC_ACT_STOLEN: + *qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN; + case TC_ACT_SHOT: + return NULL; + } +#endif + cl = (void *)res.class; + if (!cl) { + if (res.classid == sch->handle) + return HTB_DIRECT; /* X:0 (direct flow) */ + cl = htb_find(res.classid, sch); + if (!cl) + break; /* filter selected invalid classid */ + } + if (!cl->level) + return cl; /* we hit leaf; return it */ + + /* we have got inner class; apply inner filter chain */ + tcf = cl->filter_list; + } + /* classification failed; try to use default class */ + cl = htb_find(TC_H_MAKE(TC_H_MAJ(sch->handle), q->defcls), sch); + if (!cl || cl->level) + return HTB_DIRECT; /* bad default .. this is safe bet */ + return cl; +} + +/** + * htb_add_to_id_tree - adds class to the round robin list + * + * Routine adds class to the list (actually tree) sorted by classid. + * Make sure that class is not already on such list for given prio. + */ +static void htb_add_to_id_tree(struct rb_root *root, + struct htb_class *cl, int prio) +{ + struct rb_node **p = &root->rb_node, *parent = NULL; + + while (*p) { + struct htb_class *c; + parent = *p; + c = rb_entry(parent, struct htb_class, node[prio]); + + if (cl->common.classid > c->common.classid) + p = &parent->rb_right; + else + p = &parent->rb_left; + } + rb_link_node(&cl->node[prio], parent, p); + rb_insert_color(&cl->node[prio], root); +} + +/** + * htb_add_to_wait_tree - adds class to the event queue with delay + * + * The class is added to priority event queue to indicate that class will + * change its mode in cl->pq_key microseconds. Make sure that class is not + * already in the queue. + */ +static void htb_add_to_wait_tree(struct htb_sched *q, + struct htb_class *cl, long delay) +{ + struct rb_node **p = &q->wait_pq[cl->level].rb_node, *parent = NULL; + + cl->pq_key = q->now + delay; + if (cl->pq_key == q->now) + cl->pq_key++; + + /* update the nearest event cache */ + if (q->near_ev_cache[cl->level] > cl->pq_key) + q->near_ev_cache[cl->level] = cl->pq_key; + + while (*p) { + struct htb_class *c; + parent = *p; + c = rb_entry(parent, struct htb_class, pq_node); + if (cl->pq_key >= c->pq_key) + p = &parent->rb_right; + else + p = &parent->rb_left; + } + rb_link_node(&cl->pq_node, parent, p); + rb_insert_color(&cl->pq_node, &q->wait_pq[cl->level]); +} + +/** + * htb_next_rb_node - finds next node in binary tree + * + * When we are past last key we return NULL. + * Average complexity is 2 steps per call. + */ +static inline void htb_next_rb_node(struct rb_node **n) +{ + *n = rb_next(*n); +} + +/** + * htb_add_class_to_row - add class to its row + * + * The class is added to row at priorities marked in mask. + * It does nothing if mask == 0. + */ +static inline void htb_add_class_to_row(struct htb_sched *q, + struct htb_class *cl, int mask) +{ + q->row_mask[cl->level] |= mask; + while (mask) { + int prio = ffz(~mask); + mask &= ~(1 << prio); + htb_add_to_id_tree(q->row[cl->level] + prio, cl, prio); + } +} + +/* If this triggers, it is a bug in this code, but it need not be fatal */ +static void htb_safe_rb_erase(struct rb_node *rb, struct rb_root *root) +{ + if (RB_EMPTY_NODE(rb)) { + WARN_ON(1); + } else { + rb_erase(rb, root); + RB_CLEAR_NODE(rb); + } +} + + +/** + * htb_remove_class_from_row - removes class from its row + * + * The class is removed from row at priorities marked in mask. + * It does nothing if mask == 0. + */ +static inline void htb_remove_class_from_row(struct htb_sched *q, + struct htb_class *cl, int mask) +{ + int m = 0; + + while (mask) { + int prio = ffz(~mask); + + mask &= ~(1 << prio); + if (q->ptr[cl->level][prio] == cl->node + prio) + htb_next_rb_node(q->ptr[cl->level] + prio); + + htb_safe_rb_erase(cl->node + prio, q->row[cl->level] + prio); + if (!q->row[cl->level][prio].rb_node) + m |= 1 << prio; + } + q->row_mask[cl->level] &= ~m; +} + +/** + * htb_activate_prios - creates active classe's feed chain + * + * The class is connected to ancestors and/or appropriate rows + * for priorities it is participating on. cl->cmode must be new + * (activated) mode. It does nothing if cl->prio_activity == 0. + */ +static void htb_activate_prios(struct htb_sched *q, struct htb_class *cl) +{ + struct htb_class *p = cl->parent; + long m, mask = cl->prio_activity; + + while (cl->cmode == HTB_MAY_BORROW && p && mask) { + m = mask; + while (m) { + int prio = ffz(~m); + m &= ~(1 << prio); + + if (p->un.inner.feed[prio].rb_node) + /* parent already has its feed in use so that + * reset bit in mask as parent is already ok + */ + mask &= ~(1 << prio); + + htb_add_to_id_tree(p->un.inner.feed + prio, cl, prio); + } + p->prio_activity |= mask; + cl = p; + p = cl->parent; + + } + if (cl->cmode == HTB_CAN_SEND && mask) + htb_add_class_to_row(q, cl, mask); +} + +/** + * htb_deactivate_prios - remove class from feed chain + * + * cl->cmode must represent old mode (before deactivation). It does + * nothing if cl->prio_activity == 0. Class is removed from all feed + * chains and rows. + */ +static void htb_deactivate_prios(struct htb_sched *q, struct htb_class *cl) +{ + struct htb_class *p = cl->parent; + long m, mask = cl->prio_activity; + + while (cl->cmode == HTB_MAY_BORROW && p && mask) { + m = mask; + mask = 0; + while (m) { + int prio = ffz(~m); + m &= ~(1 << prio); + + if (p->un.inner.ptr[prio] == cl->node + prio) { + /* we are removing child which is pointed to from + * parent feed - forget the pointer but remember + * classid + */ + p->un.inner.last_ptr_id[prio] = cl->common.classid; + p->un.inner.ptr[prio] = NULL; + } + + htb_safe_rb_erase(cl->node + prio, p->un.inner.feed + prio); + + if (!p->un.inner.feed[prio].rb_node) + mask |= 1 << prio; + } + + p->prio_activity &= ~mask; + cl = p; + p = cl->parent; + + } + if (cl->cmode == HTB_CAN_SEND && mask) + htb_remove_class_from_row(q, cl, mask); +} + +static inline long htb_lowater(const struct htb_class *cl) +{ + if (htb_hysteresis) + return cl->cmode != HTB_CANT_SEND ? -cl->cbuffer : 0; + else + return 0; +} +static inline long htb_hiwater(const struct htb_class *cl) +{ + if (htb_hysteresis) + return cl->cmode == HTB_CAN_SEND ? -cl->buffer : 0; + else + return 0; +} + + +/** + * htb_class_mode - computes and returns current class mode + * + * It computes cl's mode at time cl->t_c+diff and returns it. If mode + * is not HTB_CAN_SEND then cl->pq_key is updated to time difference + * from now to time when cl will change its state. + * Also it is worth to note that class mode doesn't change simply + * at cl->{c,}tokens == 0 but there can rather be hysteresis of + * 0 .. -cl->{c,}buffer range. It is meant to limit number of + * mode transitions per time unit. The speed gain is about 1/6. + */ +static inline enum htb_cmode +htb_class_mode(struct htb_class *cl, long *diff) +{ + long toks; + + if ((toks = (cl->ctokens + *diff)) < htb_lowater(cl)) { + *diff = -toks; + return HTB_CANT_SEND; + } + + if ((toks = (cl->tokens + *diff)) >= htb_hiwater(cl)) + return HTB_CAN_SEND; + + *diff = -toks; + return HTB_MAY_BORROW; +} + +/** + * htb_change_class_mode - changes classe's mode + * + * This should be the only way how to change classe's mode under normal + * cirsumstances. Routine will update feed lists linkage, change mode + * and add class to the wait event queue if appropriate. New mode should + * be different from old one and cl->pq_key has to be valid if changing + * to mode other than HTB_CAN_SEND (see htb_add_to_wait_tree). + */ +static void +htb_change_class_mode(struct htb_sched *q, struct htb_class *cl, long *diff) +{ + enum htb_cmode new_mode = htb_class_mode(cl, diff); + + if (new_mode == cl->cmode) + return; + + if (cl->prio_activity) { /* not necessary: speed optimization */ + if (cl->cmode != HTB_CANT_SEND) + htb_deactivate_prios(q, cl); + cl->cmode = new_mode; + if (new_mode != HTB_CANT_SEND) + htb_activate_prios(q, cl); + } else + cl->cmode = new_mode; +} + +/** + * htb_activate - inserts leaf cl into appropriate active feeds + * + * Routine learns (new) priority of leaf and activates feed chain + * for the prio. It can be called on already active leaf safely. + * It also adds leaf into droplist. + */ +static inline void htb_activate(struct htb_sched *q, struct htb_class *cl) +{ + WARN_ON(cl->level || !cl->un.leaf.q || !cl->un.leaf.q->q.qlen); + + if (!cl->prio_activity) { + cl->prio_activity = 1 << cl->prio; + htb_activate_prios(q, cl); + list_add_tail(&cl->un.leaf.drop_list, + q->drops + cl->prio); + } +} + +/** + * htb_deactivate - remove leaf cl from active feeds + * + * Make sure that leaf is active. In the other words it can't be called + * with non-active leaf. It also removes class from the drop list. + */ +static inline void htb_deactivate(struct htb_sched *q, struct htb_class *cl) +{ + WARN_ON(!cl->prio_activity); + + htb_deactivate_prios(q, cl); + cl->prio_activity = 0; + list_del_init(&cl->un.leaf.drop_list); +} + +static int htb_enqueue(struct sk_buff *skb, struct Qdisc *sch) +{ + int uninitialized_var(ret); + struct htb_sched *q = qdisc_priv(sch); + struct htb_class *cl = htb_classify(skb, sch, &ret); + +#if OFBUF + if(cl != HTB_DIRECT && cl) + skb_get(skb); +#endif + + if (cl == HTB_DIRECT) { + /* enqueue to helper queue */ + if (q->direct_queue.qlen < q->direct_qlen) { + __skb_queue_tail(&q->direct_queue, skb); + q->direct_pkts++; + } else { + kfree_skb(skb); + sch->qstats.drops++; + return NET_XMIT_DROP; + } +#ifdef CONFIG_NET_CLS_ACT + } else if (!cl) { + if (ret & __NET_XMIT_BYPASS) + sch->qstats.drops++; + kfree_skb(skb); + return ret; +#endif + } else if ((ret = qdisc_enqueue(skb, cl->un.leaf.q)) != NET_XMIT_SUCCESS) { + /* We shouldn't drop this, but enqueue it into ofbuf */ + // TODO: is skb actually valid? + // Ans: looks like qdisc_enqueue will end up freeing the packet + // if enqueue failed. So we should incr refcnt before calling qdisc_enqueue... +#if OFBUF + __skb_queue_tail(&q->ofbuf, skb); + q->ofbuf_queued++; +#else + if (net_xmit_drop_count(ret)) { + sch->qstats.drops++; + cl->qstats.drops++; + } + return ret; +#endif + } else { + bstats_update(&cl->bstats, skb); + htb_activate(q, cl); +#if OFBUF + kfree_skb(skb); +#endif + } + + sch->q.qlen++; + return NET_XMIT_SUCCESS; +} + +static inline void htb_accnt_tokens(struct htb_class *cl, int bytes, long diff) +{ + long toks = diff + cl->tokens; + + if (toks > cl->buffer) + toks = cl->buffer; + toks -= (long) qdisc_l2t(cl->rate, bytes); + if (toks <= -cl->mbuffer) + toks = 1 - cl->mbuffer; + + cl->tokens = toks; +} + +static inline void htb_accnt_ctokens(struct htb_class *cl, int bytes, long diff) +{ + long toks = diff + cl->ctokens; + + if (toks > cl->cbuffer) + toks = cl->cbuffer; + toks -= (long) qdisc_l2t(cl->ceil, bytes); + if (toks <= -cl->mbuffer) + toks = 1 - cl->mbuffer; + + cl->ctokens = toks; +} + +/** + * htb_charge_class - charges amount "bytes" to leaf and ancestors + * + * Routine assumes that packet "bytes" long was dequeued from leaf cl + * borrowing from "level". It accounts bytes to ceil leaky bucket for + * leaf and all ancestors and to rate bucket for ancestors at levels + * "level" and higher. It also handles possible change of mode resulting + * from the update. Note that mode can also increase here (MAY_BORROW to + * CAN_SEND) because we can use more precise clock that event queue here. + * In such case we remove class from event queue first. + */ +static void htb_charge_class(struct htb_sched *q, struct htb_class *cl, + int level, struct sk_buff *skb) +{ + int bytes = qdisc_pkt_len(skb); + enum htb_cmode old_mode; + long diff; + + while (cl) { + diff = psched_tdiff_bounded(q->now, cl->t_c, cl->mbuffer); + if (cl->level >= level) { + if (cl->level == level) + cl->xstats.lends++; + htb_accnt_tokens(cl, bytes, diff); + } else { + cl->xstats.borrows++; + cl->tokens += diff; /* we moved t_c; update tokens */ + } + htb_accnt_ctokens(cl, bytes, diff); + cl->t_c = q->now; + + old_mode = cl->cmode; + diff = 0; + htb_change_class_mode(q, cl, &diff); + if (old_mode != cl->cmode) { + if (old_mode != HTB_CAN_SEND) + htb_safe_rb_erase(&cl->pq_node, q->wait_pq + cl->level); + if (cl->cmode != HTB_CAN_SEND) + htb_add_to_wait_tree(q, cl, diff); + } + + /* update basic stats except for leaves which are already updated */ + if (cl->level) + bstats_update(&cl->bstats, skb); + + cl = cl->parent; + } +} + +/** + * htb_do_events - make mode changes to classes at the level + * + * Scans event queue for pending events and applies them. Returns time of + * next pending event (0 for no event in pq, q->now for too many events). + * Note: Applied are events whose have cl->pq_key <= q->now. + */ +static psched_time_t htb_do_events(struct htb_sched *q, int level, + unsigned long start) +{ + /* don't run for longer than 2 jiffies; 2 is used instead of + * 1 to simplify things when jiffy is going to be incremented + * too soon + */ + unsigned long stop_at = start + 2; + while (time_before(jiffies, stop_at)) { + struct htb_class *cl; + long diff; + struct rb_node *p = rb_first(&q->wait_pq[level]); + + if (!p) + return 0; + + cl = rb_entry(p, struct htb_class, pq_node); + if (cl->pq_key > q->now) + return cl->pq_key; + + htb_safe_rb_erase(p, q->wait_pq + level); + diff = psched_tdiff_bounded(q->now, cl->t_c, cl->mbuffer); + htb_change_class_mode(q, cl, &diff); + if (cl->cmode != HTB_CAN_SEND) + htb_add_to_wait_tree(q, cl, diff); + } + + /* too much load - let's continue after a break for scheduling */ + if (!(q->warned & HTB_WARN_TOOMANYEVENTS)) { + pr_warning("htb: too many events!\n"); + q->warned |= HTB_WARN_TOOMANYEVENTS; + } + + return q->now; +} + +/* Returns class->node+prio from id-tree where classe's id is >= id. NULL + * is no such one exists. + */ +static struct rb_node *htb_id_find_next_upper(int prio, struct rb_node *n, + u32 id) +{ + struct rb_node *r = NULL; + while (n) { + struct htb_class *cl = + rb_entry(n, struct htb_class, node[prio]); + + if (id > cl->common.classid) { + n = n->rb_right; + } else if (id < cl->common.classid) { + r = n; + n = n->rb_left; + } else { + return n; + } + } + return r; +} + +/** + * htb_lookup_leaf - returns next leaf class in DRR order + * + * Find leaf where current feed pointers points to. + */ +static struct htb_class *htb_lookup_leaf(struct rb_root *tree, int prio, + struct rb_node **pptr, u32 * pid) +{ + int i; + struct { + struct rb_node *root; + struct rb_node **pptr; + u32 *pid; + } stk[TC_HTB_MAXDEPTH], *sp = stk; + + BUG_ON(!tree->rb_node); + sp->root = tree->rb_node; + sp->pptr = pptr; + sp->pid = pid; + + for (i = 0; i < 65535; i++) { + if (!*sp->pptr && *sp->pid) { + /* ptr was invalidated but id is valid - try to recover + * the original or next ptr + */ + *sp->pptr = + htb_id_find_next_upper(prio, sp->root, *sp->pid); + } + *sp->pid = 0; /* ptr is valid now so that remove this hint as it + * can become out of date quickly + */ + if (!*sp->pptr) { /* we are at right end; rewind & go up */ + *sp->pptr = sp->root; + while ((*sp->pptr)->rb_left) + *sp->pptr = (*sp->pptr)->rb_left; + if (sp > stk) { + sp--; + if (!*sp->pptr) { + WARN_ON(1); + return NULL; + } + htb_next_rb_node(sp->pptr); + } + } else { + struct htb_class *cl; + cl = rb_entry(*sp->pptr, struct htb_class, node[prio]); + if (!cl->level) + return cl; + (++sp)->root = cl->un.inner.feed[prio].rb_node; + sp->pptr = cl->un.inner.ptr + prio; + sp->pid = cl->un.inner.last_ptr_id + prio; + } + } + WARN_ON(1); + return NULL; +} + +/* dequeues packet at given priority and level; call only if + * you are sure that there is active class at prio/level + */ +static struct sk_buff *htb_dequeue_tree(struct htb_sched *q, int prio, + int level) +{ + struct sk_buff *skb = NULL; + struct htb_class *cl, *start; + /* look initial class up in the row */ + start = cl = htb_lookup_leaf(q->row[level] + prio, prio, + q->ptr[level] + prio, + q->last_ptr_id[level] + prio); + + do { +next: + if (unlikely(!cl)) + return NULL; + + /* class can be empty - it is unlikely but can be true if leaf + * qdisc drops packets in enqueue routine or if someone used + * graft operation on the leaf since last dequeue; + * simply deactivate and skip such class + */ + if (unlikely(cl->un.leaf.q->q.qlen == 0)) { + struct htb_class *next; + htb_deactivate(q, cl); + + /* row/level might become empty */ + if ((q->row_mask[level] & (1 << prio)) == 0) + return NULL; + + next = htb_lookup_leaf(q->row[level] + prio, + prio, q->ptr[level] + prio, + q->last_ptr_id[level] + prio); + + if (cl == start) /* fix start if we just deleted it */ + start = next; + cl = next; + goto next; + } + + skb = cl->un.leaf.q->dequeue(cl->un.leaf.q); + if (likely(skb != NULL)) + break; + + qdisc_warn_nonwc("htb", cl->un.leaf.q); + htb_next_rb_node((level ? cl->parent->un.inner.ptr : q-> + ptr[0]) + prio); + cl = htb_lookup_leaf(q->row[level] + prio, prio, + q->ptr[level] + prio, + q->last_ptr_id[level] + prio); + + } while (cl != start); + + if (likely(skb != NULL)) { + cl->un.leaf.deficit[level] -= qdisc_pkt_len(skb); + if (cl->un.leaf.deficit[level] < 0) { + cl->un.leaf.deficit[level] += cl->quantum; + htb_next_rb_node((level ? cl->parent->un.inner.ptr : q-> + ptr[0]) + prio); + } + /* this used to be after charge_class but this constelation + * gives us slightly better performance + */ + if (!cl->un.leaf.q->q.qlen) + htb_deactivate(q, cl); + htb_charge_class(q, cl, level, skb); + } + return skb; +} + +static struct sk_buff *htb_dequeue(struct Qdisc *sch) +{ + struct sk_buff *skb; + struct htb_sched *q = qdisc_priv(sch); + int level; + psched_time_t next_event; + unsigned long start_at; + u32 r, i; + struct sk_buff *pkt; + + /* try to dequeue direct packets as high prio (!) to minimize cpu work */ + skb = __skb_dequeue(&q->direct_queue); + if (skb != NULL) { +ok: + qdisc_bstats_update(sch, skb); + qdisc_unthrottled(sch); + sch->q.qlen--; +#if OFBUF + if(q->ofbuf_queued > 0) { + i = 0; + r = net_random() % q->ofbuf_queued; + // enqueue the rth packet and drop the rest + while((pkt = __skb_dequeue(&q->ofbuf)) != NULL) { + if(i == r) { + // the chosen one + htb_enqueue(pkt, sch); + } else { + kfree_skb(pkt); + } + i++; + } + q->ofbuf_queued = 0; + } +#endif + return skb; + } + + if (!sch->q.qlen) + goto fin; + q->now = psched_get_time(); + start_at = jiffies; + + next_event = q->now + 5 * PSCHED_TICKS_PER_SEC; + + for (level = 0; level < TC_HTB_MAXDEPTH; level++) { + /* common case optimization - skip event handler quickly */ + int m; + psched_time_t event; + + if (q->now >= q->near_ev_cache[level]) { + event = htb_do_events(q, level, start_at); + if (!event) + event = q->now + PSCHED_TICKS_PER_SEC; + q->near_ev_cache[level] = event; + } else + event = q->near_ev_cache[level]; + + if (next_event > event) + next_event = event; + + m = ~q->row_mask[level]; + while (m != (int)(-1)) { + int prio = ffz(m); + + m |= 1 << prio; + skb = htb_dequeue_tree(q, prio, level); + if (likely(skb != NULL)) + goto ok; + } + } + sch->qstats.overlimits++; + if (likely(next_event > q->now)) + qdisc_watchdog_schedule(&q->watchdog, next_event); + else + schedule_work(&q->work); +fin: + return skb; +} + +/* try to drop from each class (by prio) until one succeed */ +static unsigned int htb_drop(struct Qdisc *sch) +{ + struct htb_sched *q = qdisc_priv(sch); + int prio; + + for (prio = TC_HTB_NUMPRIO - 1; prio >= 0; prio--) { + struct list_head *p; + list_for_each(p, q->drops + prio) { + struct htb_class *cl = list_entry(p, struct htb_class, + un.leaf.drop_list); + unsigned int len; + if (cl->un.leaf.q->ops->drop && + (len = cl->un.leaf.q->ops->drop(cl->un.leaf.q))) { + sch->q.qlen--; + if (!cl->un.leaf.q->q.qlen) + htb_deactivate(q, cl); + return len; + } + } + } + return 0; +} + +/* reset all classes */ +/* always caled under BH & queue lock */ +static void htb_reset(struct Qdisc *sch) +{ + struct htb_sched *q = qdisc_priv(sch); + struct htb_class *cl; + struct hlist_node *n; + unsigned int i; + + for (i = 0; i < q->clhash.hashsize; i++) { + hlist_for_each_entry(cl, n, &q->clhash.hash[i], common.hnode) { + if (cl->level) + memset(&cl->un.inner, 0, sizeof(cl->un.inner)); + else { + if (cl->un.leaf.q) + qdisc_reset(cl->un.leaf.q); + INIT_LIST_HEAD(&cl->un.leaf.drop_list); + } + cl->prio_activity = 0; + cl->cmode = HTB_CAN_SEND; + + } + } + qdisc_watchdog_cancel(&q->watchdog); + __skb_queue_purge(&q->direct_queue); + sch->q.qlen = 0; +#if OFBUF + __skb_queue_purge(&q->ofbuf); + q->ofbuf_queued = 0; +#endif + memset(q->row, 0, sizeof(q->row)); + memset(q->row_mask, 0, sizeof(q->row_mask)); + memset(q->wait_pq, 0, sizeof(q->wait_pq)); + memset(q->ptr, 0, sizeof(q->ptr)); + for (i = 0; i < TC_HTB_NUMPRIO; i++) + INIT_LIST_HEAD(q->drops + i); +} + +static const struct nla_policy htb_policy[TCA_HTB_MAX + 1] = { + [TCA_HTB_PARMS] = { .len = sizeof(struct tc_htb_opt) }, + [TCA_HTB_INIT] = { .len = sizeof(struct tc_htb_glob) }, + [TCA_HTB_CTAB] = { .type = NLA_BINARY, .len = TC_RTAB_SIZE }, + [TCA_HTB_RTAB] = { .type = NLA_BINARY, .len = TC_RTAB_SIZE }, +}; + +static void htb_work_func(struct work_struct *work) +{ + struct htb_sched *q = container_of(work, struct htb_sched, work); + struct Qdisc *sch = q->watchdog.qdisc; + + __netif_schedule(qdisc_root(sch)); +} + +static int htb_init(struct Qdisc *sch, struct nlattr *opt) +{ + struct htb_sched *q = qdisc_priv(sch); + struct nlattr *tb[TCA_HTB_INIT + 1]; + struct tc_htb_glob *gopt; + int err; + int i; + + if (!opt) + return -EINVAL; + + err = nla_parse_nested(tb, TCA_HTB_INIT, opt, htb_policy); + if (err < 0) + return err; + + if (tb[TCA_HTB_INIT] == NULL) { + pr_err("HTB: hey probably you have bad tc tool ?\n"); + return -EINVAL; + } + gopt = nla_data(tb[TCA_HTB_INIT]); + if (gopt->version != HTB_VER >> 16) { + pr_err("HTB: need tc/htb version %d (minor is %d), you have %d\n", + HTB_VER >> 16, HTB_VER & 0xffff, gopt->version); + return -EINVAL; + } + + err = qdisc_class_hash_init(&q->clhash); + if (err < 0) + return err; + for (i = 0; i < TC_HTB_NUMPRIO; i++) + INIT_LIST_HEAD(q->drops + i); + + qdisc_watchdog_init(&q->watchdog, sch); + INIT_WORK(&q->work, htb_work_func); + skb_queue_head_init(&q->direct_queue); + +#if OFBUF + skb_queue_head_init(&q->ofbuf); + q->ofbuf_queued = 0; +#endif + + q->direct_qlen = qdisc_dev(sch)->tx_queue_len; + + if (q->direct_qlen < 2) /* some devices have zero tx_queue_len */ + q->direct_qlen = 2; + + if ((q->rate2quantum = gopt->rate2quantum) < 1) + q->rate2quantum = 1; + q->defcls = gopt->defcls; + + return 0; +} + +static int htb_dump(struct Qdisc *sch, struct sk_buff *skb) +{ + spinlock_t *root_lock = qdisc_root_sleeping_lock(sch); + struct htb_sched *q = qdisc_priv(sch); + struct nlattr *nest; + struct tc_htb_glob gopt; + + spin_lock_bh(root_lock); + + gopt.direct_pkts = q->direct_pkts; + gopt.version = HTB_VER; + gopt.rate2quantum = q->rate2quantum; + gopt.defcls = q->defcls; + gopt.debug = 0; + + nest = nla_nest_start(skb, TCA_OPTIONS); + if (nest == NULL) + goto nla_put_failure; + NLA_PUT(skb, TCA_HTB_INIT, sizeof(gopt), &gopt); + nla_nest_end(skb, nest); + + spin_unlock_bh(root_lock); + return skb->len; + +nla_put_failure: + spin_unlock_bh(root_lock); + nla_nest_cancel(skb, nest); + return -1; +} + +static int htb_dump_class(struct Qdisc *sch, unsigned long arg, + struct sk_buff *skb, struct tcmsg *tcm) +{ + struct htb_class *cl = (struct htb_class *)arg; + spinlock_t *root_lock = qdisc_root_sleeping_lock(sch); + struct nlattr *nest; + struct tc_htb_opt opt; + + spin_lock_bh(root_lock); + tcm->tcm_parent = cl->parent ? cl->parent->common.classid : TC_H_ROOT; + tcm->tcm_handle = cl->common.classid; + if (!cl->level && cl->un.leaf.q) + tcm->tcm_info = cl->un.leaf.q->handle; + + nest = nla_nest_start(skb, TCA_OPTIONS); + if (nest == NULL) + goto nla_put_failure; + + memset(&opt, 0, sizeof(opt)); + + opt.rate = cl->rate->rate; + opt.buffer = cl->buffer; + opt.ceil = cl->ceil->rate; + opt.cbuffer = cl->cbuffer; + opt.quantum = cl->quantum; + opt.prio = cl->prio; + opt.level = cl->level; + NLA_PUT(skb, TCA_HTB_PARMS, sizeof(opt), &opt); + + nla_nest_end(skb, nest); + spin_unlock_bh(root_lock); + return skb->len; + +nla_put_failure: + spin_unlock_bh(root_lock); + nla_nest_cancel(skb, nest); + return -1; +} + +static int +htb_dump_class_stats(struct Qdisc *sch, unsigned long arg, struct gnet_dump *d) +{ + struct htb_class *cl = (struct htb_class *)arg; + + if (!cl->level && cl->un.leaf.q) + cl->qstats.qlen = cl->un.leaf.q->q.qlen; + cl->xstats.tokens = cl->tokens; + cl->xstats.ctokens = cl->ctokens; + + if (gnet_stats_copy_basic(d, &cl->bstats) < 0 || + gnet_stats_copy_rate_est(d, NULL, &cl->rate_est) < 0 || + gnet_stats_copy_queue(d, &cl->qstats) < 0) + return -1; + + return gnet_stats_copy_app(d, &cl->xstats, sizeof(cl->xstats)); +} + +static int htb_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, + struct Qdisc **old) +{ + struct htb_class *cl = (struct htb_class *)arg; + + if (cl->level) + return -EINVAL; + if (new == NULL && + (new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, + cl->common.classid)) == NULL) + return -ENOBUFS; + + sch_tree_lock(sch); + *old = cl->un.leaf.q; + cl->un.leaf.q = new; + if (*old != NULL) { + qdisc_tree_decrease_qlen(*old, (*old)->q.qlen); + qdisc_reset(*old); + } + sch_tree_unlock(sch); + return 0; +} + +static struct Qdisc *htb_leaf(struct Qdisc *sch, unsigned long arg) +{ + struct htb_class *cl = (struct htb_class *)arg; + return !cl->level ? cl->un.leaf.q : NULL; +} + +static void htb_qlen_notify(struct Qdisc *sch, unsigned long arg) +{ + struct htb_class *cl = (struct htb_class *)arg; + + if (cl->un.leaf.q->q.qlen == 0) + htb_deactivate(qdisc_priv(sch), cl); +} + +static unsigned long htb_get(struct Qdisc *sch, u32 classid) +{ + struct htb_class *cl = htb_find(classid, sch); + if (cl) + cl->refcnt++; + return (unsigned long)cl; +} + +static inline int htb_parent_last_child(struct htb_class *cl) +{ + if (!cl->parent) + /* the root class */ + return 0; + if (cl->parent->children > 1) + /* not the last child */ + return 0; + return 1; +} + +static void htb_parent_to_leaf(struct htb_sched *q, struct htb_class *cl, + struct Qdisc *new_q) +{ + struct htb_class *parent = cl->parent; + + WARN_ON(cl->level || !cl->un.leaf.q || cl->prio_activity); + + if (parent->cmode != HTB_CAN_SEND) + htb_safe_rb_erase(&parent->pq_node, q->wait_pq + parent->level); + + parent->level = 0; + memset(&parent->un.inner, 0, sizeof(parent->un.inner)); + INIT_LIST_HEAD(&parent->un.leaf.drop_list); + parent->un.leaf.q = new_q ? new_q : &noop_qdisc; + parent->tokens = parent->buffer; + parent->ctokens = parent->cbuffer; + parent->t_c = psched_get_time(); + parent->cmode = HTB_CAN_SEND; +} + +static void htb_destroy_class(struct Qdisc *sch, struct htb_class *cl) +{ + if (!cl->level) { + WARN_ON(!cl->un.leaf.q); + qdisc_destroy(cl->un.leaf.q); + } + gen_kill_estimator(&cl->bstats, &cl->rate_est); + qdisc_put_rtab(cl->rate); + qdisc_put_rtab(cl->ceil); + + tcf_destroy_chain(&cl->filter_list); + kfree(cl); +} + +static void htb_destroy(struct Qdisc *sch) +{ + struct htb_sched *q = qdisc_priv(sch); + struct hlist_node *n, *next; + struct htb_class *cl; + unsigned int i; + + cancel_work_sync(&q->work); + qdisc_watchdog_cancel(&q->watchdog); + /* This line used to be after htb_destroy_class call below + * and surprisingly it worked in 2.4. But it must precede it + * because filter need its target class alive to be able to call + * unbind_filter on it (without Oops). + */ + tcf_destroy_chain(&q->filter_list); + + for (i = 0; i < q->clhash.hashsize; i++) { + hlist_for_each_entry(cl, n, &q->clhash.hash[i], common.hnode) + tcf_destroy_chain(&cl->filter_list); + } + for (i = 0; i < q->clhash.hashsize; i++) { + hlist_for_each_entry_safe(cl, n, next, &q->clhash.hash[i], + common.hnode) + htb_destroy_class(sch, cl); + } + qdisc_class_hash_destroy(&q->clhash); + __skb_queue_purge(&q->direct_queue); +#if OFBUF + __skb_queue_purge(&q->ofbuf); + q->ofbuf_queued = 0; +#endif +} + +static int htb_delete(struct Qdisc *sch, unsigned long arg) +{ + struct htb_sched *q = qdisc_priv(sch); + struct htb_class *cl = (struct htb_class *)arg; + unsigned int qlen; + struct Qdisc *new_q = NULL; + int last_child = 0; + + // TODO: why don't allow to delete subtree ? references ? does + // tc subsys quarantee us that in htb_destroy it holds no class + // refs so that we can remove children safely there ? + if (cl->children || cl->filter_cnt) + return -EBUSY; + + if (!cl->level && htb_parent_last_child(cl)) { + new_q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, + cl->parent->common.classid); + last_child = 1; + } + + sch_tree_lock(sch); + + if (!cl->level) { + qlen = cl->un.leaf.q->q.qlen; + qdisc_reset(cl->un.leaf.q); + qdisc_tree_decrease_qlen(cl->un.leaf.q, qlen); + } + + /* delete from hash and active; remainder in destroy_class */ + qdisc_class_hash_remove(&q->clhash, &cl->common); + if (cl->parent) + cl->parent->children--; + + if (cl->prio_activity) + htb_deactivate(q, cl); + + if (cl->cmode != HTB_CAN_SEND) + htb_safe_rb_erase(&cl->pq_node, q->wait_pq + cl->level); + + if (last_child) + htb_parent_to_leaf(q, cl, new_q); + + BUG_ON(--cl->refcnt == 0); + /* + * This shouldn't happen: we "hold" one cops->get() when called + * from tc_ctl_tclass; the destroy method is done from cops->put(). + */ + + sch_tree_unlock(sch); + return 0; +} + +static void htb_put(struct Qdisc *sch, unsigned long arg) +{ + struct htb_class *cl = (struct htb_class *)arg; + + if (--cl->refcnt == 0) + htb_destroy_class(sch, cl); +} + +static int htb_change_class(struct Qdisc *sch, u32 classid, + u32 parentid, struct nlattr **tca, + unsigned long *arg) +{ + int err = -EINVAL; + struct htb_sched *q = qdisc_priv(sch); + struct htb_class *cl = (struct htb_class *)*arg, *parent; + struct nlattr *opt = tca[TCA_OPTIONS]; + struct qdisc_rate_table *rtab = NULL, *ctab = NULL; + struct nlattr *tb[__TCA_HTB_MAX]; + struct tc_htb_opt *hopt; + + /* extract all subattrs from opt attr */ + if (!opt) + goto failure; + + err = nla_parse_nested(tb, TCA_HTB_MAX, opt, htb_policy); + if (err < 0) + goto failure; + + err = -EINVAL; + if (tb[TCA_HTB_PARMS] == NULL) + goto failure; + + parent = parentid == TC_H_ROOT ? NULL : htb_find(parentid, sch); + + hopt = nla_data(tb[TCA_HTB_PARMS]); + + rtab = qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB]); + ctab = qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB]); + if (!rtab || !ctab) + goto failure; + + if (!cl) { /* new class */ + struct Qdisc *new_q; + int prio; + struct { + struct nlattr nla; + struct gnet_estimator opt; + } est = { + .nla = { + .nla_len = nla_attr_size(sizeof(est.opt)), + .nla_type = TCA_RATE, + }, + .opt = { + /* 4s interval, 16s averaging constant */ + .interval = 2, + .ewma_log = 2, + }, + }; + + /* check for valid classid */ + if (!classid || TC_H_MAJ(classid ^ sch->handle) || + htb_find(classid, sch)) + goto failure; + + /* check maximal depth */ + if (parent && parent->parent && parent->parent->level < 2) { + pr_err("htb: tree is too deep\n"); + goto failure; + } + err = -ENOBUFS; + cl = kzalloc(sizeof(*cl), GFP_KERNEL); + if (!cl) + goto failure; + + err = gen_new_estimator(&cl->bstats, &cl->rate_est, + qdisc_root_sleeping_lock(sch), + tca[TCA_RATE] ? : &est.nla); + if (err) { + kfree(cl); + goto failure; + } + + cl->refcnt = 1; + cl->children = 0; + INIT_LIST_HEAD(&cl->un.leaf.drop_list); + RB_CLEAR_NODE(&cl->pq_node); + + for (prio = 0; prio < TC_HTB_NUMPRIO; prio++) + RB_CLEAR_NODE(&cl->node[prio]); + + /* create leaf qdisc early because it uses kmalloc(GFP_KERNEL) + * so that can't be used inside of sch_tree_lock + * -- thanks to Karlis Peisenieks + */ + new_q = qdisc_create_dflt(sch->dev_queue, + &pfifo_qdisc_ops, classid); + sch_tree_lock(sch); + if (parent && !parent->level) { + unsigned int qlen = parent->un.leaf.q->q.qlen; + + /* turn parent into inner node */ + qdisc_reset(parent->un.leaf.q); + qdisc_tree_decrease_qlen(parent->un.leaf.q, qlen); + qdisc_destroy(parent->un.leaf.q); + if (parent->prio_activity) + htb_deactivate(q, parent); + + /* remove from evt list because of level change */ + if (parent->cmode != HTB_CAN_SEND) { + htb_safe_rb_erase(&parent->pq_node, q->wait_pq); + parent->cmode = HTB_CAN_SEND; + } + parent->level = (parent->parent ? parent->parent->level + : TC_HTB_MAXDEPTH) - 1; + memset(&parent->un.inner, 0, sizeof(parent->un.inner)); + } + /* leaf (we) needs elementary qdisc */ + cl->un.leaf.q = new_q ? new_q : &noop_qdisc; + + cl->common.classid = classid; + cl->parent = parent; + + /* set class to be in HTB_CAN_SEND state */ + cl->tokens = hopt->buffer; + cl->ctokens = hopt->cbuffer; + cl->mbuffer = 60 * PSCHED_TICKS_PER_SEC; /* 1min */ + cl->t_c = psched_get_time(); + cl->cmode = HTB_CAN_SEND; + + /* attach to the hash list and parent's family */ + qdisc_class_hash_insert(&q->clhash, &cl->common); + if (parent) + parent->children++; + } else { + if (tca[TCA_RATE]) { + err = gen_replace_estimator(&cl->bstats, &cl->rate_est, + qdisc_root_sleeping_lock(sch), + tca[TCA_RATE]); + if (err) + return err; + } + sch_tree_lock(sch); + } + + /* it used to be a nasty bug here, we have to check that node + * is really leaf before changing cl->un.leaf ! + */ + if (!cl->level) { + cl->quantum = rtab->rate.rate / q->rate2quantum; + if (!hopt->quantum && cl->quantum < 1000) { + pr_warning( + "HTB: quantum of class %X is small. Consider r2q change.\n", + cl->common.classid); + cl->quantum = 1000; + } + if (!hopt->quantum && cl->quantum > 200000) { + pr_warning( + "HTB: quantum of class %X is big. Consider r2q change.\n", + cl->common.classid); + cl->quantum = 200000; + } + if (hopt->quantum) + cl->quantum = hopt->quantum; + if ((cl->prio = hopt->prio) >= TC_HTB_NUMPRIO) + cl->prio = TC_HTB_NUMPRIO - 1; + } + + cl->buffer = hopt->buffer; + cl->cbuffer = hopt->cbuffer; + if (cl->rate) + qdisc_put_rtab(cl->rate); + cl->rate = rtab; + if (cl->ceil) + qdisc_put_rtab(cl->ceil); + cl->ceil = ctab; + sch_tree_unlock(sch); + + qdisc_class_hash_grow(sch, &q->clhash); + + *arg = (unsigned long)cl; + return 0; + +failure: + if (rtab) + qdisc_put_rtab(rtab); + if (ctab) + qdisc_put_rtab(ctab); + return err; +} + +static struct tcf_proto **htb_find_tcf(struct Qdisc *sch, unsigned long arg) +{ + struct htb_sched *q = qdisc_priv(sch); + struct htb_class *cl = (struct htb_class *)arg; + struct tcf_proto **fl = cl ? &cl->filter_list : &q->filter_list; + + return fl; +} + +static unsigned long htb_bind_filter(struct Qdisc *sch, unsigned long parent, + u32 classid) +{ + struct htb_class *cl = htb_find(classid, sch); + + /*if (cl && !cl->level) return 0; + * The line above used to be there to prevent attaching filters to + * leaves. But at least tc_index filter uses this just to get class + * for other reasons so that we have to allow for it. + * ---- + * 19.6.2002 As Werner explained it is ok - bind filter is just + * another way to "lock" the class - unlike "get" this lock can + * be broken by class during destroy IIUC. + */ + if (cl) + cl->filter_cnt++; + return (unsigned long)cl; +} + +static void htb_unbind_filter(struct Qdisc *sch, unsigned long arg) +{ + struct htb_class *cl = (struct htb_class *)arg; + + if (cl) + cl->filter_cnt--; +} + +static void htb_walk(struct Qdisc *sch, struct qdisc_walker *arg) +{ + struct htb_sched *q = qdisc_priv(sch); + struct htb_class *cl; + struct hlist_node *n; + unsigned int i; + + if (arg->stop) + return; + + for (i = 0; i < q->clhash.hashsize; i++) { + hlist_for_each_entry(cl, n, &q->clhash.hash[i], common.hnode) { + if (arg->count < arg->skip) { + arg->count++; + continue; + } + if (arg->fn(sch, (unsigned long)cl, arg) < 0) { + arg->stop = 1; + return; + } + arg->count++; + } + } +} + +static const struct Qdisc_class_ops htb_class_ops = { + .graft = htb_graft, + .leaf = htb_leaf, + .qlen_notify = htb_qlen_notify, + .get = htb_get, + .put = htb_put, + .change = htb_change_class, + .delete = htb_delete, + .walk = htb_walk, + .tcf_chain = htb_find_tcf, + .bind_tcf = htb_bind_filter, + .unbind_tcf = htb_unbind_filter, + .dump = htb_dump_class, + .dump_stats = htb_dump_class_stats, +}; + +static struct Qdisc_ops htb_qdisc_ops __read_mostly = { + .cl_ops = &htb_class_ops, + .id = "htb", + .priv_size = sizeof(struct htb_sched), + .enqueue = htb_enqueue, + .dequeue = htb_dequeue, + .peek = qdisc_peek_dequeued, + .drop = htb_drop, + .init = htb_init, + .reset = htb_reset, + .destroy = htb_destroy, + .dump = htb_dump, + .owner = THIS_MODULE, +}; + +static int __init htb_module_init(void) +{ + return register_qdisc(&htb_qdisc_ops); +} +static void __exit htb_module_exit(void) +{ + unregister_qdisc(&htb_qdisc_ops); +} + +module_init(htb_module_init) +module_exit(htb_module_exit) +MODULE_LICENSE("GPL"); From b97c0392a9d83a3b4e93a78cb85c7f6ffe0d54fe Mon Sep 17 00:00:00 2001 From: Nikhil Handigol Date: Thu, 17 May 2012 19:59:55 +0000 Subject: [PATCH 141/250] make install for sch_htb.ko --- util/sch_htb-ofbuf/Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/sch_htb-ofbuf/Makefile b/util/sch_htb-ofbuf/Makefile index 4bdfdc9..c4d714f 100644 --- a/util/sch_htb-ofbuf/Makefile +++ b/util/sch_htb-ofbuf/Makefile @@ -2,5 +2,10 @@ obj-m = sch_htb.o KVERSION = $(shell uname -r) all: make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules +install: + test -e /lib/modules/$(KVERSION)/kernel/net/sched/sch_htb.ko.bak || mv /lib/modules/$(KVERSION)/kernel/net/sched/sch_htb.ko /lib/modules/$(KVERSION)/kernel/net/sched/sch_htb.ko.bak + cp sch_htb.ko /lib/modules/$(KVERSION)/kernel/net/sched/sch_htb.ko + rmmod sch_htb + modprobe sch_htb clean: make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean From 8f310286f80c2885fff582b8f936a437efc3c8d9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 21 May 2012 23:07:36 -0700 Subject: [PATCH 142/250] Add setLinkInfo() which seems to be missing. --- mininet/topo.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mininet/topo.py b/mininet/topo.py index 9ee0a02..86b5cf7 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -148,6 +148,11 @@ class Topo(object): src, dst = self.sorted([src, dst]) return self.link_info[(src, dst)] + def setlinkInfo( self, src, dst, info ): + "Set link metadata" + src, dst = self.sorted([src, dst]) + self.link_info[(src, dst)] = info + def nodeInfo( self, name ): "Return metadata (dict) for node" info = self.node_info[ name ] From e1ca7196c7260d272b450ffab1fb5b539ab1ffb6 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 23 May 2012 13:37:02 -0700 Subject: [PATCH 143/250] configHosts(): don't try to configure nonexistent interfaces. --- mininet/net.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mininet/net.py b/mininet/net.py index 827d72f..8b9e7c8 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -244,7 +244,12 @@ class Mininet( object ): "Configure a set of hosts." for host in self.hosts: info( host.name + ' ' ) - host.configDefault( defaultRoute=host.defaultIntf() ) + intf = host.defaultIntf() + if intf: + host.configDefault( defaultRoute=intf ) + else: + # Don't configure nonexistent intf + host.configDefault( ip=None, mac=None ) # You're low priority, dude! # BL: do we want to do this here or not? # May not make sense if we have CPU lmiting... From 06f7408cf2a7a72b34e58149e22d440999227e8c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 23 May 2012 20:04:36 -0700 Subject: [PATCH 144/250] Fix popen to allow popen( cmd, arg1, arg2, arg3 ) --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index d210e42..6db75c6 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -301,7 +301,7 @@ class Node( object ): raise Exception( 'popen() requires a string or list' ) elif len( args ) > 0: # popen( cmd, arg1, arg2... ) - cmd = args + cmd = list( args ) # Attach to our namespace using mnexec -a mncmd = defaults[ 'mncmd' ] del defaults[ 'mncmd' ] From f1bf3c60e0a7b9261079343e41ebcd2e60abbf2b Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 23 May 2012 20:56:35 -0700 Subject: [PATCH 145/250] Added popenpoll.py example of using popen()/pmonitor() --- examples/README | 5 +++++ examples/popenpoll.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100755 examples/popenpoll.py diff --git a/examples/README b/examples/README index 3a05f9b..b6c199b 100644 --- a/examples/README +++ b/examples/README @@ -48,6 +48,11 @@ multitest.py: This example creates a network and runs multiple tests on it. +popenpoll.py: + +This example demonstrates monitoring output from multiple hosts using +the node.popen() interface (which returns Popen objects) and pmonitor(). + scratchnet.py, scratchnetuser.py: These two examples demonstrate how to create a network by using the lowest- diff --git a/examples/popenpoll.py b/examples/popenpoll.py new file mode 100755 index 0000000..c27619c --- /dev/null +++ b/examples/popenpoll.py @@ -0,0 +1,33 @@ +#!/usr/bin/python + +"Monitor multiple hosts using popen()/pmonitor()" + +from mininet.net import Mininet +from mininet.topo import SingleSwitchTopo +from mininet.util import pmonitor +from time import time +from signal import SIGINT + +def pmonitorTest( N=3, seconds=10 ): + "Run pings and monitor multiple hosts using pmonitor" + topo = SingleSwitchTopo( N ) + net = Mininet( topo ) + net.start() + hosts = net.hosts + print "Starting test..." + server = hosts[ 0 ] + popens = {} + for h in hosts: + popens[ h ] = h.popen('ping', server.IP() ) + print "Monitoring output for", seconds, "seconds" + endTime = time() + seconds + for h, line in pmonitor( popens, timeoutms=500 ): + if h: + print '%s: %s' % ( h.name, line ), + if time() >= endTime: + for p in popens.values(): + p.send_signal( SIGINT ) + net.stop() + +if __name__ == '__main__': + pmonitorTest() From 8c778bb081cde7d10d88a9af2c6ae8f51be6da40 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 23 May 2012 21:06:15 -0700 Subject: [PATCH 146/250] Fix indentation errors. --- examples/popenpoll.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/popenpoll.py b/examples/popenpoll.py index c27619c..7e13420 100755 --- a/examples/popenpoll.py +++ b/examples/popenpoll.py @@ -19,15 +19,15 @@ def pmonitorTest( N=3, seconds=10 ): popens = {} for h in hosts: popens[ h ] = h.popen('ping', server.IP() ) - print "Monitoring output for", seconds, "seconds" - endTime = time() + seconds - for h, line in pmonitor( popens, timeoutms=500 ): - if h: - print '%s: %s' % ( h.name, line ), - if time() >= endTime: - for p in popens.values(): - p.send_signal( SIGINT ) - net.stop() + print "Monitoring output for", seconds, "seconds" + endTime = time() + seconds + for h, line in pmonitor( popens, timeoutms=500 ): + if h: + print '%s: %s' % ( h.name, line ), + if time() >= endTime: + for p in popens.values(): + p.send_signal( SIGINT ) + net.stop() if __name__ == '__main__': pmonitorTest() From e4514a4ecbaf47d6a3b381f1b2d48ab32cfdae27 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 23 May 2012 21:09:14 -0700 Subject: [PATCH 147/250] Still more indentation errors. ;-p --- examples/popenpoll.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/popenpoll.py b/examples/popenpoll.py index 7e13420..30ca4fc 100755 --- a/examples/popenpoll.py +++ b/examples/popenpoll.py @@ -24,9 +24,9 @@ def pmonitorTest( N=3, seconds=10 ): for h, line in pmonitor( popens, timeoutms=500 ): if h: print '%s: %s' % ( h.name, line ), - if time() >= endTime: - for p in popens.values(): - p.send_signal( SIGINT ) + if time() >= endTime: + for p in popens.values(): + p.send_signal( SIGINT ) net.stop() if __name__ == '__main__': From 6c947bca07c0fa7d8d6415c43fd3f4851f114794 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 23 May 2012 21:12:24 -0700 Subject: [PATCH 148/250] More indent errors - curse you emacs. --- examples/popenpoll.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/popenpoll.py b/examples/popenpoll.py index 30ca4fc..c581c27 100755 --- a/examples/popenpoll.py +++ b/examples/popenpoll.py @@ -22,11 +22,11 @@ def pmonitorTest( N=3, seconds=10 ): print "Monitoring output for", seconds, "seconds" endTime = time() + seconds for h, line in pmonitor( popens, timeoutms=500 ): - if h: - print '%s: %s' % ( h.name, line ), - if time() >= endTime: - for p in popens.values(): - p.send_signal( SIGINT ) + if h: + print '%s: %s' % ( h.name, line ), + if time() >= endTime: + for p in popens.values(): + p.send_signal( SIGINT ) net.stop() if __name__ == '__main__': From f509ae282d5f836091b111ca9693637ac078dc16 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Fri, 25 May 2012 16:33:37 -0700 Subject: [PATCH 149/250] cli: add time command --- mininet/cli.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mininet/cli.py b/mininet/cli.py index 4b83fcd..ce59853 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -30,6 +30,7 @@ from cmd import Cmd from os import isatty from select import poll, POLLIN import sys +import time from mininet.log import info, output, error from mininet.term import makeTerms @@ -278,6 +279,13 @@ class CLI( Cmd ): output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' ) output( sw.dpctl( *args ) ) + def do_time( self, line ): + "Measure time taken for any command in Mininet." + start = time.time() + self.onecmd(line) + elapsed = time.time() - start + self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed) + def default( self, line ): """Called on an input line when the command prefix is not recognized. Overridden to run shell commands when a node is the first CLI argument. From 30b4b4e7f9135a626cf93ad018e919ec43f5dda7 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 29 May 2012 23:57:52 -0700 Subject: [PATCH 150/250] Rename and document customNode Now customConstructor, because it general to both links and nodes. --- bin/mn | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bin/mn b/bin/mn index a1173ca..02a7934 100755 --- a/bin/mn +++ b/bin/mn @@ -29,8 +29,12 @@ from mininet.topolib import TreeTopo from mininet.util import makeNumeric, custom -def customNode( constructors, argStr ): - "Return custom Node constructor based on argStr" +def customConstructor( constructors, argStr ): + """Return custom constructor based on argStr + + The args and key/val pairs in argsStr will be automatically applied + when the generated constructor is later used. + """ cname, newargs, kwargs = splitArgs( argStr ) constructor = constructors.get( cname, None ) @@ -39,7 +43,7 @@ def customNode( constructors, argStr ): ( cname, constructors.keys() ) ) def customized( name, *args, **params ): - "Customized Node constructor" + "Customized constructor, useful for Node, Link, and other classes" params.update( kwargs ) if not newargs: return constructor( name, *args, **params ) @@ -245,10 +249,10 @@ class MininetRunner( object ): start = time.time() topo = buildTopo( self.options.topo ) - switch = customNode( SWITCHES, self.options.switch ) - host = customNode( HOSTS, self.options.host ) - controller = customNode( CONTROLLERS, self.options.controller ) - link = customNode( LINKS, self.options.link ) + switch = customConstructor( SWITCHES, self.options.switch ) + host = customConstructor( HOSTS, self.options.host ) + controller = customConstructor( CONTROLLERS, self.options.controller ) + link = customConstructor( LINKS, self.options.link ) if self.validate: self.validate( self.options ) From 928c0761a02fbd8d80a2baab44bbaf1fb12b6315 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Wed, 30 May 2012 00:08:01 -0700 Subject: [PATCH 151/250] Move code from mn into mininet/util to enable reuse Any code in mn is not usable by other Python code. Hence, move this code into util, so other scripts can use it. --- bin/mn | 56 +++---------------------------------------------- mininet/util.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 53 deletions(-) diff --git a/bin/mn b/bin/mn index 02a7934..91a6369 100755 --- a/bin/mn +++ b/bin/mn @@ -26,33 +26,8 @@ from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo -from mininet.util import makeNumeric, custom - - -def customConstructor( constructors, argStr ): - """Return custom constructor based on argStr - - The args and key/val pairs in argsStr will be automatically applied - when the generated constructor is later used. - """ - cname, newargs, kwargs = splitArgs( argStr ) - constructor = constructors.get( cname, None ) - - if not constructor: - raise Exception( "error: %s is unknown - please specify one of %s" % - ( cname, constructors.keys() ) ) - - def customized( name, *args, **params ): - "Customized constructor, useful for Node, Link, and other classes" - params.update( kwargs ) - if not newargs: - return constructor( name, *args, **params ) - if args: - warn( 'warning: %s replacing %s with %s\n' % ( - constructor, args, newargs ) ) - return constructor( name, *newargs, **params ) - - return customized +from mininet.util import makeNumeric, custom, customConstructor, splitArgs +from mininet.util import buildTopo # built in topologies, created only when run @@ -93,31 +68,6 @@ ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair', 'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' } -def splitArgs( argstr ): - """Split argument string into usable python arguments - argstr: argument string with format fn,arg2,kw1=arg3... - returns: fn, args, kwargs""" - split = argstr.split( ',' ) - fn = split[ 0 ] - params = split[ 1: ] - # Convert int and float args; removes the need for function - # to be flexible with input arg formats. - args = [ makeNumeric( s ) for s in params if '=' not in s ] - kwargs = {} - for s in [ p for p in params if '=' in p ]: - key, val = s.split( '=' ) - kwargs[ key ] = makeNumeric( val ) - return fn, args, kwargs - - -def buildTopo( topoStr ): - "Create topology from string with format (object, arg1, arg2,...)." - topo, args, kwargs = splitArgs( topoStr ) - if topo not in TOPOS: - raise Exception( 'Invalid topo name %s' % topo ) - return TOPOS[ topo ]( *args, **kwargs ) - - def addDictOption( opts, choicesDict, default, name, helpStr=None ): """Convenience function to add choices dicts to OptionParser. opts: OptionParser instance @@ -248,7 +198,7 @@ class MininetRunner( object ): start = time.time() - topo = buildTopo( self.options.topo ) + topo = buildTopo( TOPOS, self.options.topo ) switch = customConstructor( SWITCHES, self.options.switch ) host = customConstructor( HOSTS, self.options.host ) controller = customConstructor( CONTROLLERS, self.options.controller ) diff --git a/mininet/util.py b/mininet/util.py index af4b17d..19cb552 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -400,3 +400,54 @@ def custom( cls, **params ): kwargs.update( params ) return cls( *args, **kwargs ) return customized + +def splitArgs( argstr ): + """Split argument string into usable python arguments + argstr: argument string with format fn,arg2,kw1=arg3... + returns: fn, args, kwargs""" + split = argstr.split( ',' ) + fn = split[ 0 ] + params = split[ 1: ] + # Convert int and float args; removes the need for function + # to be flexible with input arg formats. + args = [ makeNumeric( s ) for s in params if '=' not in s ] + kwargs = {} + for s in [ p for p in params if '=' in p ]: + key, val = s.split( '=' ) + kwargs[ key ] = makeNumeric( val ) + return fn, args, kwargs + +def customConstructor( constructors, argStr ): + """Return custom constructor based on argStr + + The args and key/val pairs in argsStr will be automatically applied + when the generated constructor is later used. + """ + cname, newargs, kwargs = splitArgs( argStr ) + constructor = constructors.get( cname, None ) + + if not constructor: + raise Exception( "error: %s is unknown - please specify one of %s" % + ( cname, constructors.keys() ) ) + + def customized( name, *args, **params ): + "Customized constructor, useful for Node, Link, and other classes" + params.update( kwargs ) + if not newargs: + return constructor( name, *args, **params ) + if args: + warn( 'warning: %s replacing %s with %s\n' % ( + constructor, args, newargs ) ) + return constructor( name, *newargs, **params ) + + return customized + +def buildTopo( topos, topoStr ): + """Create topology from string with format (object, arg1, arg2,...). + + input topos is a dict of topo names to constructors, possibly w/args. + """ + topo, args, kwargs = splitArgs( topoStr ) + if topo not in topos: + raise Exception( 'Invalid topo name %s' % topo ) + return topos[ topo ]( *args, **kwargs ) From 6bb5e12347848a83791872695a6241132019cedb Mon Sep 17 00:00:00 2001 From: Nikhil Handigol Date: Tue, 5 Jun 2012 12:16:32 -0700 Subject: [PATCH 152/250] RED bug fix: change avg. packet size --- mininet/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/link.py b/mininet/link.py index 04c95de..88c581d 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -214,7 +214,7 @@ class TCIntf( Intf ): if enable_ecn: cmds += [ '%s qdisc add dev %s' + parent + 'handle 10: red limit 1000000 ' + - 'min 20000 max 25000 avpkt 1000 ' + + 'min 30000 max 35000 avpkt 1500 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1 ecn' % bw ] parent = ' parent 10: ' From 107785ddf160ff54eb245080109c61e8a8d8cfa3 Mon Sep 17 00:00:00 2001 From: Nikhil Handigol Date: Tue, 5 Jun 2012 12:18:26 -0700 Subject: [PATCH 153/250] RED bug fix in another place --- mininet/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/link.py b/mininet/link.py index 88c581d..4e01496 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -221,7 +221,7 @@ class TCIntf( Intf ): elif enable_red: cmds += [ '%s qdisc add dev %s' + parent + 'handle 10: red limit 1000000 ' + - 'min 20000 max 25000 avpkt 1000 ' + + 'min 30000 max 35000 avpkt 1500 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1' % bw ] parent = ' parent 10: ' From 0f832c92266a3b44b46167b2ad66432d31676cf2 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 25 Jun 2012 14:14:09 -0700 Subject: [PATCH 154/250] Propagate prefix length to host IP configuration. --- mininet/net.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mininet/net.py b/mininet/net.py index 8b9e7c8..0774cd6 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -168,7 +168,8 @@ class Mininet( object ): # Default IP and MAC addresses defaults = { 'ip': ipAdd( self.nextIP, ipBaseNum=self.ipBaseNum, - prefixLen=self.prefixLen ) } + prefixLen=self.prefixLen ) + + '/%s' % self.prefixLen } if self.autoSetMacs: defaults[ 'mac'] = macColonHex( self.nextIP ) if self.autoPinCpus: From e04c207c3c0077beea8fd6d2528f4848a64c7a91 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 1 Jul 2012 19:06:58 -0700 Subject: [PATCH 155/250] Update for Mininet 2.0.0 development. --- INSTALL | 260 +++++++------------------------------------------------ LICENSE | 4 +- README | 13 +-- setup.py | 17 ++-- 4 files changed, 48 insertions(+), 246 deletions(-) diff --git a/INSTALL b/INSTALL index a03af19..2b8738a 100644 --- a/INSTALL +++ b/INSTALL @@ -1,12 +1,14 @@ Mininet Installation/Configuration Notes -Mininet 1.0.0 +Mininet 2.0.0d1 --- The supported installation methods for Mininet are 1) using -a pre-built VM image, and 2) native installation on Ubuntu or Debian. +a pre-built VM image, and 2) native installation on Ubuntu. You +can also easily create your own Mininet VM image (3). + (Other distributions may be supported in the future - if you would like to contribute an installation script, we would welcome it!) @@ -20,10 +22,15 @@ Boot up the VM image, log in, and follow the instructions on the wiki page. An additional advantage of using the VM image is that it doesn't mess with your native OS installation or damage it in any way. -2. Native installation (experimental!) for Ubuntu 10.04 LTS +2. Native installation on Ubuntu -If you are running Ubuntu 10.04 LTS (or possibly Debian 5), you may be -able to use our handy install.sh script, which is in mininet/util. +If you're reading this, you've probably already done it, but the command to +download the Mininet source code is; + + git clone git://openflow.org/mininet.git + +If you are running Ubuntu, you may be able to use our handy install.sh script, +which is in mininet/util. WARNING: USE AT YOUR OWN RISK! @@ -34,240 +41,39 @@ do anything completely terrible, you may want to look at the script before you run it, and you should make sure your system and home directory are backed up just in case! +To install Mininet itself, the OpenFlow reference implementation, and +Open vSwitch, you may use: + +$ mininet/util/install.sh -fnv + +This should be reasonably quick and the following command should work +after the installation: + +$ sudo mn --test pingall + To install ALL of the software which we use for OpenFlow tutorials, -you may use +including NOX classic, the OpenFlow WireShark dissector, the oftest +framework, and other potentially useful software (and to add some stuff +to /etc/sysctl.conf which may or may not be useful) you may use -$ mininet/util/install.sh +$ mininet/util/install.sh -a -This takes about 20-30 minutes. +This takes about 20 minutes on our test system. -Alternately, you can install just the pieces you need. +3. Creating your own Mininet/OpenFlow tutorial VM -We recommend the following steps, in order: +Creating your own Ubuntu Mininet VM for use with the OpenFlow tutorial +is easy! First, create a new Ubuntu VM. Then, run -[a) On Debian 5, first install a Mininet-compatible kernel: - $ mininet/util/install.sh -k - Reboot and run 'uname -r' to make sure you're running the new kernel.] +$ wget https://raw.github.com/mininet/mininet/util/vm/install-mininet-vm.sh +$ time install-mininet-vm.sh -b) Install mininet and its dependencies: - $ mininet/util/install.sh -n - -c) Install OpenFlow 1.0 and associated useful software - $ mininet/util/install.sh -f - -d) Install Open vSwitch and its kernel module - $ mininet/util/install.sh -vm - -e) If you wish to install the version of NOX we use in the tutorial: - $ mininet/util/install.sh -x - - Note: NOX development is progressing over time, so after you complete - the tutorial you may wish to install the latest and greatest NOX from - noxrepo.org. - -Good luck! Some additional installation notes are provided below, for -the brave and/or Linux-savvy, or those who are trying to understand what -is installed and why. +Good luck! p.s. Note that only one instance of Mininet is currently supported on a single machine - that's one reason we recommend using a VM to run it. --- -Mininet Manual Installation Notes - -These installation notes assume you understand how to do things like -compile kernels, apply patches, configure networks, write code, etc.. If -this is unfamiliar territory, or if you run into trouble, we recommend -using one of our pre-built virtual machine images (see above.) - -If you wish to try to create a VM to run Mininet, you may also wish -to look at the Wiki page: - -http://openflow.org/foswiki/bin/view/OpenFlow/MininetVMCreationNotes - -0. Obtaining Mininet - - If you're reading this, you've already done it, but the command to - download mininet is: - - git clone git://openflow.org/mininet.git - -1. Core Mininet installation - - The core Mininet installation requires gcc, make, python, - and setuptools. On Ubuntu and Debian you may install them with: - - # aptitude install gcc make python setuptools - - To install Mininet itself, with root privileges: - - # cd mininet - # make install - - This places the mininet package in /usr/lib/python-*/site-packages/, - so that 'import mininet' will work, and installs the primary mn - script (mn) as well as its helper utility (mnexec.) - - On Ubuntu and Debian, Mininet's dependencies and core files may also be - installed using mininet/util/install.sh -n - -2. Installation script for Ubuntu/Debian Lenny - - If you are running Ubuntu 10.04 or Debian Lenny, you may be able to use the - util/install.sh script to install a compatible Linux kernel as well as - other software including the OpenFlow reference implementation, the Open - vSwitch switch implementation, and the NOX OpenFlow controller. - - Many different installation options are possible by passing different - options to install.sh; install.sh -h lists them all. - - Assuming the mininet source tree is installed in ~/mininet, the steps to run - install.sh to install EVERYTHING we use for OpenFlow tutorials are: - - % cd - % time ~/mininet/util/install.sh # installs tons of stuff - % sudo reboot # to load new kernel - % ~/mininet/util/install.sh -c # to clean out unneeded kernel stuff - - This installs a lot of useful software, but it will take a while (30 - minutes or more, depending on your network connection, computer, etc..) - - Probably the minimal semi-useful configuration would be to install - Mininet itself, kernel support if necessary, and either the - reference OpenFlow switch or Open vSwitch. This could be installed - as follows: - - % sudo ~/mininet/util/install.sh -knvm - - Respectively, this installs kernel support, core mininet dependencies, - Open vSwitch, and the Open vSwitch kernel module. If a new kernel was - installed, then a reboot may be required. - - If install.sh cannot be used for some reason (e.g. you're on Fedora - or some other Linux - please don't say CentOS) or if you don't want to - install all of these components (they're useful!), the kernel and - OpenFlow software requirements are described in steps [3] and [4], - which follow. - - If you successfully used install.sh, congratulations! You're basically - done. Proceed to step [6] for additional advice. - -3. Linux Kernel requirements - - Mininet requires a kernel built with network namespace support enabled, - i.e. with CONFIG_NET_NS=Y, such as the kernel shipped with - Ubuntu 10.04 LTS, currently 2.6.32. On Ubuntu 10.04, you should not need - to install or build a custom kernel, although 2.6.33+ is faster at - tearing down virtual ethernet pairs. - - For Ubuntu and Debian, we provide a 2.6.33 kernel package which you may be - able to install using "util/install.sh -k". Note our kernel package - requires an ext2 or ext3 root file system, so it won't work if you have - a default Ubuntu install, which uses ext4. - - If your kernel wasn't compiled with CONFIG_NET_NS=Y, you will need to - build and install a kernel that does! >= 2.6.33 works better, but may - be harder to get working, depending on your Linux distribution. - - A script for building Debian packages for 2.6.33.1 is provided in - mininet/util/kbuild. You may wish to read it, as it applies patches - to enable 2.6.33.1 to build under debian-stable, and to enable the - tun driver to work correctly with Mininet. - - Earlier kernels (e.g. 2.6.29) work with CONFIG_NET_NS enabled and no - additional patches, but are much slower at removing veth interfaces, - resulting in much slower switch shutdown. - - For scalable configurations, you might need to increase some of your - kernel limits. Sample params are in util/sysctl_addon, which can be - appended to /etc/sysctl.conf (and modified as necessary for your - desired configuration): - - sudo su -c "cat sysctl_addon >> /etc/sysctl.conf" - - To save the config change, run: - - sudo sysctl -p - -4. OpenFlow software and configuration requirements - - Mininet requires either the reference OpenFlow switch implementation - (from openflowswitch.org) or Open vSwitch (openvswitch.org) to be - installed. "make test" requires the reference user space - implementations as well as Open vSwitch. Note the reference kernel - implementation is not currently included in OpenFlow 1.0. - - On Ubuntu and Debian, the install.sh script may be used with the '-f' - option to install the OpenFlow reference implementation, the '-v' option - to build Open vSwitch, and the '-m' option to install the Open vSwitch - kernel module into /lib/modules (note: you must build Open vSwitch first!) - - Mininet will automatically load and remove kernel module dependencies - for supported switch types, using modprobe and rmmod - but these - modules must be in a location where modprobe can find them (e.g. - something like /lib/modules/`uname -r`/kernel/drivers/net/) - - The reference OpenFlow controller (controller(8)) only supports 16 - switches by default! If you wish to run a network with more than 16 - switches, please recompile controller(8) with larger limits, or use a - different controller such as nox. A patch to controller(8) is included - as util/openflow-patches/controller.patch. - -5. Other software dependencies - - On Ubuntu and Debian, other Mininet dependencies may be installed using - the '-n' option of the install.sh script. - - To run the iperf test, you need to install iperf: - - sudo aptitude/yum install iperf - - We assume you already have ping installed. ;-) - - To use xterm or sshd with Mininet, you need the following: - - sudo aptitude/yum install sshd xterm screen - - Some examples may have additional requirements - consult the specific - example file for details. - - The install.sh script has an '-x' option to install the version of - NOX from the OpenFlow tutorial. - -6. Other notes and recommendations - - If you did not install certain useful packages and you wish to later, - it may be possible to install them using install.sh. - - Mininet should be run either on a machine with - no other important processes, or on a virtual machine (recommended!) - - Multiple concurrent Mininet instances are not supported! - -Good luck! - ---- - -Historical information on OpenFlow 0.8.9 and the reference kernel module: - - The kernel reference implementation has been deprecated, but it may - be possible to get it work with Mininet. - - To switch to the most recent OpenFlow 0.8.9 release branch (the most - recent one with full NOX support and kernel datapath support) in your - OpenFlow git tree: - - git checkout -b release/0.8.9 remotes/origin/release/0.8.9 - - A patch to enable datapath.c to compile with recent kernels - is included in util/openflow-patches/datapath.patch. - - In OpenFlow 1.0, switch port numbering starts at 1 (for better or for worse.) - To run with previous versions of OpenFlow, it may be necessary - to change SWITCH_PORT_BASE from 1 to 0 in node.py. - - - diff --git a/LICENSE b/LICENSE index 7546c7f..820ae85 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -Mininet 1.0.0 License +Mininet 2.0.0d1 License -Copyright (c) 2009-2011 Bob Lantz and Brandon Heller +Copyright (c) 2009-2012 Bob Lantz and Brandon Heller We are making Mininet available for public use and benefit with the expectation that others will use, modify and enhance the Software and diff --git a/README b/README index dc8d25f..f74d4d0 100644 --- a/README +++ b/README @@ -1,9 +1,9 @@ Mininet: A Simple Virtual Testbed for OpenFlow/SDN or -How to Squeeze a 1024-node OpenFlow Network onto your Laptop +How to Squeeze an OpenFlow Network onto your Laptop -Mininet 1.0.0 +Mininet 2.0.0d1 --- Welcome to Mininet! @@ -19,16 +19,9 @@ Linux kernel. Mininet may be invoked directly from the command line, and also provides a handy Python API for creating networks of varying sizes and topologies. -Mininet is currently in *limited alpha release*. We encourage you to -experiment with it and hope that you will provide us with feedback on -features, documentation, and how you're using it. We plan to make it -available publicly via a GPL or BSD license (probably in April), but please -don't distribute the code or URLs yet! The feedback you provide will help -us improve Mininet for general release. - In order to run Mininet, you must have: -* A Linux 2.6.26 or greater kernel compiled with network namespace support +* A Linux kernel compiled with network namespace support enabled (see INSTALL for additional information.) * An OpenFlow implementation (either the reference user or kernel diff --git a/setup.py b/setup.py index 8bcfb12..8525f83 100644 --- a/setup.py +++ b/setup.py @@ -11,23 +11,26 @@ modname = distname = 'mininet' setup( name=distname, - version='0.0.0', + version='2.0.0d1', description='Process-based OpenFlow emulator', author='Bob Lantz', author_email='rlantz@cs.stanford.edu', packages=find_packages(exclude='test'), long_description=""" -Insert longer description here. - """, + Mininet is a network emulator which uses lightweight + virtualization to create virtual networks for rapid + prototyping of Software-Defined Network (SDN) designs + using OpenFlow. http://openflow.org/mininet + """, classifiers=[ - "License :: OSI Approved :: GNU General Public License (GPL)", + "License :: OSI Approved :: BSD License", "Programming Language :: Python", - "Development Status :: 4 - Beta", + "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Internet", ], - keywords='networking protocol Internet OpenFlow', - license='unspecified', + keywords='networking emulator protocol Internet OpenFlow SDN', + license='BSD', install_requires=[ 'setuptools', 'networkx' From cb859243a571979384c561f663a2a64515e13b29 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 1 Jul 2012 19:16:41 -0700 Subject: [PATCH 156/250] Added Debian/Ubuntu packaging - thanks to James Page. --- debian/changelog | 6 ++++++ debian/compat | 1 + debian/control | 23 +++++++++++++++++++++++ debian/copyright | 0 debian/rules | 8 ++++++++ debian/source/format | 1 + 6 files changed, 39 insertions(+) create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules create mode 100644 debian/source/format diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..a85ec01 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +mininet (2.0.0d1) precise; urgency=low + + * Initial release + + -- Bob Lantz Sun, 01 Jul 2012 23:19:54 +0000 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..152d22a --- /dev/null +++ b/debian/control @@ -0,0 +1,23 @@ +Source: mininet +Section: net +Priority: extra +Maintainer: Ubuntu Developers +Standards-Version: 3.9.3 +Build-Depends: + debhelper (>= 7), + python-dev, + python-pkg-resources, + python-setuptools + +Package: mininet +Architecture: any +Depends: + openvswitch-switch, + ${misc:Depends}, + ${python:Depends}, + ${shlibs:Depends} +Description: Process-based network emulator + Mininet is a network emulator which uses lightweight + virtualization to create virtual networks for rapid + prototyping of Software-Defined Network (SDN) designs + using OpenFlow. http://openflow.org/mininet diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..e69de29 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..b587d32 --- /dev/null +++ b/debian/rules @@ -0,0 +1,8 @@ +#!/usr/bin/make -f + +%: + dh $@ --buildsystem=python_distutils --with=python2 + +override_dh_auto_build: + make mnexec && cp mnexec bin/ + dh_auto_build diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) From ec969b7f9996854a144d7eee31aad390a61f67aa Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 1 Jul 2012 20:31:30 -0700 Subject: [PATCH 157/250] Change default controller for mn to ovsc (ovs-controller.) Also add check to see if another controller is running - eventually we should really detect errors from starting the controller!! --- bin/mn | 2 +- debian/control | 2 ++ mininet/node.py | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/bin/mn b/bin/mn index 91a6369..f6b9e78 100755 --- a/bin/mn +++ b/bin/mn @@ -48,7 +48,7 @@ HOSTS = { 'proc': Host, 'rt': custom( CPULimitedHost, sched='rt' ), 'cfs': custom( CPULimitedHost, sched='cfs' ) } -CONTROLLERDEF = 'ref' +CONTROLLERDEF = 'ovsc' CONTROLLERS = { 'ref': Controller, 'ovsc': OVSController, 'nox': NOX, diff --git a/debian/control b/debian/control index 152d22a..649c234 100644 --- a/debian/control +++ b/debian/control @@ -13,6 +13,8 @@ Package: mininet Architecture: any Depends: openvswitch-switch, + openvswitch-controller, + telnet, ${misc:Depends}, ${python:Depends}, ${shlibs:Depends} diff --git a/mininet/node.py b/mininet/node.py index 6db75c6..8e4786d 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -975,6 +975,20 @@ class Controller( Node ): self.port = port Node.__init__( self, name, inNamespace=inNamespace, ip=ip, **params ) + self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary + self.checkListening() + + def checkListening( self ): + "Make sure no controllers are running on our port" + listening = self.cmd( "echo A | telnet -e A %s %d" % + ( self.ip, self.port ) ) + if 'Unable' not in listening: + servers = self.cmd( 'netstat -atp' ).split( '\n' ) + pstr = ':%d ' % self.port + info = servers[ 0:1 ] + [ s for s in servers if pstr in s ] + raise Exception( "Please shut down the controller which is" + " running on port %d:\n" % self.port + + '\n'.join( info ) ) def start( self ): """Start on controller. From 93bf7793cb67161241ac07907d0b051fa0d3e15c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 1 Jul 2012 23:22:05 -0700 Subject: [PATCH 158/250] Debian copyright wants a real file, so change LICENSE to symlink. --- LICENSE | 30 +----------------------------- debian/copyright | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 29 deletions(-) mode change 100644 => 120000 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 820ae85..0000000 --- a/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -Mininet 2.0.0d1 License - -Copyright (c) 2009-2012 Bob Lantz and Brandon Heller - -We are making Mininet available for public use and benefit with the -expectation that others will use, modify and enhance the Software and -contribute those enhancements back to the community. However, since we -would like to make the Software available for broadest use, with as few -restrictions as possible permission is hereby granted, free of charge, to -any person obtaining a copy of this Software to deal in the Software -under the copyrights without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -The name and trademarks of copyright holder(s) may NOT be used in -advertising or publicity pertaining to the Software or any derivatives -without specific, written prior permission. diff --git a/LICENSE b/LICENSE new file mode 120000 index 0000000..9060ce8 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +debian/copyright \ No newline at end of file diff --git a/debian/copyright b/debian/copyright index e69de29..820ae85 100644 --- a/debian/copyright +++ b/debian/copyright @@ -0,0 +1,29 @@ +Mininet 2.0.0d1 License + +Copyright (c) 2009-2012 Bob Lantz and Brandon Heller + +We are making Mininet available for public use and benefit with the +expectation that others will use, modify and enhance the Software and +contribute those enhancements back to the community. However, since we +would like to make the Software available for broadest use, with as few +restrictions as possible permission is hereby granted, free of charge, to +any person obtaining a copy of this Software to deal in the Software +under the copyrights without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +The name and trademarks of copyright holder(s) may NOT be used in +advertising or publicity pertaining to the Software or any derivatives +without specific, written prior permission. From 0809105bed69dc367f34f3a57db9223cc72b377b Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 1 Jul 2012 23:22:44 -0700 Subject: [PATCH 159/250] Fixing some lintian problems. --- debian/control | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/debian/control b/debian/control index 649c234..0d7fc19 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Priority: extra Maintainer: Ubuntu Developers Standards-Version: 3.9.3 Build-Depends: - debhelper (>= 7), + debhelper (>= 7.0.50~), python-dev, python-pkg-resources, python-setuptools @@ -15,11 +15,12 @@ Depends: openvswitch-switch, openvswitch-controller, telnet, + python-networkx, ${misc:Depends}, ${python:Depends}, ${shlibs:Depends} Description: Process-based network emulator - Mininet is a network emulator which uses lightweight - virtualization to create virtual networks for rapid - prototyping of Software-Defined Network (SDN) designs - using OpenFlow. http://openflow.org/mininet + Mininet is a network emulator which uses lightweight + virtualization to create virtual networks for rapid + prototyping of Software-Defined Network (SDN) designs + using OpenFlow. http://openflow.org/mininet From f2e7884ade8bf93504b1992d7d1e5a80756024f6 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sun, 1 Jul 2012 23:23:56 -0700 Subject: [PATCH 160/250] Add support for generating man page from mn --help. --- Makefile | 11 +++++++++-- bin/mn | 10 +++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 681495c..6ffde7e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ MININET = mininet/*.py TEST = mininet/test/*.py EXAMPLES = examples/*.py -BIN = bin/mn +MN = bin/mn +BIN = $(MN) PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN) MNEXEC = mnexec P8IGN = E251,E201,E302,E202 @@ -34,6 +35,12 @@ develop: $(MNEXEC) install $(MNEXEC) /usr/local/bin/ python setup.py develop -doc: +man: mn.1 + +mn.1: $(MN) + help2man -N -n "create a Mininet network." --no-discard-stderr $(MN) \ + > mn.1 + +doc: man doxygen doxygen.cfg diff --git a/bin/mn b/bin/mn index f6b9e78..c473a77 100755 --- a/bin/mn +++ b/bin/mn @@ -134,7 +134,11 @@ class MininetRunner( object ): else: raise Exception( 'Custom file name not found' ) - opts = OptionParser() + 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." ) + + opts = OptionParser( description=desc ) addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' ) addDictOption( opts, HOSTS, HOSTDEF, 'host' ) addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' ) @@ -175,6 +179,10 @@ class MininetRunner( object ): opts.add_option( '--pin', action='store_true', default=False, help="pin hosts to CPU cores " "(requires --host cfs or --host rt)" ) + def fakeversion( *args ): + "Fake version for help2man" + print "mn (development version)" + opts.add_option( '--version', action='callback', callback=fakeversion ) self.options, self.args = opts.parse_args() From 7fe847967d409a377da1bb75abd32fd021c813b0 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Jul 2012 22:08:25 -0700 Subject: [PATCH 161/250] clean up "make man" slightly --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6ffde7e..33758e1 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ man: mn.1 mn.1: $(MN) help2man -N -n "create a Mininet network." --no-discard-stderr $(MN) \ - > mn.1 + -o $@ doc: man doxygen doxygen.cfg From 39128f8cf87b3ccc4bf3256f5d73d0817a4be10b Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Jul 2012 22:08:33 -0700 Subject: [PATCH 162/250] Add VERSION string. --- bin/mn | 12 +++++++----- mininet/net.py | 3 +++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/bin/mn b/bin/mn index c473a77..548a452 100755 --- a/bin/mn +++ b/bin/mn @@ -19,7 +19,7 @@ import time from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info, warn -from mininet.net import Mininet, MininetWithControlNet +from mininet.net import Mininet, MininetWithControlNet, VERSION from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, NOX, RemoteController, UserSwitch, OVSKernelSwitch, OVSLegacyKernelSwitch ) @@ -87,6 +87,11 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ): help = helpStr ) +def version( *_args ): + "Print Mininet version and exit" + print "mn (Mininet %s)" % VERSION + exit() + class MininetRunner( object ): "Build, setup, and run Mininet." @@ -179,10 +184,7 @@ class MininetRunner( object ): opts.add_option( '--pin', action='store_true', default=False, help="pin hosts to CPU cores " "(requires --host cfs or --host rt)" ) - def fakeversion( *args ): - "Fake version for help2man" - print "mn (development version)" - opts.add_option( '--version', action='callback', callback=fakeversion ) + opts.add_option( '--version', action='callback', callback=version ) self.options, self.args = opts.parse_args() diff --git a/mininet/net.py b/mininet/net.py index 0774cd6..eb14411 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -100,6 +100,9 @@ from mininet.util import quietRun, fixLimits, numCores 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.0.0d1" + class Mininet( object ): "Network emulation with hosts spawned in network namespaces." From 78b2f585aea53a2b9852c9d12ebeac52c65f7412 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Jul 2012 22:50:53 -0700 Subject: [PATCH 163/250] Fixed support for adding man page to debian package - to pass lintian! --- Makefile | 20 +++++++++++++------- debian/mininet.manpages | 1 + debian/rules | 1 + 3 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 debian/mininet.manpages diff --git a/Makefile b/Makefile index 33758e1..443eb94 100644 --- a/Makefile +++ b/Makefile @@ -5,12 +5,15 @@ MN = bin/mn BIN = $(MN) PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN) MNEXEC = mnexec +MANPAGE = mn.1 P8IGN = E251,E201,E302,E202 +BINDIR = /usr/bin +MANDIR = /usr/share/man/man1 all: codecheck test clean: - rm -rf build dist *.egg-info *.pyc $(MNEXEC) + rm -rf build dist *.egg-info *.pyc $(MNEXEC) $(MANPAGE) codecheck: $(PYSRC) -echo "Running code check" @@ -27,17 +30,20 @@ test: $(MININET) $(TEST) -echo "Running tests" mininet/test/test_nets.py -install: $(MNEXEC) - install $(MNEXEC) /usr/local/bin/ +install: $(MNEXEC) $(MANPAGE) + install $(MNEXEC) $(BINDIR) + install $(MANPAGE) $(MANDIR) python setup.py install -develop: $(MNEXEC) - install $(MNEXEC) /usr/local/bin/ +develop: $(MNEXEC) $(MANPAGE) + # Perhaps we should link these as well + install $(MNEXEC) $(BINDIR) + install $(MANPAGE) $(MANDIR) python setup.py develop -man: mn.1 +man: $(MANPAGE) -mn.1: $(MN) +$(MANPAGE): $(MN) help2man -N -n "create a Mininet network." --no-discard-stderr $(MN) \ -o $@ diff --git a/debian/mininet.manpages b/debian/mininet.manpages new file mode 100644 index 0000000..f7e585b --- /dev/null +++ b/debian/mininet.manpages @@ -0,0 +1 @@ +*.1 diff --git a/debian/rules b/debian/rules index b587d32..f1153d0 100755 --- a/debian/rules +++ b/debian/rules @@ -4,5 +4,6 @@ dh $@ --buildsystem=python_distutils --with=python2 override_dh_auto_build: + make man make mnexec && cp mnexec bin/ dh_auto_build From ccc0b1a1cfce3e0f4dca6ae9975e3a6b7407eff9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Jul 2012 23:09:37 -0700 Subject: [PATCH 164/250] Fixed debian/copyright --- LICENSE | 30 +++++++++++++++++++++++++++++- debian/copyright | 35 +++++++---------------------------- 2 files changed, 36 insertions(+), 29 deletions(-) mode change 120000 => 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 120000 index 9060ce8..0000000 --- a/LICENSE +++ /dev/null @@ -1 +0,0 @@ -debian/copyright \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..820ae85 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Mininet 2.0.0d1 License + +Copyright (c) 2009-2012 Bob Lantz and Brandon Heller + +We are making Mininet available for public use and benefit with the +expectation that others will use, modify and enhance the Software and +contribute those enhancements back to the community. However, since we +would like to make the Software available for broadest use, with as few +restrictions as possible permission is hereby granted, free of charge, to +any person obtaining a copy of this Software to deal in the Software +under the copyrights without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +The name and trademarks of copyright holder(s) may NOT be used in +advertising or publicity pertaining to the Software or any derivatives +without specific, written prior permission. diff --git a/debian/copyright b/debian/copyright index 820ae85..4f0c842 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,29 +1,8 @@ -Mininet 2.0.0d1 License +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: mininet +Source: https://github.com/mininet/mininet -Copyright (c) 2009-2012 Bob Lantz and Brandon Heller - -We are making Mininet available for public use and benefit with the -expectation that others will use, modify and enhance the Software and -contribute those enhancements back to the community. However, since we -would like to make the Software available for broadest use, with as few -restrictions as possible permission is hereby granted, free of charge, to -any person obtaining a copy of this Software to deal in the Software -under the copyrights without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -The name and trademarks of copyright holder(s) may NOT be used in -advertising or publicity pertaining to the Software or any derivatives -without specific, written prior permission. +Files: * +Copyright: 2009-2012 Bob Lantz + 2009-2012 Brandon Heller +License: BSD-3-Clause From 9c4d047462334cc9890fc918388a1046484b3ca1 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Jul 2012 23:19:08 -0700 Subject: [PATCH 165/250] Ugh, it looks like lintian wants a duplication of the license in debian/copyright.... --- debian/copyright | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/debian/copyright b/debian/copyright index 4f0c842..6288d00 100644 --- a/debian/copyright +++ b/debian/copyright @@ -6,3 +6,33 @@ Files: * Copyright: 2009-2012 Bob Lantz 2009-2012 Brandon Heller License: BSD-3-Clause + +LIcense: BSD-3-Clause + +Copyright (c) 2009-2012 Bob Lantz and Brandon Heller + +We are making Mininet available for public use and benefit with the +expectation that others will use, modify and enhance the Software and +contribute those enhancements back to the community. However, since we +would like to make the Software available for broadest use, with as few +restrictions as possible permission is hereby granted, free of charge, to +any person obtaining a copy of this Software to deal in the Software +under the copyrights without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +The name and trademarks of copyright holder(s) may NOT be used in +advertising or publicity pertaining to the Software or any derivatives +without specific, written prior permission. From b43a67edbdbabf114d6d4a09e55d659b51c44717 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 3 Jul 2012 23:53:47 -0700 Subject: [PATCH 166/250] Pass lintian. This is still annoyingly redundant. ;-( y --- debian/copyright | 60 +++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/debian/copyright b/debian/copyright index 6288d00..7fa88fa 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,38 +1,36 @@ Format: http://dep.debian.net/deps/dep5 Upstream-Name: mininet -Source: https://github.com/mininet/mininet +Source: https://github.com/mininet/mininet/tree/devel/ppa Files: * Copyright: 2009-2012 Bob Lantz 2009-2012 Brandon Heller License: BSD-3-Clause - -LIcense: BSD-3-Clause - -Copyright (c) 2009-2012 Bob Lantz and Brandon Heller - -We are making Mininet available for public use and benefit with the -expectation that others will use, modify and enhance the Software and -contribute those enhancements back to the community. However, since we -would like to make the Software available for broadest use, with as few -restrictions as possible permission is hereby granted, free of charge, to -any person obtaining a copy of this Software to deal in the Software -under the copyrights without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -The name and trademarks of copyright holder(s) may NOT be used in -advertising or publicity pertaining to the Software or any derivatives -without specific, written prior permission. + Mininet 2.0.0d1 License + . + Copyright (c) 2009-2012 Bob Lantz and Brandon Heller + . + We are making Mininet available for public use and benefit with the + expectation that others will use, modify and enhance the Software and + contribute those enhancements back to the community. However, since we + would like to make the Software available for broadest use, with as few + restrictions as possible permission is hereby granted, free of charge, to + any person obtaining a copy of this Software to deal in the Software + under the copyrights without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + . + The name and trademarks of copyright holder(s) may NOT be used in + advertising or publicity pertaining to the Software or any derivatives + without specific, written prior permission. From 8aa7e05d83ee5b8d9d613381ac339d77d7250356 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 4 Jul 2012 00:23:06 -0700 Subject: [PATCH 167/250] Added missing help2man to build deps. --- debian/control | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 0d7fc19..c235cb0 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,8 @@ Build-Depends: debhelper (>= 7.0.50~), python-dev, python-pkg-resources, - python-setuptools + python-setuptools, + help2man Package: mininet Architecture: any From 0ab282400e6be79b9b752385f73a9c614dbd0f4d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Wed, 4 Jul 2012 10:46:25 -0700 Subject: [PATCH 168/250] update maintainer for ppa submission to work --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index c235cb0..f2bef89 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,7 @@ Source: mininet Section: net Priority: extra -Maintainer: Ubuntu Developers +Maintainer: Bob Lantz Standards-Version: 3.9.3 Build-Depends: debhelper (>= 7.0.50~), From 320df7fe289315ae306f1b7ad2c54de0071ec8fd Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 6 Jul 2012 16:06:19 -0700 Subject: [PATCH 169/250] Merging in James Page's packaging tweaks for quantal. --- debian/changelog | 4 +-- debian/control | 30 ++++++++++++---------- debian/copyright | 61 +++++++++++++++++++++----------------------- debian/source/format | 2 +- 4 files changed, 48 insertions(+), 49 deletions(-) diff --git a/debian/changelog b/debian/changelog index a85ec01..9ac1649 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,6 @@ -mininet (2.0.0d1) precise; urgency=low +mininet (2.0.0d1-0ubuntu1) quantal; urgency=low - * Initial release + * Initial release. -- Bob Lantz Sun, 01 Jul 2012 23:19:54 +0000 diff --git a/debian/control b/debian/control index f2bef89..45597f3 100644 --- a/debian/control +++ b/debian/control @@ -1,27 +1,29 @@ Source: mininet Section: net Priority: extra -Maintainer: Bob Lantz +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Bob Lantz Standards-Version: 3.9.3 Build-Depends: - debhelper (>= 7.0.50~), - python-dev, - python-pkg-resources, - python-setuptools, - help2man + debhelper (>= 7.0.50~), + help2man, + python-dev, + python-pkg-resources, + python-setuptools +Homepage: http://openflow.org/mininet Package: mininet Architecture: any Depends: - openvswitch-switch, - openvswitch-controller, - telnet, - python-networkx, - ${misc:Depends}, - ${python:Depends}, - ${shlibs:Depends} + openvswitch-controller, + openvswitch-switch, + python-networkx, + telnet, + ${misc:Depends}, + ${python:Depends}, + ${shlibs:Depends} Description: Process-based network emulator Mininet is a network emulator which uses lightweight virtualization to create virtual networks for rapid prototyping of Software-Defined Network (SDN) designs - using OpenFlow. http://openflow.org/mininet + using OpenFlow. diff --git a/debian/copyright b/debian/copyright index 7fa88fa..b571896 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,36 +1,33 @@ -Format: http://dep.debian.net/deps/dep5 +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 Upstream-Name: mininet -Source: https://github.com/mininet/mininet/tree/devel/ppa +Source: https://github.com/mininet/mininet Files: * -Copyright: 2009-2012 Bob Lantz +Copyright: 2009-2012 Bob Lantz, 2009-2012 Brandon Heller -License: BSD-3-Clause - Mininet 2.0.0d1 License - . - Copyright (c) 2009-2012 Bob Lantz and Brandon Heller - . - We are making Mininet available for public use and benefit with the - expectation that others will use, modify and enhance the Software and - contribute those enhancements back to the community. However, since we - would like to make the Software available for broadest use, with as few - restrictions as possible permission is hereby granted, free of charge, to - any person obtaining a copy of this Software to deal in the Software - under the copyrights without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - . - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - . - The name and trademarks of copyright holder(s) may NOT be used in - advertising or publicity pertaining to the Software or any derivatives - without specific, written prior permission. +License: + We are making Mininet available for public use and benefit with the + expectation that others will use, modify and enhance the Software and + contribute those enhancements back to the community. However, since we + would like to make the Software available for broadest use, with as few + restrictions as possible permission is hereby granted, free of charge, to + any person obtaining a copy of this Software to deal in the Software + under the copyrights without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + . + The name and trademarks of copyright holder(s) may NOT be used in + advertising or publicity pertaining to the Software or any derivatives + without specific, written prior permission. diff --git a/debian/source/format b/debian/source/format index 89ae9db..163aaf8 100644 --- a/debian/source/format +++ b/debian/source/format @@ -1 +1 @@ -3.0 (native) +3.0 (quilt) From d54cde46e00040e99cde1fef4cc88e409837d61d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 6 Jul 2012 19:54:42 -0700 Subject: [PATCH 170/250] Add PYTHONPATH=. to allow "make man" to work if Mininet is not installed. --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 443eb94..d41fd8c 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,8 @@ develop: $(MNEXEC) $(MANPAGE) man: $(MANPAGE) $(MANPAGE): $(MN) - help2man -N -n "create a Mininet network." --no-discard-stderr $(MN) \ + PYTHONPATH=. help2man -N -n "create a Mininet network." \ + --no-discard-stderr $(MN) \ -o $@ doc: man From 9a518b1eee6a05c3356a8abc2603ab20f17d49a3 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 6 Jul 2012 22:28:27 -0700 Subject: [PATCH 171/250] Add missing packaging files from launchpad. --- debian/docs | 1 + debian/examples | 1 + debian/install | 1 + debian/manpages | 1 + debian/watch | 3 +++ 5 files changed, 7 insertions(+) create mode 100644 debian/docs create mode 100644 debian/examples create mode 100644 debian/install create mode 100644 debian/manpages create mode 100644 debian/watch diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..e845566 --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ +README diff --git a/debian/examples b/debian/examples new file mode 100644 index 0000000..e39721e --- /dev/null +++ b/debian/examples @@ -0,0 +1 @@ +examples/* diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..790f822 --- /dev/null +++ b/debian/install @@ -0,0 +1 @@ +mnexec /usr/bin diff --git a/debian/manpages b/debian/manpages new file mode 100644 index 0000000..f7e585b --- /dev/null +++ b/debian/manpages @@ -0,0 +1 @@ +*.1 diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..4614367 --- /dev/null +++ b/debian/watch @@ -0,0 +1,3 @@ +version=3 +opts=dversionmangle=s/\+dfsg// \ +http://githubredir.debian.net/github/mininet/mininet/ mininet-(.*).tar.gz From 89a6dea7f9a54c2a84ab4c874bbb822a661c41ee Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 6 Jul 2012 22:43:11 -0700 Subject: [PATCH 172/250] Remove unnecessary copy to bin/ --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index f1153d0..f000338 100755 --- a/debian/rules +++ b/debian/rules @@ -5,5 +5,5 @@ override_dh_auto_build: make man - make mnexec && cp mnexec bin/ + make mnexec dh_auto_build From d85a58feebc279b6c4a2d8d5e3ca379574bc4089 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 6 Jul 2012 23:18:24 -0700 Subject: [PATCH 173/250] Autogenerate man page for mnexec. --- Makefile | 18 ++++++++++++------ bin/mn | 2 +- mnexec.c | 34 +++++++++++++++++++++++----------- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index d41fd8c..ac83ca5 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ MN = bin/mn BIN = $(MN) PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN) MNEXEC = mnexec -MANPAGE = mn.1 +MANPAGES = mn.1 mnexec.1 P8IGN = E251,E201,E302,E202 BINDIR = /usr/bin MANDIR = /usr/share/man/man1 @@ -13,7 +13,7 @@ MANDIR = /usr/share/man/man1 all: codecheck test clean: - rm -rf build dist *.egg-info *.pyc $(MNEXEC) $(MANPAGE) + rm -rf build dist *.egg-info *.pyc $(MNEXEC) $(MANPAGES) codecheck: $(PYSRC) -echo "Running code check" @@ -41,12 +41,18 @@ develop: $(MNEXEC) $(MANPAGE) install $(MANPAGE) $(MANDIR) python setup.py develop -man: $(MANPAGE) +man: $(MANPAGES) -$(MANPAGE): $(MN) +mn.1: $(MN) PYTHONPATH=. help2man -N -n "create a Mininet network." \ - --no-discard-stderr $(MN) \ - -o $@ + --no-discard-stderr $< -o $@ + +mnexec: mnexec.c $(MN) mininet/net.py + cc -DVERSION=\"`$(MN) --version`\" $< -o $@ + +mnexec.1: mnexec + help2man -N -n "execution utility for Mininet." \ + -h "-h" -v "-v" --no-discard-stderr ./$< -o $@ doc: man doxygen doxygen.cfg diff --git a/bin/mn b/bin/mn index 548a452..a8b12cd 100755 --- a/bin/mn +++ b/bin/mn @@ -89,7 +89,7 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ): def version( *_args ): "Print Mininet version and exit" - print "mn (Mininet %s)" % VERSION + print "%s" % VERSION exit() class MininetRunner( object ): diff --git a/mnexec.c b/mnexec.c index ebd479c..42a9cf6 100644 --- a/mnexec.c +++ b/mnexec.c @@ -23,17 +23,23 @@ #include #include +#if !defined(VERSION) +#define VERSION "(devel)" +#endif + void usage(char *name) { - printf("Execution utility for Mininet.\n" - "usage: %s [-cdnp] [-a pid] [-g group] [-r rtprio] cmd args...\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" - "-p: print ^A + pid\n" - "-a pid: attach to pid's network namespace\n" - "-g group: add to cgroup\n" - "-r rtprio: run with SCHED_RR (usually requires -g)\n", + printf("Execution utility for Mininet\n\n" + "Usage: %s [-cdnp] [-a pid] [-g group] [-r rtprio] cmd args...\n\n" + "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" + " -p: print ^A + pid\n" + " -a pid: attach to pid's network namespace\n" + " -g group: add to cgroup\n" + " -r rtprio: run with SCHED_RR (usually requires -g)\n" + " -v: print version\n", name); } @@ -92,7 +98,7 @@ int main(int argc, char *argv[]) int nsid; int pid; static struct sched_param sp; - while ((c = getopt(argc, argv, "+cdnpa:g:r:")) != -1) + while ((c = getopt(argc, argv, "+cdnpa:g:r:vh")) != -1) switch(c) { case 'c': /* close file descriptors except stdin/out/error */ @@ -152,9 +158,15 @@ int main(int argc, char *argv[]) return 1; } break; + case 'v': + printf("%s\n", VERSION); + exit(0); + case 'h': + usage(argv[0]); + exit(0); default: usage(argv[0]); - break; + exit(1); } if (optind < argc) { From 28c2cdc2c438f308bbd1b2f3f2cc5c9d89b49f11 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 6 Jul 2012 23:30:14 -0700 Subject: [PATCH 174/250] Workaround for openvswitch_mod rename and pass code check. --- bin/mn | 4 ++-- mininet/moduledeps.py | 2 +- mininet/node.py | 8 +++++--- mininet/util.py | 4 +--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bin/mn b/bin/mn index a8b12cd..77f65b8 100755 --- a/bin/mn +++ b/bin/mn @@ -18,7 +18,7 @@ import time from mininet.clean import cleanup from mininet.cli import CLI -from mininet.log import lg, LEVELS, info, warn +from mininet.log import lg, LEVELS, info from mininet.net import Mininet, MininetWithControlNet, VERSION from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, NOX, RemoteController, UserSwitch, OVSKernelSwitch, @@ -26,7 +26,7 @@ from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo -from mininet.util import makeNumeric, custom, customConstructor, splitArgs +from mininet.util import custom, customConstructor from mininet.util import buildTopo diff --git a/mininet/moduledeps.py b/mininet/moduledeps.py index 15b575d..584d6c7 100644 --- a/mininet/moduledeps.py +++ b/mininet/moduledeps.py @@ -19,7 +19,7 @@ def modprobe( mod ): return quietRun( [ 'modprobe', mod ] ) OF_KMOD = 'ofdatapath' -OVS_KMOD = 'openvswitch_mod' +OVS_KMOD = 'openvswitch_mod' # Renamed 'openvswitch' in OVS 1.7+/Linux 3.5+ TUN = 'tun' def moduleDeps( subtract=None, add=None ): diff --git a/mininet/node.py b/mininet/node.py index 8e4786d..4ed4c92 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -896,7 +896,9 @@ class OVSSwitch( Switch ): "Make sure Open vSwitch is installed and working" pathCheck( 'ovs-vsctl', moduleName='Open vSwitch (openvswitch.org)') - moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) + # This should no longer be needed, and it breaks + # with OVS 1.7 which has renamed the kernel module: + # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' ) if exitcode: error( out + err + @@ -985,10 +987,10 @@ class Controller( Node ): if 'Unable' not in listening: servers = self.cmd( 'netstat -atp' ).split( '\n' ) pstr = ':%d ' % self.port - info = servers[ 0:1 ] + [ s for s in servers if pstr in s ] + clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ] raise Exception( "Please shut down the controller which is" " running on port %d:\n" % self.port + - '\n'.join( info ) ) + '\n'.join( clist ) ) def start( self ): """Start on controller. diff --git a/mininet/util.py b/mininet/util.py index 19cb552..bb645fe 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -1,6 +1,6 @@ "Utility functions for Mininet." -from mininet.log import output, info, error +from mininet.log import output, info, error, warn from time import sleep from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE @@ -419,7 +419,6 @@ def splitArgs( argstr ): def customConstructor( constructors, argStr ): """Return custom constructor based on argStr - The args and key/val pairs in argsStr will be automatically applied when the generated constructor is later used. """ @@ -444,7 +443,6 @@ def customConstructor( constructors, argStr ): def buildTopo( topos, topoStr ): """Create topology from string with format (object, arg1, arg2,...). - input topos is a dict of topo names to constructors, possibly w/args. """ topo, args, kwargs = splitArgs( topoStr ) From 55179737f9801bf8112a90ac90d276a36b14108f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 6 Jul 2012 23:44:27 -0700 Subject: [PATCH 175/250] Change version to 2.0.0d2 - there has to be a better way. --- INSTALL | 2 +- LICENSE | 2 +- README | 2 +- debian/changelog | 2 +- mininet/net.py | 2 +- setup.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/INSTALL b/INSTALL index 2b8738a..22e10db 100644 --- a/INSTALL +++ b/INSTALL @@ -1,7 +1,7 @@ Mininet Installation/Configuration Notes -Mininet 2.0.0d1 +Mininet 2.0.0d2 --- diff --git a/LICENSE b/LICENSE index 820ae85..eb4c3b1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Mininet 2.0.0d1 License +Mininet 2.0.0d2 License Copyright (c) 2009-2012 Bob Lantz and Brandon Heller diff --git a/README b/README index f74d4d0..2ceccc3 100644 --- a/README +++ b/README @@ -3,7 +3,7 @@ or How to Squeeze an OpenFlow Network onto your Laptop -Mininet 2.0.0d1 +Mininet 2.0.0d2 --- Welcome to Mininet! diff --git a/debian/changelog b/debian/changelog index 9ac1649..af2b1d9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -mininet (2.0.0d1-0ubuntu1) quantal; urgency=low +mininet (2.0.0d2-0ubuntu1) quantal; urgency=low * Initial release. diff --git a/mininet/net.py b/mininet/net.py index eb14411..cd583cc 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -101,7 +101,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.0.0d1" +VERSION = "2.0.0d2" class Mininet( object ): "Network emulation with hosts spawned in network namespaces." diff --git a/setup.py b/setup.py index 8525f83..d9d9e28 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ modname = distname = 'mininet' setup( name=distname, - version='2.0.0d1', + version='2.0.0d2', description='Process-based OpenFlow emulator', author='Bob Lantz', author_email='rlantz@cs.stanford.edu', From 232acc8261f4109d3e46d32f1d6cdef6fa87d4f6 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 7 Jul 2012 19:07:20 -0700 Subject: [PATCH 176/250] Track tagged versions on github directly. --- debian/watch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/watch b/debian/watch index 4614367..b6f6a64 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,3 @@ version=3 opts=dversionmangle=s/\+dfsg// \ -http://githubredir.debian.net/github/mininet/mininet/ mininet-(.*).tar.gz +https://github.com/mininet/mininet/tags .*/tarball/(\d[\d\.abd]+) From 1e9106badb3539a01b3979f6b210fccb407edba7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 7 Jul 2012 22:23:53 -0700 Subject: [PATCH 177/250] Add option to mangle github tarball filename. --- debian/watch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/watch b/debian/watch index b6f6a64..9d78e15 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,3 @@ version=3 -opts=dversionmangle=s/\+dfsg// \ +opts=filenamemangle=s/.*tarball\/(.*)/$1\.tar\.gz/ \ https://github.com/mininet/mininet/tags .*/tarball/(\d[\d\.abd]+) From cd580debb6d5da24f152efd32208ded22cc40267 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 7 Jul 2012 22:29:46 -0700 Subject: [PATCH 178/250] Removed redundant debian/mininet.manpages --- debian/mininet.manpages | 1 - 1 file changed, 1 deletion(-) delete mode 100644 debian/mininet.manpages diff --git a/debian/mininet.manpages b/debian/mininet.manpages deleted file mode 100644 index f7e585b..0000000 --- a/debian/mininet.manpages +++ /dev/null @@ -1 +0,0 @@ -*.1 From ce823507cd1617bdd222522a1f413bb8958ab197 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 31 Jul 2012 17:22:13 -0700 Subject: [PATCH 179/250] Fix man page install. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ac83ca5..c667a03 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ test: $(MININET) $(TEST) -echo "Running tests" mininet/test/test_nets.py -install: $(MNEXEC) $(MANPAGE) +install: $(MNEXEC) $(MANPAGES) install $(MNEXEC) $(BINDIR) install $(MANPAGE) $(MANDIR) python setup.py install @@ -38,7 +38,7 @@ install: $(MNEXEC) $(MANPAGE) develop: $(MNEXEC) $(MANPAGE) # Perhaps we should link these as well install $(MNEXEC) $(BINDIR) - install $(MANPAGE) $(MANDIR) + install $(MANPAGES) $(MANDIR) python setup.py develop man: $(MANPAGES) From fa24f22d4df53425abc4428944ed63b8becfa454 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 31 Jul 2012 17:23:08 -0700 Subject: [PATCH 180/250] Re-enable slicing in UserSwitch since it's fixed with newer kernels. --- mininet/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 4ed4c92..b441d96 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -817,7 +817,6 @@ class UserSwitch( Switch ): intfs = [ str( i ) for i in self.intfList() if not i.IP() ] self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' punix:/tmp/' + self.name + ' -d ' + self.dpid + - ' --no-slicing ' + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) self.cmd( 'ofprotocol unix:/tmp/' + self.name + ' tcp:%s:%d' % ( controller.IP(), controller.port ) + From 9b112384679c0953b27c1a9cd8ca38c1422a659d Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 12 Jul 2012 10:07:31 +0100 Subject: [PATCH 181/250] Observe build environment flags and use PYTHONPATH when generating version number --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c667a03..83de871 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ mn.1: $(MN) --no-discard-stderr $< -o $@ mnexec: mnexec.c $(MN) mininet/net.py - cc -DVERSION=\"`$(MN) --version`\" $< -o $@ + cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(MN) --version`\" $< -o $@ mnexec.1: mnexec help2man -N -n "execution utility for Mininet." \ From 2b35a2caeb3055fdaec9cc5a7519445b7da84d51 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 12 Jul 2012 10:08:37 +0100 Subject: [PATCH 182/250] Override remote controller check to ensure that remote controller is contactable --- mininet/node.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mininet/node.py b/mininet/node.py index b441d96..d201dff 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1073,3 +1073,12 @@ class RemoteController( Controller ): def stop( self ): "Overridden to do nothing." return + + def checkListening( self ): + "Ensure that the remote controller is accessible" + listening = self.cmd( "echo A | telnet -e A %s %d" % + ( self.ip, self.port ) ) + if 'Unable' in listening: + raise Exception( "Unable to contact the remote controller" + " at %s:%d\n" % (self.ip, self.port)) + From 54c51c0299a6be4a15ccb84225b8df1f1c2b4be7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 6 Aug 2012 11:41:00 -0700 Subject: [PATCH 183/250] Fix whitespace and change no controller exception to warning. --- mininet/node.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index d201dff..0a20d29 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1075,10 +1075,9 @@ class RemoteController( Controller ): return def checkListening( self ): - "Ensure that the remote controller is accessible" + "Warn if remote controller is not accessible" listening = self.cmd( "echo A | telnet -e A %s %d" % ( self.ip, self.port ) ) if 'Unable' in listening: - raise Exception( "Unable to contact the remote controller" - " at %s:%d\n" % (self.ip, self.port)) - + warn( "Unable to contact the remote controller" + " at %s:%d\n" % ( self.ip, self.port ) ) From e8238d185d271ee301885b6ce8354ccb5299393a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 10 Aug 2012 12:45:51 -0700 Subject: [PATCH 184/250] Use Mininet version number from mininet.net Contributing toward issue #46. --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d9d9e28..3f9f0eb 100644 --- a/setup.py +++ b/setup.py @@ -5,13 +5,18 @@ from setuptools import setup, find_packages from os.path import join +# Get version number from source tree +import sys +sys.path.append( '.' ) +from mininet.net import VERSION + scripts = [ join( 'bin', filename ) for filename in [ 'mn' ] ] modname = distname = 'mininet' setup( name=distname, - version='2.0.0d2', + version=VERSION, description='Process-based OpenFlow emulator', author='Bob Lantz', author_email='rlantz@cs.stanford.edu', From 01e0758e5d5ffec6ac7f508f2d3e0a1c1dc682f9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 16 Aug 2012 16:12:55 -0700 Subject: [PATCH 185/250] Add 'type mn -h for details' to usage message. --- bin/mn | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/mn b/bin/mn index 77f65b8..91cac20 100755 --- a/bin/mn +++ b/bin/mn @@ -143,7 +143,10 @@ class MininetRunner( object ): "command line. It can create parametrized topologies,\n" "invoke the Mininet CLI, and run tests." ) - opts = OptionParser( description=desc ) + usage = ( '%prog [options]\n' + '(type %prog -h for details)' ) + + opts = OptionParser( description=desc, usage=usage ) addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' ) addDictOption( opts, HOSTS, HOSTDEF, 'host' ) addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' ) From ce15c4f67d6be8ef6d05e241fd0912000dfee7f7 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 16 Aug 2012 18:48:41 -0700 Subject: [PATCH 186/250] rename Topo() methods for consistency: add_node() -> addNode() --- custom/topo-2sw-2host.py | 8 +++---- examples/linearbandwidth.py | 10 ++++---- examples/simpleperf.py | 6 ++--- mininet/topo.py | 48 ++++++++++++++++++------------------- mininet/topolib.py | 6 ++--- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/custom/topo-2sw-2host.py b/custom/topo-2sw-2host.py index b0fff06..ee5ec35 100644 --- a/custom/topo-2sw-2host.py +++ b/custom/topo-2sw-2host.py @@ -28,10 +28,10 @@ class MyTopo( Topo ): rightHost = 4 # Add nodes - self.add_node( leftSwitch, Node( is_switch=True ) ) - self.add_node( rightSwitch, Node( is_switch=True ) ) - self.add_node( leftHost, Node( is_switch=False ) ) - self.add_node( rightHost, Node( is_switch=False ) ) + self.addNode( leftSwitch, Node( isSwitch=True ) ) + self.addNode( rightSwitch, Node( isSwitch=True ) ) + self.addNode( leftHost, Node( isSwitch=False ) ) + self.addNode( rightHost, Node( isSwitch=False ) ) # Add edges self.add_edge( leftHost, leftSwitch ) diff --git a/examples/linearbandwidth.py b/examples/linearbandwidth.py index 42b3eb9..c361045 100755 --- a/examples/linearbandwidth.py +++ b/examples/linearbandwidth.py @@ -41,22 +41,22 @@ class LinearTestTopo( Topo ): Topo.__init__( self, **params ) # Create switches and hosts - hosts = [ self.add_host( 'h%s' % h ) + hosts = [ self.addHost( 'h%s' % h ) for h in irange( 1, N ) ] - switches = [ self.add_switch( 's%s' % s ) + switches = [ self.addSwitch( 's%s' % s ) for s in irange( 1, N - 1 ) ] # Wire up switches last = None for switch in switches: if last: - self.add_link( last, switch ) + self.addLink( last, switch ) last = switch # Wire up hosts - self.add_link( hosts[ 0 ], switches[ 0 ] ) + self.addLink( hosts[ 0 ], switches[ 0 ] ) for host, switch in zip( hosts[ 1: ], switches ): - self.add_link( host, switch ) + self.addLink( host, switch ) def linearBandwidthTest( lengths ): diff --git a/examples/simpleperf.py b/examples/simpleperf.py index 05cb2ae..76de0bd 100644 --- a/examples/simpleperf.py +++ b/examples/simpleperf.py @@ -15,13 +15,13 @@ class SingleSwitchTopo(Topo): "Single switch connected to n hosts." def __init__(self, n=2, **opts): Topo.__init__(self, **opts) - switch = self.add_switch('s1') + switch = self.addSwitch('s1') for h in range(n): # Each host gets 50%/n of system CPU - host = self.add_host('h%s' % (h + 1), + host = self.addHost('h%s' % (h + 1), cpu=.5 / n) # 10 Mbps, 5ms delay, 10% loss - self.add_link(host, switch, + self.addLink(host, switch, bw=10, delay='5ms', loss=10, use_htb=True) def perfTest(): diff --git a/mininet/topo.py b/mininet/topo.py index 86b5cf7..27df013 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -34,7 +34,7 @@ class Topo(object): self.lopts = {} if lopts is None else lopts self.ports = {} # ports[src][dst] is port on src that connects to dst - def add_node(self, name, **opts): + def addNode(self, name, **opts): """Add Node to graph. name: name opts: node options @@ -43,26 +43,26 @@ class Topo(object): self.node_info[name] = opts return name - def add_host(self, name, **opts): + def addHost(self, name, **opts): """Convenience method: Add host to graph. name: host name opts: host options returns: host name""" if not opts and self.hopts: opts = self.hopts - return self.add_node(name, **opts) + return self.addNode(name, **opts) - def add_switch(self, name, **opts): + def addSwitch(self, name, **opts): """Convenience method: Add switch to graph. name: switch name opts: switch options returns: switch name""" if not opts and self.sopts: opts = self.sopts - result = self.add_node(name, is_switch=True, **opts) + result = self.addNode(name, isSwitch=True, **opts) return result - def add_link(self, node1, node2, port1=None, port2=None, + def addLink(self, node1, node2, port1=None, port2=None, **opts): """node1, node2: nodes to link together port1, port2: ports (optional) @@ -70,13 +70,13 @@ class Topo(object): returns: link info key""" if not opts and self.lopts: opts = self.lopts - self.add_port(node1, node2, port1, port2) + self.addPort(node1, node2, port1, port2) key = tuple(self.sorted([node1, node2])) self.link_info[key] = opts self.g.add_edge(*key) return key - def add_port(self, src, dst, sport=None, dport=None): + def addPort(self, src, dst, sport=None, dport=None): '''Generate port mapping for new edge. @param src source switch name @param dst destination switch name @@ -84,8 +84,8 @@ class Topo(object): self.ports.setdefault(src, {}) self.ports.setdefault(dst, {}) # New port: number of outlinks + base - src_base = 1 if self.is_switch(src) else 0 - dst_base = 1 if self.is_switch(dst) else 0 + src_base = 1 if self.isSwitch(src) else 0 + dst_base = 1 if self.isSwitch(dst) else 0 if sport is None: sport = len(self.ports[src]) + src_base if dport is None: @@ -100,24 +100,24 @@ class Topo(object): else: return self.g.nodes() - def is_switch(self, n): + def isSwitch(self, n): '''Returns true if node is a switch.''' info = self.node_info[n] - return info and info.get('is_switch', False) + return info and info.get('isSwitch', False) def switches(self, sort=True): '''Return switches. sort: sort switches alphabetically @return dpids list of dpids ''' - return [n for n in self.nodes(sort) if self.is_switch(n)] + return [n for n in self.nodes(sort) if self.isSwitch(n)] def hosts(self, sort=True): '''Return hosts. sort: sort hosts alphabetically @return dpids list of dpids ''' - return [n for n in self.nodes(sort) if not self.is_switch(n)] + return [n for n in self.nodes(sort) if not self.isSwitch(n)] def links(self, sort=True): '''Return links. @@ -180,10 +180,10 @@ class SingleSwitchTopo(Topo): self.k = k - switch = self.add_switch('s1') + switch = self.addSwitch('s1') for h in irange(1, k): - host = self.add_host('h%s' % h) - self.add_link(host, switch) + host = self.addHost('h%s' % h) + self.addLink(host, switch) class SingleSwitchReversedTopo(Topo): @@ -201,10 +201,10 @@ class SingleSwitchReversedTopo(Topo): ''' super(SingleSwitchReversedTopo, self).__init__(**opts) self.k = k - switch = self.add_switch('s1') + switch = self.addSwitch('s1') for h in irange(1, k): - host = self.add_host('h%s' % h) - self.add_link(host, switch, + host = self.addHost('h%s' % h) + self.addLink(host, switch, port1=0, port2=(k - h + 1)) class LinearTopo(Topo): @@ -222,9 +222,9 @@ class LinearTopo(Topo): lastSwitch = None for i in irange(1, k): - host = self.add_host('h%s' % i) - switch = self.add_switch('s%s' % i) - self.add_link( host, switch) + host = self.addHost('h%s' % i) + switch = self.addSwitch('s%s' % i) + self.addLink( host, switch) if lastSwitch: - self.add_link( switch, lastSwitch) + self.addLink( switch, lastSwitch) lastSwitch = switch diff --git a/mininet/topolib.py b/mininet/topolib.py index a8de9d8..63ba36d 100644 --- a/mininet/topolib.py +++ b/mininet/topolib.py @@ -19,13 +19,13 @@ class TreeTopo( Topo ): returns: last node added""" isSwitch = depth > 0 if isSwitch: - node = self.add_switch( 's%s' % self.switchNum ) + node = self.addSwitch( 's%s' % self.switchNum ) self.switchNum += 1 for _ in range( fanout ): child = self.addTree( depth - 1, fanout ) - self.add_link( node, child ) + self.addLink( node, child ) else: - node = self.add_host( 'h%s' % self.hostNum ) + node = self.addHost( 'h%s' % self.hostNum ) self.hostNum += 1 return node From 9d5a21a799f31c7c5b0e20822a90cf15a0095bdf Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 17 Aug 2012 15:25:06 -0700 Subject: [PATCH 187/250] Fix typo MANPAGE->MANPAGES Thanks to Isaku Yamahata. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 83de871..5cba0c3 100644 --- a/Makefile +++ b/Makefile @@ -32,10 +32,10 @@ test: $(MININET) $(TEST) install: $(MNEXEC) $(MANPAGES) install $(MNEXEC) $(BINDIR) - install $(MANPAGE) $(MANDIR) + install $(MANPAGES) $(MANDIR) python setup.py install -develop: $(MNEXEC) $(MANPAGE) +develop: $(MNEXEC) $(MANPAGES) # Perhaps we should link these as well install $(MNEXEC) $(BINDIR) install $(MANPAGES) $(MANDIR) From 2aafefc2fa3cd7ff5f6e912791e3b271d3560066 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 17 Aug 2012 15:26:19 -0700 Subject: [PATCH 188/250] Fix typo in RemoteController.__init__ comment. Thanks to Isaku Yamahata. --- mininet/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/node.py b/mininet/node.py index 0a20d29..80366c4 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1060,7 +1060,7 @@ class RemoteController( Controller ): port=6633, **kwargs): """Init. name: name to give controller - defaultIP: the IP address where the remote controller is + ip: the IP address where the remote controller is listening port: the port where the remote controller is listening""" Controller.__init__( self, name, ip=ip, port=port, From 4f33cad025a7805fc179d21ac0a45fc216677588 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 17 Aug 2012 18:20:22 -0700 Subject: [PATCH 189/250] Ignore more stuff, notably generated docs and man pages. --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 66c0b7c..0e2001d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,12 @@ mnexec *.pyc *~ +*.1 +*.xcodeproj +*.xcworkspace \#*\# mininet.egg-info build/* dist/* - +doc/* +trunk/* From 62499d96df543927ecf3c934a46d15090b78ecbd Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 23 Aug 2012 18:27:48 -0700 Subject: [PATCH 190/250] Adjust README slightly to be markdown-compatible. --- README | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/README b/README index 2ceccc3..c2e6ff9 100644 --- a/README +++ b/README @@ -1,12 +1,12 @@ Mininet: A Simple Virtual Testbed for OpenFlow/SDN or -How to Squeeze an OpenFlow Network onto your Laptop + How to Squeeze an OpenFlow Network onto your Laptop Mininet 2.0.0d2 --- -Welcome to Mininet! +**Welcome to Mininet!** Mininet creates OpenFlow test networks by using process-based virtualization and network namespaces. @@ -22,14 +22,14 @@ handy Python API for creating networks of varying sizes and topologies. In order to run Mininet, you must have: * A Linux kernel compiled with network namespace support - enabled (see INSTALL for additional information.) + enabled (see `INSTALL` for additional information.) * An OpenFlow implementation (either the reference user or kernel space implementations, or Open vSwitch.) Appropriate kernel modules (e.g. tun and ofdatapath for the reference kernel implementation) must be loaded. -* Python, bash, ping, iperf, etc. +* Python, `bash`, `ping`, `iperf`, etc. * Root privileges (required for network device access) @@ -42,7 +42,7 @@ Currently Mininet includes: topologies (Topo subclasses.) For example, a tree network may be created with the command - # mn --topo tree,depth=2,fanout=3 + `# mn --topo tree,depth=2,fanout=3` - Basic tests, including connectivity (ping) and bandwidth (iperf) @@ -50,15 +50,15 @@ Currently Mininet includes: diagnostic commands, as well as the ability to send a command to a node. For example, - mininet> h11 ifconfig -a + `mininet> h11 ifconfig -a` - tells host h11 to run the command 'ifconfig -a' + tells host h11 to run the command `ifconfig -a` - A 'cleanup' command to get rid of junk (interfaces, processes, files in /tmp, etc.) which might be left around by Mininet or Linux. Try this if things stop working! - # mn -c + `# mn -c` - Examples (in the examples/ directory) to help you get started. @@ -68,12 +68,11 @@ However, some preliminary installation notes are included in the INSTALL file. Additionally, much useful information, including a Mininet tutorial, -is available on the Mininet wiki: - -http://openflow.org/mininet +is available on the [Mininet Wiki](http://openflow.org/mininet). Enjoy, and good luck! --- Bob Lantz rlantz@cs.stanford.edu + From ae6475598f9def17764ec9e5e13794ee7ce756a9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 23 Aug 2012 18:28:15 -0700 Subject: [PATCH 191/250] Change README to README.md for github presentation. This isn't the final README text, of course, but it should show up formatted now on github. --- README => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README => README.md (100%) diff --git a/README b/README.md similarity index 100% rename from README rename to README.md From 6eb01d7923d4c2b569d8ad680c420ad4f615c629 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 23 Aug 2012 18:36:50 -0700 Subject: [PATCH 192/250] Minor formatting changes. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c2e6ff9..58d71c0 100644 --- a/README.md +++ b/README.md @@ -35,16 +35,16 @@ In order to run Mininet, you must have: Currently Mininet includes: -- A simple node infrastructure (Host, Switch, Controller classes) for +- A simple node infrastructure (`Host`, `Switch`, `Controller` classes) for creating virtual OpenFlow networks -- A simple network infrastructure (Mininet class) supporting parametrized - topologies (Topo subclasses.) For example, a tree network may be created +- A simple network infrastructure (`Mininet` class) supporting parametrized + topologies (`Topo` subclasses.) For example, a tree network may be created with the command `# mn --topo tree,depth=2,fanout=3` -- Basic tests, including connectivity (ping) and bandwidth (iperf) +- Basic tests, including connectivity (`ping`) and bandwidth (`iperf`) - A command-line interface (CLI class) which provides useful diagnostic commands, as well as the ability to send a command to a From 655194d3e1800197bd9111757cecceeb0a97c475 Mon Sep 17 00:00:00 2001 From: Bob Date: Wed, 29 Aug 2012 15:10:36 -0700 Subject: [PATCH 193/250] Update util/install.sh Update to reflect new NOX classic repo on github and default branch. --- util/install.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/util/install.sh b/util/install.sh index 94893c5..74558fc 100755 --- a/util/install.sh +++ b/util/install.sh @@ -348,9 +348,11 @@ function nox { # Fetch NOX destiny cd ~/ - git clone git://noxrepo.org/nox noxcore + git clone https://github.com/noxrepo/nox-classic.git noxcore cd noxcore - git checkout -b destiny remotes/origin/destiny + if ! git checkout -b destiny remotes/origin/destiny ; then + echo "Did not check out a new destiny branch - assuming current branch is destiny" + fi # Apply patches git checkout -b tutorial-destiny From d4ece25ba6259e920e3887b6b2853d4869983f7a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 11 Sep 2012 06:47:42 -0700 Subject: [PATCH 194/250] Deprecate NOX-classic; "install" POX. Fixes #61 --- util/install.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/util/install.sh b/util/install.sh index 74558fc..6a319a9 100755 --- a/util/install.sh +++ b/util/install.sh @@ -377,6 +377,13 @@ function nox { #./nox_core -v -i ptcp: } +# "Install" POX +function pox { + echo "Installing POX into $HOME/pox..." + cd ~ + git clone https://github.com/noxrepo/pox.git +} + # Install OFtest function oftest { echo "Installing oftest..." @@ -458,7 +465,9 @@ function all { of wireshark ovs - nox + # NOX-classic is deprecated, but you can install it manually if desired. + # nox + pox oftest cbench other @@ -527,7 +536,7 @@ if [ $# -eq 0 ] then all else - while getopts 'abcdfhkmnrtvwx' OPTION + while getopts 'abcdfhkmnprtvwx' OPTION do case $OPTION in a) all;; @@ -539,6 +548,7 @@ else k) kernel;; m) modprobe;; n) mn_deps;; + p) pox;; r) remove_ovs;; t) other;; v) ovs;; From 29884297470efda3cfefea7bb58329c0f79c6de4 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 14 Sep 2012 15:18:17 -0700 Subject: [PATCH 195/250] show method names in git diff output --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..95105a3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.py diff=python From b69ef234ac40fd413bc6e0a6170fed29024b6c3c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 14 Sep 2012 18:23:30 -0700 Subject: [PATCH 196/250] Fix multi-controller/failover support on User, OVSLegacy switches --- mininet/node.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index 80366c4..afc58ce 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -810,7 +810,9 @@ class UserSwitch( Switch ): """Start OpenFlow reference user datapath. Log to /tmp/sN-{ofd,ofp}.log. controllers: list of controller objects""" - controller = controllers[ 0 ] + # Add controllers + clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) + for c in controllers ] ) ofdlog = '/tmp/' + self.name + '-ofd.log' ofplog = '/tmp/' + self.name + '-ofp.log' self.cmd( 'ifconfig lo up' ) @@ -819,7 +821,7 @@ class UserSwitch( Switch ): ' punix:/tmp/' + self.name + ' -d ' + self.dpid + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) self.cmd( 'ofprotocol unix:/tmp/' + self.name + - ' tcp:%s:%d' % ( controller.IP(), controller.port ) + + ' ' + clist + ' --fail=closed ' + self.opts + ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) @@ -865,9 +867,10 @@ class OVSLegacyKernelSwitch( Switch ): intfs = [ str( i ) for i in self.intfList() if not i.IP() ] self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) ) # Run protocol daemon - controller = controllers[ 0 ] + clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) + for c in controllers ] ) self.cmd( 'ovs-openflowd ' + self.dp + - ' tcp:%s:%d' % ( controller.IP(), controller.port ) + + ' ' + clist + ' --fail=secure ' + self.opts + ' --datapath-id=' + self.dpid + ' 1>' + ofplog + ' 2>' + ofplog + '&' ) From 93f9b956e46e6ee203aa1da8f9a74979436ff3db Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 1 Oct 2012 17:57:03 -0700 Subject: [PATCH 197/250] Updated copyright to fix #68 --- LICENSE | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index eb4c3b1..9120a90 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,10 @@ -Mininet 2.0.0d2 License +Mininet 2.0.0d3 License -Copyright (c) 2009-2012 Bob Lantz and Brandon Heller +Copyright (c) 2012 Open Networking Laboratory +Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of +The Leland Stanford Junior University + +Original authors: Bob Lantz and Brandon Heller We are making Mininet available for public use and benefit with the expectation that others will use, modify and enhance the Software and From 9c0ed88c72cb4a3c43f474c9c67435d83c71958d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 4 Oct 2012 18:57:56 -0700 Subject: [PATCH 198/250] Create version check utility and add to code check. Fixes #70 --- Makefile | 1 + util/versioncheck.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100755 util/versioncheck.py diff --git a/Makefile b/Makefile index 5cba0c3..4752ca9 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ clean: codecheck: $(PYSRC) -echo "Running code check" + util/versioncheck.py pyflakes $(PYSRC) pylint --rcfile=.pylint $(PYSRC) pep8 --repeat --ignore=$(P8IGN) $(PYSRC) diff --git a/util/versioncheck.py b/util/versioncheck.py new file mode 100755 index 0000000..d9e5483 --- /dev/null +++ b/util/versioncheck.py @@ -0,0 +1,24 @@ +#!/usr/bin/python + +from subprocess import check_output as co +from sys import exit + +# Actually run bin/mn rather than importing via python path +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 ) + +error = False + +for line in lines.split( '\n' ): + if line and 'Binary' not in line: + fname, fversion = line.split( ':' ) + if version != fversion: + print "%s: incorrect version '%s' (should be '%s')" % ( + fname, fversion, version ) + error = True + +if error: + exit( 1 ) From 4ff6243fd6463ffdf59eee2ed28cffe8b080a464 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 4 Oct 2012 18:58:51 -0700 Subject: [PATCH 199/250] Update version number to 2.0.0d3 (and pass version check) --- INSTALL | 2 +- README.md | 2 +- mininet/net.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/INSTALL b/INSTALL index 22e10db..7edb2c2 100644 --- a/INSTALL +++ b/INSTALL @@ -1,7 +1,7 @@ Mininet Installation/Configuration Notes -Mininet 2.0.0d2 +Mininet 2.0.0d3 --- diff --git a/README.md b/README.md index 58d71c0..b3bc8bd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ or How to Squeeze an OpenFlow Network onto your Laptop -Mininet 2.0.0d2 +Mininet 2.0.0d3 --- **Welcome to Mininet!** diff --git a/mininet/net.py b/mininet/net.py index cd583cc..c1159b7 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -101,7 +101,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.0.0d2" +VERSION = "2.0.0d3" class Mininet( object ): "Network emulation with hosts spawned in network namespaces." From 600dad249884c685b20f1d55b576238d614b20ca Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 4 Oct 2012 19:08:37 -0700 Subject: [PATCH 200/250] Added package installation "instructions" --- INSTALL | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/INSTALL b/INSTALL index 7edb2c2..5de4347 100644 --- a/INSTALL +++ b/INSTALL @@ -22,7 +22,13 @@ Boot up the VM image, log in, and follow the instructions on the wiki page. An additional advantage of using the VM image is that it doesn't mess with your native OS installation or damage it in any way. -2. Native installation on Ubuntu +2. Next-easiest install: use our Ubuntu package! + +To install Mininet itself (i.e. mn and the Python API) on Ubuntu 12.10+ + + sudo apt-get install mininet + +3. Native installation from source on Ubuntu 11.10+ If you're reading this, you've probably already done it, but the command to download the Mininet source code is; @@ -60,7 +66,7 @@ $ mininet/util/install.sh -a This takes about 20 minutes on our test system. -3. Creating your own Mininet/OpenFlow tutorial VM +4. Creating your own Mininet/OpenFlow tutorial VM Creating your own Ubuntu Mininet VM for use with the OpenFlow tutorial is easy! First, create a new Ubuntu VM. Then, run From 31015ef5d8fb0699188d74b4a73257c75a4faa7c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 29 Oct 2012 15:36:19 -0700 Subject: [PATCH 201/250] Make doc a real subdirectory so we can put other things there. --- Makefile | 16 ++++++++++------ doxygen.cfg => doc/doxygen.cfg | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) rename doxygen.cfg => doc/doxygen.cfg (99%) diff --git a/Makefile b/Makefile index 4752ca9..f9e71a3 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,13 @@ MANPAGES = mn.1 mnexec.1 P8IGN = E251,E201,E302,E202 BINDIR = /usr/bin MANDIR = /usr/share/man/man1 +DOCDIRS = doc/html doc/latex +PDF = doc/latex/refman.pdf all: codecheck test clean: - rm -rf build dist *.egg-info *.pyc $(MNEXEC) $(MANPAGES) + rm -rf build dist *.egg-info *.pyc $(MNEXEC) $(MANPAGES) $(DOCDIRS) codecheck: $(PYSRC) -echo "Running code check" @@ -31,6 +33,9 @@ test: $(MININET) $(TEST) -echo "Running tests" mininet/test/test_nets.py +mnexec: mnexec.c $(MN) mininet/net.py + cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(MN) --version`\" $< -o $@ + install: $(MNEXEC) $(MANPAGES) install $(MNEXEC) $(BINDIR) install $(MANPAGES) $(MANDIR) @@ -48,13 +53,12 @@ mn.1: $(MN) PYTHONPATH=. help2man -N -n "create a Mininet network." \ --no-discard-stderr $< -o $@ -mnexec: mnexec.c $(MN) mininet/net.py - cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(MN) --version`\" $< -o $@ - mnexec.1: mnexec help2man -N -n "execution utility for Mininet." \ -h "-h" -v "-v" --no-discard-stderr ./$< -o $@ -doc: man - doxygen doxygen.cfg +.PHONY: doc +doc: man + doxygen doc/doxygen.cfg + make -C doc/latex diff --git a/doxygen.cfg b/doc/doxygen.cfg similarity index 99% rename from doxygen.cfg rename to doc/doxygen.cfg index acc568c..b8015ec 100644 --- a/doxygen.cfg +++ b/doc/doxygen.cfg @@ -25,7 +25,7 @@ DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. -PROJECT_NAME = Mininet +PROJECT_NAME = "Mininet Python API Reference Manual" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or @@ -919,7 +919,7 @@ COMPACT_LATEX = NO # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. -PAPER_TYPE = a4wide +PAPER_TYPE = letter # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. From cec44763030a737dcb3449250043c6af188c7f22 Mon Sep 17 00:00:00 2001 From: Angad Singh Date: Mon, 29 Oct 2012 16:10:00 -0700 Subject: [PATCH 202/250] Merge pull request - closes #36 --- mininet/link.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index 4e01496..2594a17 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -178,7 +178,7 @@ class TCIntf( Intf ): as well as delay, loss and max queue length""" def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False, - enable_ecn=False, enable_red=False ): + latency_ms=None, enable_ecn=False, enable_red=False ): "Return tc commands to set bandwidth" cmds, parent = [], ' root ' @@ -200,10 +200,11 @@ class TCIntf( Intf ): '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] elif use_tbf: - latency_us = 10 * 1500 * 8 / bw + if latency_ms is None: + latency_ms = 15 * 8 / bw cmds += ['%s qdisc add dev %s root handle 1: tbf ' + - 'rate %fMbit burst 15000 latency %fus' % - ( bw, latency_us ) ] + 'rate %fMbit burst 15000 latency %fms' % + ( bw, latency_ms ) ] else: cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1', '%s class add dev %s parent 1:0 classid 1:1 htb ' + @@ -228,18 +229,21 @@ class TCIntf( Intf ): return cmds, parent @staticmethod - def delayCmds( parent, delay=None, loss=None, - max_queue_size=None ): + def delayCmds( parent, delay=None, jitter=None, + loss=None, max_queue_size=None ): "Internal method: return tc commands for delay and loss" cmds = [] if delay and delay < 0: error( 'Negative delay', delay, '\n' ) + elif jitter and jitter < 0: + error( 'Negative jitter', jitter, '\n' ) elif loss and ( loss < 0 or loss > 100 ): error( 'Bad loss percentage', loss, '%%\n' ) else: - # Delay/loss/max queue size - netemargs = '%s%s%s' % ( + # Delay/jitter/loss/max queue size + netemargs = '%s%s%s%s' % ( 'delay %s ' % delay if delay is not None else '', + '%s ' % jitter if jitter is not None else '', 'loss %d ' % loss if loss is not None else '', 'limit %d' % max_queue_size if max_queue_size is not None else '' ) @@ -255,9 +259,10 @@ class TCIntf( Intf ): debug(" *** executing command: %s\n" % c) return self.cmd( c ) - def config( self, bw=None, delay=None, loss=None, disable_gro=True, - speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False, - enable_red=False, max_queue_size=None, **params ): + def config( self, bw=None, delay=None, jitter=None, loss=None, + disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False, + latency_ms=None, enable_ecn=False, enable_red=False, + max_queue_size=None, **params ): "Configure the port and set its properties." result = Intf.config( self, **params) @@ -278,18 +283,19 @@ class TCIntf( Intf ): # Bandwidth limits via various methods bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup, use_hfsc=use_hfsc, use_tbf=use_tbf, - enable_ecn=enable_ecn, + latency_ms=latency_ms, enable_ecn=enable_ecn, enable_red=enable_red ) cmds += bwcmds - # Delay/loss/max_queue_size using netem - cmds += self.delayCmds( delay=delay, loss=loss, + # Delay/jitter/loss/max_queue_size using netem + cmds += self.delayCmds( delay=delay, jitter=jitter, loss=loss, max_queue_size=max_queue_size, parent=parent ) # Ugly but functional: display configuration info stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + ( [ '%s delay' % delay ] if delay is not None else [] ) + + ( [ '%s jitter' % jitter ] if jitter is not None else [] ) + ( ['%d%% loss' % loss ] if loss is not None else [] ) + ( [ 'ECN' ] if enable_ecn else [ 'RED' ] if enable_red else [] ) ) From 22b8e5e42784450834d6f8f4a872bfd9ec5bec0d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 30 Oct 2012 16:45:11 -0700 Subject: [PATCH 203/250] Add custom name to customized functions. Note: we could probably use functools.partial. --- mininet/util.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mininet/util.py b/mininet/util.py index bb645fe..2a82008 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -399,6 +399,7 @@ def custom( cls, **params ): "Customized constructor" kwargs.update( params ) return cls( *args, **kwargs ) + customized.__name__ = 'custom(%s,%s)' % ( cls, params ) return customized def splitArgs( argstr ): @@ -439,6 +440,7 @@ def customConstructor( constructors, argStr ): constructor, args, newargs ) ) return constructor( name, *newargs, **params ) + customized.__name__ = 'customConstructor(%s)' % argStr return customized def buildTopo( topos, topoStr ): From f58f83c04309c574f93f10a40cee3c858e3c3625 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 30 Oct 2012 16:50:12 -0700 Subject: [PATCH 204/250] Allow controller to optionally be a list of constructors/classes --- mininet/net.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mininet/net.py b/mininet/net.py index c1159b7..0105ea8 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -278,7 +278,11 @@ class Mininet( object ): if not self.controllers: # Add a default controller info( '*** Adding controller\n' ) - self.addController( 'c0' ) + classes = self.controller + if type( classes ) is not list: + classes = [ classes ] + for i, cls in enumerate( classes ): + self.addController( 'c%d' % i, cls ) info( '*** Adding hosts:\n' ) for hostName in topo.hosts(): From 4b1dc93bccbca0096f77f48f37771b6edb5b8d40 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 30 Oct 2012 16:58:51 -0700 Subject: [PATCH 205/250] Avoid modifying keyword parameter dictionary in customized() --- mininet/util.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mininet/util.py b/mininet/util.py index 2a82008..6418f52 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -395,8 +395,11 @@ def irange(start, end): def custom( cls, **params ): "Returns customized constructor for class cls." + # Note: we may wish to see if we can use functools.partial() here + # and in customConstructor def customized( *args, **kwargs): "Customized constructor" + kwargs = kwargs.copy() kwargs.update( params ) return cls( *args, **kwargs ) customized.__name__ = 'custom(%s,%s)' % ( cls, params ) @@ -432,6 +435,7 @@ def customConstructor( constructors, argStr ): def customized( name, *args, **params ): "Customized constructor, useful for Node, Link, and other classes" + params = params.copy() params.update( kwargs ) if not newargs: return constructor( name, *args, **params ) From c04ef88e5c12a31a34b78ef6447ea28e5e6f3d44 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 3 Nov 2012 16:10:57 -0700 Subject: [PATCH 206/250] Add note regarding removing old OVS junk. --- INSTALL | 6 ++++++ doc/doxygen.cfg | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/INSTALL b/INSTALL index 5de4347..d45fdde 100644 --- a/INSTALL +++ b/INSTALL @@ -28,6 +28,12 @@ To install Mininet itself (i.e. mn and the Python API) on Ubuntu 12.10+ sudo apt-get install mininet +Note: if you are upgrading from an older version of Mininet, make sure you +remove the old OVS from /usr/local: + + sudo rm /usr/local/bin/ovs* + sudo rm /usr/local/sbin/ovs* + 3. Native installation from source on Ubuntu 11.10+ If you're reading this, you've probably already done it, but the command to diff --git a/doc/doxygen.cfg b/doc/doxygen.cfg index b8015ec..a92bb15 100644 --- a/doc/doxygen.cfg +++ b/doc/doxygen.cfg @@ -114,7 +114,7 @@ FULL_PATH_NAMES = YES # If left blank the directory from which doxygen is run is used as the # path to strip. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells From 4744aa2b78ad03cd7ba43033d5f27056d61cf61d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 3 Nov 2012 16:53:26 -0700 Subject: [PATCH 207/250] Updated README to reflect Mininet 2.0. --- README.md | 99 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index b3bc8bd..f406405 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ - Mininet: A Simple Virtual Testbed for OpenFlow/SDN - or - How to Squeeze an OpenFlow Network onto your Laptop + Mininet: Rapid Prototyping for Software Defined Networks + or + The best way to emulate almost any network on your laptop! -Mininet 2.0.0d3 +Mininet 2.0.0rc1 --- -**Welcome to Mininet!** +** Welcome to Mininet! ** -Mininet creates OpenFlow test networks by using process-based +Mininet creates virtual SDN/OpenFlow test networks by using process-based virtualization and network namespaces. Simulated hosts (as well as switches and controllers with the user @@ -16,24 +16,38 @@ datapath) are created as processes in separate network namespaces. This allows a complete OpenFlow network to be simulated on top of a single Linux kernel. +Mininet's support for OpenFlow and Linux allows you to create a custom +network with customized routing, and to run almost any existing Linux +networking application on top of it without modification. OpenFlow-based +designs that work in Mininet can usually be transferred to hardware with +minimal change for full line-rate execution. + Mininet may be invoked directly from the command line, and also provides a handy Python API for creating networks of varying sizes and topologies. -In order to run Mininet, you must have: +** Mininet 2.0.0 ** -* A Linux kernel compiled with network namespace support - enabled (see `INSTALL` for additional information.) +Mininet 2.0.0 is a major upgrade to the Mininet system and provides +a number of enhancements and new features, including: -* An OpenFlow implementation (either the reference user or kernel - space implementations, or Open vSwitch.) Appropriate kernel modules - (e.g. tun and ofdatapath for the reference kernel implementation) must - be loaded. +* First-class Interface (`Intf`) and Link (`Link`) classes -* Python, `bash`, `ping`, `iperf`, etc. +* An upgraded Topology (`Topo`) class which supports node and link + customization -* Root privileges (required for network device access) +* Link bandwidth limits using `tc` (`TCIntf` and `TCLink` classes) -Currently Mininet includes: +* CPU isolation and bandwidth limits (`CPULimitedHost` class) + +* Support for the Open vSwitch 1.4+ (including Ubuntu OVS packages) + +* Man pages for the `mn` and `mnexec` utilities. + +[Since the API (most notably the topology) has changed, existing code that +runs in Mininet 1.0 will need to be changed to run with Mininet 2.0. This +is the primary reason for the major version number change.] + +Mininet also includes: - A simple node infrastructure (`Host`, `Switch`, `Controller` classes) for creating virtual OpenFlow networks @@ -62,17 +76,56 @@ Currently Mininet includes: - Examples (in the examples/ directory) to help you get started. -Batteries are not included (yet!) +- Full API documentation via Python `help()` docstrings, as well as + the ability to generate PDF/HTML documentation with "make doc." -However, some preliminary installation notes are included in the INSTALL -file. +In order to run Mininet, you must have: -Additionally, much useful information, including a Mininet tutorial, -is available on the [Mininet Wiki](http://openflow.org/mininet). +* A Linux kernel compiled with network namespace support + enabled (see `INSTALL` for additional information.) -Enjoy, and good luck! +* An OpenFlow implementation (either the reference user or kernel + space implementations, or Open vSwitch.) Appropriate kernel modules + (e.g. tun and ofdatapath for the reference kernel implementation) must + be loaded. + +* Python, `bash`, `ping`, `iperf`, etc. + +* Root privileges (required for network device access) + +Installation instructions are available in INSTALL + +*** Mininet Documentation *** + +In addition to the API documentation (`make doc`) much useful information, +including a Mininet walkthrough and an introduction to the Python API is +available on the [Mininet Web Site](http://openflow.org/mininet). There is +also a wiki which you are encouraged to read and to contribute to, +particularly the Frequently Asked Questions (FAQ.) + +*** Mininet Support *** + +Mininet is supported by the friendly Mininet community. We encourage you to +join the Mininet mailing list, `mininet-discuss` at: + + + +*** Contributing to Mininet *** + +Mininet is an open-source project and is currently hosted at +. You are encouraged to download the code, +examine it, modify it, and submit bug reports, bug fixes, feature +requests, and enhancements! + +Best wishes, and we look forward to seeing what you can do with Mininet +to change the networking world! --- + Bob Lantz -rlantz@cs.stanford.edu +Brandon Heller +Nikhil Handigol +Vimal Jeyakumar + +Mininet Project From e2b799b815f0e2af63246394458857903d92ff41 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 3 Nov 2012 17:47:29 -0700 Subject: [PATCH 208/250] Fix/work around setuptools' evil PYTHONPATH madness. --- bin/mn | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/mn b/bin/mn index 91cac20..142208c 100755 --- a/bin/mn +++ b/bin/mn @@ -12,10 +12,14 @@ Example to pull custom params (topo, switch, etc.) from a file: """ from optparse import OptionParser -import os.path +import os import sys import time +# Fix setuptools' evil madness, and open up (more?) security holes +if 'PYTHONPATH' in os.environ: + sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path + from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info From 21b2c2c4aab6fc13a5caeef8ac8d326389f08967 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 3 Nov 2012 21:38:59 -0700 Subject: [PATCH 209/250] VERSION -> 2.0.0rc1 --- INSTALL | 2 +- LICENSE | 2 +- README.md | 4 ++-- mininet/net.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/INSTALL b/INSTALL index d45fdde..3c11640 100644 --- a/INSTALL +++ b/INSTALL @@ -1,7 +1,7 @@ Mininet Installation/Configuration Notes -Mininet 2.0.0d3 +Mininet 2.0.0rc1 --- diff --git a/LICENSE b/LICENSE index 9120a90..ba81938 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Mininet 2.0.0d3 License +Mininet 2.0.0rc1 License Copyright (c) 2012 Open Networking Laboratory Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of diff --git a/README.md b/README.md index f406405..c44bd94 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ minimal change for full line-rate execution. Mininet may be invoked directly from the command line, and also provides a handy Python API for creating networks of varying sizes and topologies. -** Mininet 2.0.0 ** +** Mininet 2.0.0rc1 ** -Mininet 2.0.0 is a major upgrade to the Mininet system and provides +Mininet 2.0.0rc1 is a major upgrade to the Mininet system and provides a number of enhancements and new features, including: * First-class Interface (`Intf`) and Link (`Link`) classes diff --git a/mininet/net.py b/mininet/net.py index 0105ea8..a44b670 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -101,7 +101,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.0.0d3" +VERSION = "2.0.0rc1" class Mininet( object ): "Network emulation with hosts spawned in network namespaces." From ff4b41439a77d743aa314de270a69a055e8bb00a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 3 Nov 2012 21:59:04 -0700 Subject: [PATCH 210/250] Minor changes. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c44bd94..fbea7dd 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ allows a complete OpenFlow network to be simulated on top of a single Linux kernel. Mininet's support for OpenFlow and Linux allows you to create a custom -network with customized routing, and to run almost any existing Linux +network with customized routing, and to run almost any Linux-compatible networking application on top of it without modification. OpenFlow-based designs that work in Mininet can usually be transferred to hardware with minimal change for full line-rate execution. @@ -43,6 +43,8 @@ a number of enhancements and new features, including: * Man pages for the `mn` and `mnexec` utilities. +* Debian packaging (and apt-get install in Ubuntu 12.10) + [Since the API (most notably the topology) has changed, existing code that runs in Mininet 1.0 will need to be changed to run with Mininet 2.0. This is the primary reason for the major version number change.] From 73da7204e6b3c8629a764c007234327c73d562fe Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Thu, 8 Nov 2012 15:31:10 -0800 Subject: [PATCH 211/250] Update README --- README.md | 148 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 78 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index fbea7dd..16024fc 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,95 @@ +Mininet: Rapid Prototyping for Software Defined Networks +=== - Mininet: Rapid Prototyping for Software Defined Networks - or - The best way to emulate almost any network on your laptop! +The best way to emulate almost any network on your laptop! -Mininet 2.0.0rc1 +Version 2.0.0rc1 ---- -** Welcome to Mininet! ** +### What is Mininet? -Mininet creates virtual SDN/OpenFlow test networks by using process-based -virtualization and network namespaces. +Mininet emulates a complete network of hosts, links, and switches on a single +machine. To create a sample two-host, one-switch network, just run: -Simulated hosts (as well as switches and controllers with the user -datapath) are created as processes in separate network namespaces. This -allows a complete OpenFlow network to be simulated on top of a single -Linux kernel. + sudo mn -Mininet's support for OpenFlow and Linux allows you to create a custom -network with customized routing, and to run almost any Linux-compatible -networking application on top of it without modification. OpenFlow-based -designs that work in Mininet can usually be transferred to hardware with -minimal change for full line-rate execution. +Mininet is useful for interactive development, testing, and demos, especially +those using OpenFlow and SDN. OpenFlow-based network controllers prototyped in +Mininet can usually be transferred to hardware with minimal changes for full +line-rate execution. -Mininet may be invoked directly from the command line, and also provides a -handy Python API for creating networks of varying sizes and topologies. +### How does it work? -** Mininet 2.0.0rc1 ** +Mininet creates virtual networks using process-based virtualization and network +namespaces - features that are available in recent Linux kernels. In Mininet, +hosts are emulated as bash processes running in a network namespace, so any +code that would normally run on a Linux server (like a web server or client +program) should run just fine within a Mininet "Host". The Mininet "Host" will +have its own private network interface and can only see its own processes. +Switches in Mininet are software-based switches like Open vSwitch or the +OpenFlow reference switch. Links are virtual ethernet pairs, which live in the +Linux kernel and connect our emulated switches to emulated hosts (processes). -Mininet 2.0.0rc1 is a major upgrade to the Mininet system and provides +### Features + +Mininet includes: + +* A command-line launcher ('mn') to instantiate networks. + +* A handy Python API for creating networks of varying sizes and topologies. + +* Examples (in the examples/ directory) to help you get started. + +* Full API documentation via Python `help()` docstrings, as well as the ability + to generate PDF/HTML documentation with "make doc." + +* Parametrized topologies (`Topo` subclasses) using the Mininet object. For + example, a tree network may be created with the command: + + mn --topo tree,depth=2,fanout=3` + +* A command-line interface (CLI class) which provides useful diagnostic + commands (like iperf and ping), as well as the ability to run a command to a + node. For example, + + mininet> h11 ifconfig -a` + + tells host h11 to run the command `ifconfig -a` + +* A 'cleanup' command to get rid of junk (interfaces, processes, files in + /tmp, etc.) which might be left around by Mininet or Linux. Try this if + things stop working! + + mn -c + +### New features in 2.0.0: + +Mininet 2.0.0 is a major upgrade and provides a number of enhancements and new features, including: -* First-class Interface (`Intf`) and Link (`Link`) classes - -* An upgraded Topology (`Topo`) class which supports node and link - customization - * Link bandwidth limits using `tc` (`TCIntf` and `TCLink` classes) * CPU isolation and bandwidth limits (`CPULimitedHost` class) * Support for the Open vSwitch 1.4+ (including Ubuntu OVS packages) -* Man pages for the `mn` and `mnexec` utilities. - * Debian packaging (and apt-get install in Ubuntu 12.10) +* First-class Interface (`Intf`) and Link (`Link`) classes for easier extensibility + +* An upgraded Topology (`Topo`) class which supports node and link + customization + +* Man pages for the `mn` and `mnexec` utilities. + [Since the API (most notably the topology) has changed, existing code that runs in Mininet 1.0 will need to be changed to run with Mininet 2.0. This is the primary reason for the major version number change.] -Mininet also includes: +### Install -- A simple node infrastructure (`Host`, `Switch`, `Controller` classes) for - creating virtual OpenFlow networks - -- A simple network infrastructure (`Mininet` class) supporting parametrized - topologies (`Topo` subclasses.) For example, a tree network may be created - with the command - - `# mn --topo tree,depth=2,fanout=3` - -- Basic tests, including connectivity (`ping`) and bandwidth (`iperf`) +To install Mininet, the easiest approach is to start with an Ubuntu system like 12.04 and run util/vm/install.sh, which will install any needed dependencies. -- A command-line interface (CLI class) which provides useful - diagnostic commands, as well as the ability to send a command to a - node. For example, - - `mininet> h11 ifconfig -a` - - tells host h11 to run the command `ifconfig -a` - -- A 'cleanup' command to get rid of junk (interfaces, processes, files in - /tmp, etc.) which might be left around by Mininet or Linux. Try this if - things stop working! - - `# mn -c` - -- Examples (in the examples/ directory) to help you get started. - -- Full API documentation via Python `help()` docstrings, as well as - the ability to generate PDF/HTML documentation with "make doc." - -In order to run Mininet, you must have: +In general, you must have: * A Linux kernel compiled with network namespace support enabled (see `INSTALL` for additional information.) @@ -95,9 +103,9 @@ In order to run Mininet, you must have: * Root privileges (required for network device access) -Installation instructions are available in INSTALL +Further installation instructions are available in INSTALL. -*** Mininet Documentation *** +### Documentation In addition to the API documentation (`make doc`) much useful information, including a Mininet walkthrough and an introduction to the Python API is @@ -105,14 +113,14 @@ available on the [Mininet Web Site](http://openflow.org/mininet). There is also a wiki which you are encouraged to read and to contribute to, particularly the Frequently Asked Questions (FAQ.) -*** Mininet Support *** +### Support -Mininet is supported by the friendly Mininet community. We encourage you to +Mininet community-supported. We encourage you to join the Mininet mailing list, `mininet-discuss` at: -*** Contributing to Mininet *** +### Contributing Mininet is an open-source project and is currently hosted at . You are encouraged to download the code, @@ -122,12 +130,12 @@ requests, and enhancements! Best wishes, and we look forward to seeing what you can do with Mininet to change the networking world! ---- +### Credits -Bob Lantz -Brandon Heller -Nikhil Handigol -Vimal Jeyakumar +The Mininet Team: -Mininet Project +* Bob Lantz +* Brandon Heller +* Nikhil Handigol +* Vimal Jeyakumar From 4885cb24ed240f8336a32953a411e3878d6fd0ed Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 8 Nov 2012 22:05:52 -0800 Subject: [PATCH 212/250] Update README.md File and command names in typewriter text. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 16024fc..804d940 100644 --- a/README.md +++ b/README.md @@ -33,19 +33,19 @@ Linux kernel and connect our emulated switches to emulated hosts (processes). Mininet includes: -* A command-line launcher ('mn') to instantiate networks. +* A command-line launcher (`mn`) to instantiate networks. * A handy Python API for creating networks of varying sizes and topologies. -* Examples (in the examples/ directory) to help you get started. +* Examples (in the `examples/` directory) to help you get started. * Full API documentation via Python `help()` docstrings, as well as the ability - to generate PDF/HTML documentation with "make doc." + to generate PDF/HTML documentation with `make doc`. * Parametrized topologies (`Topo` subclasses) using the Mininet object. For example, a tree network may be created with the command: - mn --topo tree,depth=2,fanout=3` + mn --topo tree,depth=2,fanout=3 * A command-line interface (CLI class) which provides useful diagnostic commands (like iperf and ping), as well as the ability to run a command to a @@ -59,7 +59,7 @@ Mininet includes: /tmp, etc.) which might be left around by Mininet or Linux. Try this if things stop working! - mn -c + mn -c ### New features in 2.0.0: @@ -72,7 +72,7 @@ a number of enhancements and new features, including: * Support for the Open vSwitch 1.4+ (including Ubuntu OVS packages) -* Debian packaging (and apt-get install in Ubuntu 12.10) +* Debian packaging (and `apt-get install mininet` in Ubuntu 12.10) * First-class Interface (`Intf`) and Link (`Link`) classes for easier extensibility @@ -103,7 +103,7 @@ In general, you must have: * Root privileges (required for network device access) -Further installation instructions are available in INSTALL. +Further installation instructions are available in `INSTALL`. ### Documentation From 535e61d2be8a13e652ae58b1b50fe593246b3918 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Nov 2012 23:18:56 -0800 Subject: [PATCH 213/250] Still trying to fix the typeface and spacing... --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 804d940..fb51695 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,13 @@ Mininet includes: * Parametrized topologies (`Topo` subclasses) using the Mininet object. For example, a tree network may be created with the command: - mn --topo tree,depth=2,fanout=3 + `mn --topo tree,depth=2,fanout=3` * A command-line interface (CLI class) which provides useful diagnostic commands (like iperf and ping), as well as the ability to run a command to a node. For example, - mininet> h11 ifconfig -a` + `mininet> h11 ifconfig -a` tells host h11 to run the command `ifconfig -a` @@ -59,7 +59,7 @@ Mininet includes: /tmp, etc.) which might be left around by Mininet or Linux. Try this if things stop working! - mn -c + `mn -c` ### New features in 2.0.0: From 3e38a959c3d4a9586e5e91fbdd1dc7de497472e2 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Nov 2012 23:31:31 -0800 Subject: [PATCH 214/250] Moved installation instructions and prereqs into INSTALL. --- INSTALL | 38 ++++++++++++++++++++++++++++++++------ README.md | 23 ++++------------------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/INSTALL b/INSTALL index 3c11640..59aee0a 100644 --- a/INSTALL +++ b/INSTALL @@ -12,17 +12,23 @@ can also easily create your own Mininet VM image (3). (Other distributions may be supported in the future - if you would like to contribute an installation script, we would welcome it!) -1. Easiest "install" - use our pre-built VM image! +1. Easiest "installation" - use our pre-built VM image! The easiest way to get Mininet running is to start with one of our pre-built virtual machine images from http://openflow.org/mininet Boot up the VM image, log in, and follow the instructions on the wiki page. -An additional advantage of using the VM image is that it doesn't mess with +One advantage of using the VM image is that it doesn't mess with your native OS installation or damage it in any way. -2. Next-easiest install: use our Ubuntu package! +Although a single Mininet instance can simulate multiple networks with +multiple controllers, only one Mininet instance may currently be run at +a time, and Mininet requires root access in the machine it's running on. +Therefore, if you have a multiuser system, you may wish to consider +running Mininet in a VM. + +2. Next-easiest option: use our Ubuntu package! To install Mininet itself (i.e. mn and the Python API) on Ubuntu 12.10+ @@ -36,7 +42,7 @@ remove the old OVS from /usr/local: 3. Native installation from source on Ubuntu 11.10+ -If you're reading this, you've probably already done it, but the command to +If you're reading this, you've probably already done so, but the command to download the Mininet source code is; git clone git://openflow.org/mininet.git @@ -80,10 +86,30 @@ is easy! First, create a new Ubuntu VM. Then, run $ wget https://raw.github.com/mininet/mininet/util/vm/install-mininet-vm.sh $ time install-mininet-vm.sh +5. Installation on other Linux distributions + +Although we don't support other Linux distributions directly, it should be +possible to install and run Mininet with some degree of manual effort. + +In general, you must have: + +* A Linux kernel compiled with network namespace support enabled + +* An OpenFlow implementation (either the reference user or kernel + space implementations, or Open vSwitch.) Appropriate kernel modules + (e.g. tun and ofdatapath for the reference kernel implementation) must + be loaded. + +* Python, `bash`, `ping`, `iperf`, etc. + +* Root privileges (required for network device access) + +We encourage contribution of patches to the `install.sh` script to support +other Linux distributions. + Good luck! -p.s. Note that only one instance of Mininet is currently supported on a single -machine - that's one reason we recommend using a VM to run it. +Mininet Team --- diff --git a/README.md b/README.md index fb51695..f889786 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ + Mininet: Rapid Prototyping for Software Defined Networks -=== +======================================================== The best way to emulate almost any network on your laptop! @@ -85,25 +86,9 @@ a number of enhancements and new features, including: runs in Mininet 1.0 will need to be changed to run with Mininet 2.0. This is the primary reason for the major version number change.] -### Install +### Installation -To install Mininet, the easiest approach is to start with an Ubuntu system like 12.04 and run util/vm/install.sh, which will install any needed dependencies. - -In general, you must have: - -* A Linux kernel compiled with network namespace support - enabled (see `INSTALL` for additional information.) - -* An OpenFlow implementation (either the reference user or kernel - space implementations, or Open vSwitch.) Appropriate kernel modules - (e.g. tun and ofdatapath for the reference kernel implementation) must - be loaded. - -* Python, `bash`, `ping`, `iperf`, etc. - -* Root privileges (required for network device access) - -Further installation instructions are available in `INSTALL`. +See `INSTALL` for installation instructions and details. ### Documentation From 0fb91f186e4e424a65bc342d6ede659dc7f60ee1 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Nov 2012 23:47:48 -0800 Subject: [PATCH 215/250] More minor tweaks.... --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f889786..b5158d4 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ Mininet includes: `mn --topo tree,depth=2,fanout=3` -* A command-line interface (CLI class) which provides useful diagnostic - commands (like iperf and ping), as well as the ability to run a command to a - node. For example, +* A command-line interface (`CLI` class) which provides useful diagnostic + commands (like `iperf` and `ping`), as well as the ability to run a command + to a node. For example, `mininet> h11 ifconfig -a` @@ -62,7 +62,7 @@ Mininet includes: `mn -c` -### New features in 2.0.0: +### New features in 2.0.0 Mininet 2.0.0 is a major upgrade and provides a number of enhancements and new features, including: @@ -75,7 +75,8 @@ a number of enhancements and new features, including: * Debian packaging (and `apt-get install mininet` in Ubuntu 12.10) -* First-class Interface (`Intf`) and Link (`Link`) classes for easier extensibility +* First-class Interface (`Intf`) and Link (`Link`) classes for easier + extensibility * An upgraded Topology (`Topo`) class which supports node and link customization From ead9f83050a2a21fe8a2deca56344c540e2f4579 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Nov 2012 23:52:07 -0800 Subject: [PATCH 216/250] Reflow text and try crazy idea for heading. --- README.md | 97 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index b5158d4..6ac1ef5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ - -Mininet: Rapid Prototyping for Software Defined Networks -======================================================== +========================================================= + Mininet: Rapid Prototyping for Software Defined Networks +========================================================= The best way to emulate almost any network on your laptop! @@ -8,27 +8,30 @@ Version 2.0.0rc1 ### What is Mininet? -Mininet emulates a complete network of hosts, links, and switches on a single -machine. To create a sample two-host, one-switch network, just run: +Mininet emulates a complete network of hosts, links, and switches +on a single machine. To create a sample two-host, one-switch network, +just run: sudo mn -Mininet is useful for interactive development, testing, and demos, especially -those using OpenFlow and SDN. OpenFlow-based network controllers prototyped in -Mininet can usually be transferred to hardware with minimal changes for full -line-rate execution. +Mininet is useful for interactive development, testing, and demos, +especially those using OpenFlow and SDN. OpenFlow-based network +controllers prototyped in Mininet can usually be transferred to +hardware with minimal changes for full line-rate execution. ### How does it work? -Mininet creates virtual networks using process-based virtualization and network -namespaces - features that are available in recent Linux kernels. In Mininet, -hosts are emulated as bash processes running in a network namespace, so any -code that would normally run on a Linux server (like a web server or client -program) should run just fine within a Mininet "Host". The Mininet "Host" will -have its own private network interface and can only see its own processes. -Switches in Mininet are software-based switches like Open vSwitch or the -OpenFlow reference switch. Links are virtual ethernet pairs, which live in the -Linux kernel and connect our emulated switches to emulated hosts (processes). +Mininet creates virtual networks using process-based virtualization +and network namespaces - features that are available in recent Linux +kernels. In Mininet, hosts are emulated as bash processes running in +a network namespace, so any code that would normally run on a Linux +server (like a web server or client program) should run just fine +within a Mininet "Host". The Mininet "Host" will have its own private +network interface and can only see its own processes. Switches in +Mininet are software-based switches like Open vSwitch or the OpenFlow +reference switch. Links are virtual ethernet pairs, which live in the +Linux kernel and connect our emulated switches to emulated hosts +(processes). ### Features @@ -36,30 +39,32 @@ Mininet includes: * A command-line launcher (`mn`) to instantiate networks. -* A handy Python API for creating networks of varying sizes and topologies. +* A handy Python API for creating networks of varying sizes and + topologies. * Examples (in the `examples/` directory) to help you get started. -* Full API documentation via Python `help()` docstrings, as well as the ability - to generate PDF/HTML documentation with `make doc`. +* Full API documentation via Python `help()` docstrings, as well as + the ability to generate PDF/HTML documentation with `make doc`. + +* Parametrized topologies (`Topo` subclasses) using the Mininet + object. For example, a tree network may be created with the + command: -* Parametrized topologies (`Topo` subclasses) using the Mininet object. For - example, a tree network may be created with the command: - `mn --topo tree,depth=2,fanout=3` -* A command-line interface (`CLI` class) which provides useful diagnostic - commands (like `iperf` and `ping`), as well as the ability to run a command - to a node. For example, - +* A command-line interface (`CLI` class) which provides useful + diagnostic commands (like `iperf` and `ping`), as well as the + ability to run a command to a node. For example, + `mininet> h11 ifconfig -a` - + tells host h11 to run the command `ifconfig -a` -* A 'cleanup' command to get rid of junk (interfaces, processes, files in - /tmp, etc.) which might be left around by Mininet or Linux. Try this if - things stop working! - +* A 'cleanup' command to get rid of junk (interfaces, processes, files + in /tmp, etc.) which might be left around by Mininet or Linux. Try + this if things stop working! + `mn -c` ### New features in 2.0.0 @@ -83,9 +88,9 @@ a number of enhancements and new features, including: * Man pages for the `mn` and `mnexec` utilities. -[Since the API (most notably the topology) has changed, existing code that -runs in Mininet 1.0 will need to be changed to run with Mininet 2.0. This -is the primary reason for the major version number change.] +[Since the API (most notably the topology) has changed, existing code +that runs in Mininet 1.0 will need to be changed to run with Mininet +2.0. This is the primary reason for the major version number change.] ### Installation @@ -93,16 +98,17 @@ See `INSTALL` for installation instructions and details. ### Documentation -In addition to the API documentation (`make doc`) much useful information, -including a Mininet walkthrough and an introduction to the Python API is -available on the [Mininet Web Site](http://openflow.org/mininet). There is -also a wiki which you are encouraged to read and to contribute to, -particularly the Frequently Asked Questions (FAQ.) +In addition to the API documentation (`make doc`) much useful +information, including a Mininet walkthrough and an introduction +to the Python API is available on the +[Mininet Web Site](http://openflow.org/mininet). +There is also a wiki which you are encouraged to read and to +contribute to, particularly the Frequently Asked Questions (FAQ.) ### Support -Mininet community-supported. We encourage you to -join the Mininet mailing list, `mininet-discuss` at: +Mininet is community-supported. We encourage you to join the +Mininet mailing list, `mininet-discuss` at: @@ -113,8 +119,8 @@ Mininet is an open-source project and is currently hosted at examine it, modify it, and submit bug reports, bug fixes, feature requests, and enhancements! -Best wishes, and we look forward to seeing what you can do with Mininet -to change the networking world! +Best wishes, and we look forward to seeing what you can do with +Mininet to change the networking world! ### Credits @@ -124,4 +130,3 @@ The Mininet Team: * Brandon Heller * Nikhil Handigol * Vimal Jeyakumar - From dd1a450b50e0a622c35e42ecdc06ba304657316c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Nov 2012 23:55:31 -0800 Subject: [PATCH 217/250] Mars needs commas! --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ac1ef5..9e29f57 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,9 @@ See `INSTALL` for installation instructions and details. ### Documentation -In addition to the API documentation (`make doc`) much useful +In addition to the API documentation (`make doc`), much useful information, including a Mininet walkthrough and an introduction -to the Python API is available on the +to the Python API, is available on the [Mininet Web Site](http://openflow.org/mininet). There is also a wiki which you are encouraged to read and to contribute to, particularly the Frequently Asked Questions (FAQ.) From dabc0b812a352b2cc997ec9148e2217c1e37486b Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Nov 2012 23:57:39 -0800 Subject: [PATCH 218/250] Bash in tt, abolish forward single quotes. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e29f57..40156a6 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ hardware with minimal changes for full line-rate execution. Mininet creates virtual networks using process-based virtualization and network namespaces - features that are available in recent Linux -kernels. In Mininet, hosts are emulated as bash processes running in +kernels. In Mininet, hosts are emulated as `bash` processes running in a network namespace, so any code that would normally run on a Linux server (like a web server or client program) should run just fine within a Mininet "Host". The Mininet "Host" will have its own private @@ -61,7 +61,7 @@ Mininet includes: tells host h11 to run the command `ifconfig -a` -* A 'cleanup' command to get rid of junk (interfaces, processes, files +* A "cleanup" command to get rid of junk (interfaces, processes, files in /tmp, etc.) which might be left around by Mininet or Linux. Try this if things stop working! From eb3b74ea346258b9de0c1e00cacac6ce5156fe55 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Thu, 8 Nov 2012 23:58:54 -0800 Subject: [PATCH 219/250] sudo mn seems lonely if it takes up the whole line --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40156a6..20072a8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Mininet emulates a complete network of hosts, links, and switches on a single machine. To create a sample two-host, one-switch network, just run: - sudo mn + `sudo mn` Mininet is useful for interactive development, testing, and demos, especially those using OpenFlow and SDN. OpenFlow-based network From bad8656361d7208420fc9469cac626c07d8cbc25 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Nov 2012 00:02:17 -0800 Subject: [PATCH 220/250] More header madness. --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 20072a8..acf7304 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ -========================================================= - Mininet: Rapid Prototyping for Software Defined Networks -========================================================= - -The best way to emulate almost any network on your laptop! +================================================================== + Mininet: Rapid Prototyping for Software Defined Networks +================================================================== +__or, the best way to emulate almost any network on your laptop!__ Version 2.0.0rc1 From 015aaa2107dd8bdb45e4579357fc417f91fdb45a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Nov 2012 00:02:54 -0800 Subject: [PATCH 221/250] That didn't work... --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index acf7304..5a5d923 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ================================================================== Mininet: Rapid Prototyping for Software Defined Networks ================================================================== + __or, the best way to emulate almost any network on your laptop!__ Version 2.0.0rc1 From f7d6c9e7a1ad2e113afc5ad15b32f649cb844b0b Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Nov 2012 00:03:36 -0800 Subject: [PATCH 222/250] Another attempt. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5a5d923..bd83759 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -================================================================== - Mininet: Rapid Prototyping for Software Defined Networks -================================================================== + +Mininet: Rapid Prototyping for Software Defined Networks +======================================================== __or, the best way to emulate almost any network on your laptop!__ From d49aaf0f704fbc7c92cb19f54ba4f4caf38a7cf0 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Nov 2012 00:04:40 -0800 Subject: [PATCH 223/250] Hmm, maybe this is better. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd83759..f2a7f8a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Mininet: Rapid Prototyping for Software Defined Networks ======================================================== -__or, the best way to emulate almost any network on your laptop!__ +*The best way to emulate almost any network on your laptop!* Version 2.0.0rc1 From 01e028c19f2899fe46a8afb022d6071da92672d9 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Nov 2012 00:07:56 -0800 Subject: [PATCH 224/250] Add Mininet-HiFi comment --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f2a7f8a..f8168ea 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,13 @@ Mininet includes: Mininet 2.0.0 is a major upgrade and provides a number of enhancements and new features, including: -* Link bandwidth limits using `tc` (`TCIntf` and `TCLink` classes) +* "Mininet-HiFi" functionality: -* CPU isolation and bandwidth limits (`CPULimitedHost` class) + * Link bandwidth limits using `tc` (`TCIntf` and `TCLink` classes) -* Support for the Open vSwitch 1.4+ (including Ubuntu OVS packages) + * CPU isolation and bandwidth limits (`CPULimitedHost` class) + +* Support for Open vSwitch 1.4+ (including Ubuntu OVS packages) * Debian packaging (and `apt-get install mininet` in Ubuntu 12.10) From 78e3e18c42c1066a9e8900b8eadfd343d3ea1b6e Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Nov 2012 00:09:11 -0800 Subject: [PATCH 225/250] trying to fix list spacing --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index f8168ea..e3a3adc 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,7 @@ Mininet 2.0.0 is a major upgrade and provides a number of enhancements and new features, including: * "Mininet-HiFi" functionality: - * Link bandwidth limits using `tc` (`TCIntf` and `TCLink` classes) - * CPU isolation and bandwidth limits (`CPULimitedHost` class) * Support for Open vSwitch 1.4+ (including Ubuntu OVS packages) From e3c8066a9e121ca4504f13b4b838b1dba5b3b60c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 9 Nov 2012 00:09:41 -0800 Subject: [PATCH 226/250] OK, github is broken - reverting to old spacing. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e3a3adc..f8168ea 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,9 @@ Mininet 2.0.0 is a major upgrade and provides a number of enhancements and new features, including: * "Mininet-HiFi" functionality: + * Link bandwidth limits using `tc` (`TCIntf` and `TCLink` classes) + * CPU isolation and bandwidth limits (`CPULimitedHost` class) * Support for Open vSwitch 1.4+ (including Ubuntu OVS packages) From b597ef5d555da40728cf6f96529c14ae47ea44f8 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Mon, 12 Nov 2012 16:45:32 -0800 Subject: [PATCH 227/250] install: Add help2man to MN deps 'make install' calls help2man, so apt-get install it beforehand. --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 6a319a9..5af003b 100755 --- a/util/install.sh +++ b/util/install.sh @@ -123,7 +123,7 @@ function kernel_clean { function mn_deps { echo "Installing Mininet dependencies" $install gcc make screen psmisc xterm ssh iperf iproute \ - python-setuptools python-networkx cgroup-bin ethtool + python-setuptools python-networkx cgroup-bin ethtool help2man if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then echo "Upgrading networkx to avoid deprecation warning" From 59897168d40d530e8d3b448c0bacbed66dc89f89 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Mon, 12 Nov 2012 16:45:51 -0800 Subject: [PATCH 228/250] install: Update OFTest repo location This repo has moved to git://github.com/floodlight/oftest.git --- util/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 5af003b..5970ebd 100755 --- a/util/install.sh +++ b/util/install.sh @@ -393,7 +393,7 @@ function oftest { # Install oftest: cd ~/ - git clone git://openflow.org/oftest + git clone git://github.com/floodlight/oftest.git cd oftest cd tools/munger sudo make install From f6c42394097ff2c633cd6b0d57c460dd653c3f2c Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Mon, 12 Nov 2012 18:07:19 -0800 Subject: [PATCH 229/250] install: Add 'make codecheck' deps These total to only ~200KB. --- util/install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/install.sh b/util/install.sh index 5970ebd..7e2b54d 100755 --- a/util/install.sh +++ b/util/install.sh @@ -123,7 +123,8 @@ function kernel_clean { function mn_deps { echo "Installing Mininet dependencies" $install gcc make screen psmisc xterm ssh iperf iproute \ - python-setuptools python-networkx cgroup-bin ethtool help2man + python-setuptools python-networkx cgroup-bin ethtool help2man \ + pyflakes pylint pep8 if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then echo "Upgrading networkx to avoid deprecation warning" From 03d211f2a909dfa18ba6f19f2cc4cb31c67a5d4b Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Mon, 12 Nov 2012 18:08:59 -0800 Subject: [PATCH 230/250] codecheck: Make codecheck happy with a consistent version num --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f8168ea..3e75af3 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,9 @@ Mininet includes: `mn -c` -### New features in 2.0.0 +### New features in 2.0.0rc1 -Mininet 2.0.0 is a major upgrade and provides +Mininet 2.0.0rc1 is a major upgrade and provides a number of enhancements and new features, including: * "Mininet-HiFi" functionality: From 1052f8a0d462cb195220dd5b54895fc9435c4fa4 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 14:36:56 -0800 Subject: [PATCH 231/250] pep8: Fix E271/E272, spaces before/after keyword --- mininet/link.py | 2 +- mininet/util.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/link.py b/mininet/link.py index 2594a17..3930ead 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -297,7 +297,7 @@ class TCIntf( Intf ): ( [ '%s delay' % delay ] if delay is not None else [] ) + ( [ '%s jitter' % jitter ] if jitter is not None else [] ) + ( ['%d%% loss' % loss ] if loss is not None else [] ) + - ( [ 'ECN' ] if enable_ecn else [ 'RED' ] + ( [ 'ECN' ] if enable_ecn else [ 'RED' ] if enable_red else [] ) ) info( '(' + ' '.join( stuff ) + ') ' ) diff --git a/mininet/util.py b/mininet/util.py index 6418f52..fedea1a 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -252,7 +252,7 @@ def ipStr( ip ): def ipNum( w, x, y, z ): """Generate unsigned int from components of IP address returns: w << 24 | x << 16 | y << 8 | z""" - return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z + return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ): """Return IP address string from ints From 0bd5c6519c52ec5f13d8fde830511f016929477a Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 14:39:31 -0800 Subject: [PATCH 232/250] pep8: Fix E203 whitespace before punctutation --- examples/consoles.py | 2 +- examples/miniedit.py | 2 +- examples/multiping.py | 2 +- mininet/node.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/consoles.py b/examples/consoles.py index 2a0c195..698a242 100755 --- a/examples/consoles.py +++ b/examples/consoles.py @@ -252,7 +252,7 @@ class Graph( Frame ): x1 = x0 + self.barwidth y0 = self.gheight y1 = ( 1 - percent ) * self.gheight - c.create_rectangle( x0 , y0, x1, y1, fill='green' ) + c.create_rectangle( x0, y0, x1, y1, fill='green' ) self.xpos += 1 self.updateScrollRegions() self.graph.xview( 'moveto', '1.0' ) diff --git a/examples/miniedit.py b/examples/miniedit.py index 900b94a..2889533 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -289,7 +289,7 @@ class MiniEdit( Frame ): def deleteItem( self, item ): "Delete an item." # Don't delete while network is running - if self.buttons[ 'Select' ][ 'state' ] == 'disabled' : + if self.buttons[ 'Select' ][ 'state' ] == 'disabled': return # Delete from model if item in self.links: diff --git a/examples/multiping.py b/examples/multiping.py index ac8aed5..bb53526 100755 --- a/examples/multiping.py +++ b/examples/multiping.py @@ -18,7 +18,7 @@ from time import time def chunks( l, n ): "Divide list l into chunks of size n - thanks Stackoverflow" - return [ l[ i : i + n ] for i in range( 0, len( l ), n ) ] + return [ l[ i: i + n ] for i in range( 0, len( l ), n ) ] def startpings( host, targetips ): "Tell host to repeatedly ping targets" diff --git a/mininet/node.py b/mininet/node.py index afc58ce..b06c910 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -167,7 +167,7 @@ class Node( object ): if '\n' not in self.readbuf: return None pos = self.readbuf.find( '\n' ) - line = self.readbuf[ 0 : pos ] + line = self.readbuf[ 0: pos ] self.readbuf = self.readbuf[ pos + 1: ] return line From 7a5060478b8a779552c5a91be4296a27a19fde9f Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 14:46:58 -0800 Subject: [PATCH 233/250] pep8: Fix E711, comparisons to None should use 'is' or 'is not' Lengthy discussion of why this is a good thing (I didn't know) at SO: http://stackoverflow.com/questions/2209755/python-operation-vs-is-not --- mininet/log.py | 2 +- mininet/net.py | 2 +- mininet/util.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mininet/log.py b/mininet/log.py index 50d8cf7..3aee5e2 100644 --- a/mininet/log.py +++ b/mininet/log.py @@ -117,7 +117,7 @@ class MininetLogger( Logger, object ): Convenience function to support lowercase names. levelName: level name from LEVELS""" level = LOGLEVELDEFAULT - if levelname != None: + if levelname is not None: if levelname not in LEVELS: raise Exception( 'unknown levelname seen in setLogLevel' ) else: diff --git a/mininet/net.py b/mininet/net.py index a44b670..17c7133 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -421,7 +421,7 @@ class Mininet( object ): return (1, 0) r = r'(\d+) packets transmitted, (\d+) received' m = re.search( r, pingOutput ) - if m == None: + if m is None: error( '*** Error: could not parse ping output: %s\n' % pingOutput ) return (1, 0) diff --git a/mininet/util.py b/mininet/util.py index fedea1a..7e2df6a 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -46,7 +46,7 @@ def oldQuietRun( *cmd ): break out += data popen.poll() - if popen.returncode != None: + if popen.returncode is not None: break return out From 615ebb7afa0f2892e3f59eba7da12d14dd9603d0 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 14:56:49 -0800 Subject: [PATCH 234/250] pep8: Fix E125 continuation line does not distinguish itself from next logical line --- examples/consoles.py | 8 ++++---- examples/miniedit.py | 2 +- mininet/net.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/consoles.py b/examples/consoles.py index 698a242..35fa205 100755 --- a/examples/consoles.py +++ b/examples/consoles.py @@ -170,10 +170,10 @@ class Graph( Frame ): "Graph that we can add bars to over time." def __init__( self, parent=None, - bg = 'white', - gheight=200, gwidth=500, - barwidth=10, - ymax=3.5,): + bg = 'white', + gheight=200, gwidth=500, + barwidth=10, + ymax=3.5,): Frame.__init__( self, parent ) diff --git a/examples/miniedit.py b/examples/miniedit.py index 2889533..d90c685 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -475,7 +475,7 @@ class MiniEdit( Frame ): target = self.findItem( x, y ) 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 ): + or dest in source.links or source in dest.links ): self.releaseLink( event ) return # For now, don't allow hosts to be directly linked diff --git a/mininet/net.py b/mininet/net.py index 17c7133..9c39bb7 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -515,7 +515,7 @@ class Mininet( object ): servout += server.monitor() if l4Type == 'TCP': while 'Connected' not in client.cmd( - 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()): + 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()): output('waiting for iperf to start up...') sleep(.5) cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' + @@ -617,7 +617,7 @@ class MininetWithControlNet( Mininet ): # in the control network location. def configureRoutedControlNetwork( self, ip='192.168.123.1', - prefixLen=16 ): + prefixLen=16 ): """Configure a routed control network on controller and switches. For use with the user datapath only right now.""" controller = self.controllers[ 0 ] From 33d548b412e678cb48ce45da45602b20b277d855 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 15:01:11 -0800 Subject: [PATCH 235/250] pep8: Fix E121 continuation line indentation is not a multiple of four --- mininet/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index b06c910..eea195a 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -83,8 +83,8 @@ class Node( object ): # Make pylint happy ( self.shell, self.execed, self.pid, self.stdin, self.stdout, - self.lastPid, self.lastCmd, self.pollOut ) = ( - None, None, None, None, None, None, None, None ) + self.lastPid, self.lastCmd, self.pollOut ) = ( + None, None, None, None, None, None, None, None ) self.waiting = False self.readbuf = '' From c0095746af569c20b9a7f09c49b04c7102651871 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 16:08:26 -0800 Subject: [PATCH 236/250] pep8: Fix E121/126, continuation line indention --- examples/cpu.py | 2 +- mininet/node.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/cpu.py b/examples/cpu.py index 34426bc..2eebf20 100755 --- a/examples/cpu.py +++ b/examples/cpu.py @@ -46,7 +46,7 @@ def bwtest( cpuLimits, period_us=100000, seconds=5 ): server.cmd( 'iperf -s -p 5001 &' ) waitListening( client, server, 5001 ) result = client.cmd( 'iperf -yc -t %s -c %s' % ( - seconds, server.IP() ) ).split( ',' ) + seconds, server.IP() ) ).split( ',' ) bps = float( result[ -1 ] ) server.cmdPrint( 'kill %iperf' ) net.stop() diff --git a/mininet/node.py b/mininet/node.py index eea195a..bf5320c 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -515,7 +515,7 @@ class Node( object ): intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) for i in self.intfList() ] ) ) return '<%s %s: %s pid=%s> ' % ( - self.__class__.__name__, self.name, intfs, self.pid ) + self.__class__.__name__, self.name, intfs, self.pid ) def __str__( self ): "Abbreviated string representation" @@ -683,7 +683,7 @@ class CPULimitedHost( Host ): # We have to do this here after we've specified # cpus and mems errFail( 'cgclassify -g cpuset:/%s %s' % ( - self.name, self.pid ) ) + self.name, self.pid ) ) def config( self, cpu=None, cores=None, **params ): """cpu: desired overall system CPU fraction @@ -777,7 +777,7 @@ class Switch( Node ): intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) for i in self.intfList() ] ) ) return '<%s %s: %s pid=%s> ' % ( - self.__class__.__name__, self.name, intfs, self.pid ) + self.__class__.__name__, self.name, intfs, self.pid ) class UserSwitch( Switch ): "User-space switch." @@ -1021,8 +1021,8 @@ class Controller( Node ): def __repr__( self ): "More informative string representation" return '<%s %s: %s:%s pid=%s> ' % ( - self.__class__.__name__, self.name, - self.IP(), self.port, self.pid ) + self.__class__.__name__, self.name, + self.IP(), self.port, self.pid ) class OVSController( Controller ): From edf6003217dd31c4d3a30d2f25da7103fadf2bf9 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 16:59:10 -0800 Subject: [PATCH 237/250] pep8: fix E128 continuation line under-indented errors I wasn't sure this was worth fixing at first, but it does look more readable now. --- bin/mn | 59 +++++++++++++++++----------------- examples/consoles.py | 19 +++++------ examples/hwintf.py | 2 +- examples/linearbandwidth.py | 2 +- examples/miniedit.py | 18 +++++------ examples/multiping.py | 2 +- examples/multipoll.py | 2 +- examples/treeping64.py | 6 ++-- mininet/cli.py | 6 ++-- mininet/link.py | 17 +++++----- mininet/log.py | 10 +++--- mininet/moduledeps.py | 8 ++--- mininet/net.py | 16 +++++----- mininet/node.py | 63 +++++++++++++++++++------------------ mininet/util.py | 4 +-- 15 files changed, 118 insertions(+), 116 deletions(-) diff --git a/bin/mn b/bin/mn index 142208c..27cb95d 100755 --- a/bin/mn +++ b/bin/mn @@ -37,14 +37,14 @@ from mininet.util import buildTopo # built in topologies, created only when run TOPODEF = 'minimal' TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), - 'linear': LinearTopo, - 'reversed': SingleSwitchReversedTopo, - 'single': SingleSwitchTopo, - 'tree': TreeTopo } + 'linear': LinearTopo, + 'reversed': SingleSwitchReversedTopo, + 'single': SingleSwitchTopo, + 'tree': TreeTopo } SWITCHDEF = 'ovsk' SWITCHES = { 'user': UserSwitch, - 'ovsk': OVSKernelSwitch, + 'ovsk': OVSKernelSwitch, 'ovsl': OVSLegacyKernelSwitch } HOSTDEF = 'proc' @@ -66,10 +66,13 @@ LINKS = { 'default': Link, # optional tests to run TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', - 'none' ] + 'none' ] -ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair', - 'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' } +ALTSPELLING = { 'pingall': 'pingAll', + 'pingpair': 'pingPair', + 'iperfudp': 'iperfUdp', + 'iperfUDP': 'iperfUdp', + 'prefixlen': 'prefixLen' } def addDictOption( opts, choicesDict, default, name, helpStr=None ): @@ -81,14 +84,14 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ): help: string""" if default not in choicesDict: raise Exception( 'Invalid default %s for choices dict: %s' % - ( default, name ) ) + ( default, name ) ) if not helpStr: helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + - '[,param=value...]' ) + '[,param=value...]' ) opts.add_option( '--' + name, - type='string', - default = default, - help = helpStr ) + type='string', + default = default, + help = helpStr ) def version( *_args ): @@ -158,36 +161,36 @@ class MininetRunner( object ): addDictOption( opts, TOPOS, TOPODEF, 'topo' ) opts.add_option( '--clean', '-c', action='store_true', - default=False, help='clean and exit' ) + default=False, help='clean and exit' ) opts.add_option( '--custom', type='string', default=None, - help='read custom topo and node params from .py file' ) + help='read custom topo and node params from .py file' ) opts.add_option( '--test', type='choice', choices=TESTS, - default=TESTS[ 0 ], - help='|'.join( TESTS ) ) + default=TESTS[ 0 ], + help='|'.join( TESTS ) ) opts.add_option( '--xterms', '-x', action='store_true', - default=False, help='spawn xterms for each node' ) + default=False, help='spawn xterms for each node' ) opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8', help='base IP address for hosts' ) opts.add_option( '--mac', action='store_true', - default=False, help='automatically set host MACs' ) + default=False, help='automatically set host MACs' ) opts.add_option( '--arp', action='store_true', - default=False, help='set all-pairs ARP entries' ) + default=False, help='set all-pairs ARP entries' ) opts.add_option( '--verbosity', '-v', type='choice', - choices=LEVELS.keys(), default = 'info', - help = '|'.join( LEVELS.keys() ) ) + choices=LEVELS.keys(), default = 'info', + help = '|'.join( LEVELS.keys() ) ) opts.add_option( '--innamespace', action='store_true', - default=False, help='sw and ctrl in namespace?' ) + default=False, help='sw and ctrl in namespace?' ) opts.add_option( '--listenport', type='int', default=6635, help='base port for passive switch listening' ) opts.add_option( '--nolistenport', action='store_true', - default=False, help="don't use passive listening port") + default=False, help="don't use passive listening port") opts.add_option( '--pre', type='string', default=None, - help='CLI script to run before tests' ) + help='CLI script to run before tests' ) opts.add_option( '--post', type='string', default=None, - help='CLI script to run after tests' ) + help='CLI script to run after tests' ) opts.add_option( '--prefixlen', type='int', default=8, - help='prefix length (e.g. /8) for automatic ' - 'network configuration' ) + help='prefix length (e.g. /8) for automatic ' + 'network configuration' ) opts.add_option( '--pin', action='store_true', default=False, help="pin hosts to CPU cores " "(requires --host cfs or --host rt)" ) diff --git a/examples/consoles.py b/examples/consoles.py index 35fa205..ea2e28d 100755 --- a/examples/consoles.py +++ b/examples/consoles.py @@ -74,11 +74,11 @@ class Console( Frame ): "Pop up a new terminal window for a node." net.terms += makeTerms( [ node ], title ) label = Button( self, text=self.node.name, command=newTerm, - **self.buttonStyle ) + **self.buttonStyle ) label.pack( side='top', fill='x' ) text = Text( self, wrap='word', **self.textStyle ) ybar = Scrollbar( self, orient='vertical', width=7, - command=text.yview ) + command=text.yview ) text.configure( yscrollcommand=ybar.set ) text.pack( side='left', expand=True, fill='both' ) ybar.pack( side='right', fill='y' ) @@ -95,7 +95,7 @@ class Console( Frame ): # way to trigger a file event handler from Tk's # event loop! self.tk.createfilehandler( self.node.stdout, READABLE, - self.handleReadable ) + self.handleReadable ) # We're not a terminal (yet?), so we ignore the following # control characters other than [\b\n\r] @@ -169,11 +169,8 @@ class Graph( Frame ): "Graph that we can add bars to over time." - def __init__( self, parent=None, - bg = 'white', - gheight=200, gwidth=500, - barwidth=10, - ymax=3.5,): + def __init__( self, parent=None, bg = 'white', gheight=200, gwidth=500, + barwidth=10, ymax=3.5,): Frame.__init__( self, parent ) @@ -195,7 +192,7 @@ class Graph( Frame ): width = 25 ymax = self.ymax scale = Canvas( self, width=width, height=height, - background=self.bg ) + background=self.bg ) opts = { 'fill': 'red' } # Draw scale line scale.create_line( width - 1, height, width - 1, 0, **opts ) @@ -211,7 +208,7 @@ class Graph( Frame ): ofs = 20 height = self.gheight + ofs self.graph.configure( scrollregion=( 0, -ofs, - self.xpos * self.barwidth, height ) ) + self.xpos * self.barwidth, height ) ) self.scale.configure( scrollregion=( 0, -ofs, 0, height ) ) def yview( self, *args ): @@ -231,7 +228,7 @@ class Graph( Frame ): xbar = Scrollbar( self, orient='horizontal', command=graph.xview ) ybar = Scrollbar( self, orient='vertical', command=self.yview ) graph.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set, - scrollregion=(0, 0, width, height ) ) + scrollregion=(0, 0, width, height ) ) scale.configure( yscrollcommand=ybar.set ) # Layout diff --git a/examples/hwintf.py b/examples/hwintf.py index 71c72a3..d8d3fe2 100755 --- a/examples/hwintf.py +++ b/examples/hwintf.py @@ -21,7 +21,7 @@ def checkIntf( intf ): ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) ) if ips: error( 'Error:', intf, 'has an IP address,' - 'and is probably in use!\n' ) + 'and is probably in use!\n' ) exit( 1 ) if __name__ == '__main__': diff --git a/examples/linearbandwidth.py b/examples/linearbandwidth.py index c361045..3fd06c7 100755 --- a/examples/linearbandwidth.py +++ b/examples/linearbandwidth.py @@ -68,7 +68,7 @@ def linearBandwidthTest( lengths ): hostCount = switchCount + 1 switches = { 'reference user': UserSwitch, - 'Open vSwitch kernel': OVSKernelSwitch } + 'Open vSwitch kernel': OVSKernelSwitch } topo = LinearTestTopo( hostCount ) diff --git a/examples/miniedit.py b/examples/miniedit.py index d90c685..89c97e6 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -112,7 +112,7 @@ class MiniEdit( Frame ): 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) + font=font) appMenu.add_separator() appMenu.add_command( label='Quit', command=self.quit, font=font ) @@ -127,7 +127,7 @@ class MiniEdit( Frame ): 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 ) ) + command=lambda: self.deleteSelection( None ) ) runMenu = Menu( mbar, tearoff=False ) mbar.add_cascade( label="Run", font=font, menu=runMenu ) @@ -143,7 +143,7 @@ class MiniEdit( Frame ): f = Frame( self ) canvas = Canvas( f, width=self.cwidth, height=self.cheight, - bg=self.bg ) + bg=self.bg ) # Scroll bars xbar = Scrollbar( f, orient='horizontal', command=canvas.xview ) @@ -177,7 +177,7 @@ class MiniEdit( Frame ): bbox = self.canvas.bbox( 'all' ) if bbox is not None: self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], - bbox[ 3 ] ) ) + bbox[ 3 ] ) ) def canvasx( self, x_root ): "Convert root x coordinate to canvas coordinate." @@ -223,7 +223,7 @@ class MiniEdit( Frame ): for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]: doCmd = getattr( self, 'do' + cmd ) b = Button( toolbar, text=cmd, font=self.smallFont, - fg=color, command=doCmd ) + fg=color, command=doCmd ) b.pack( fill='x', side='bottom' ) return toolbar @@ -308,7 +308,7 @@ class MiniEdit( Frame ): def nodeIcon( self, node, name ): "Create a new node icon." icon = Button( self.canvas, image=self.images[ node ], - text=name, compound='top' ) + text=name, compound='top' ) # Unfortunately bindtags wants a tuple bindtags = [ str( self.nodeBindings ) ] bindtags += list( icon.bindtags() ) @@ -322,8 +322,8 @@ class MiniEdit( Frame ): self.nodeCount += 1 name = self.nodePrefixes[ node ] + str( self.nodeCount ) icon = self.nodeIcon( node, name ) - item = self.canvas.create_window( x, y, anchor='c', - window=icon, tags=node ) + item = self.canvas.create_window( x, y, anchor='c', window=icon, + tags=node ) self.widgetToItem[ icon ] = item self.itemToWidget[ item ] = icon self.selectItem( item ) @@ -437,7 +437,7 @@ class MiniEdit( Frame ): 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' ) + fill='blue', tag='link' ) self.linkx, self.linky = x, y self.linkWidget = w self.linkItem = item diff --git a/examples/multiping.py b/examples/multiping.py index bb53526..3bd231c 100755 --- a/examples/multiping.py +++ b/examples/multiping.py @@ -40,7 +40,7 @@ def startpings( host, targetips ): 'done &' ) print ( '*** Host %s (%s) will be pinging ips: %s' % - ( host.name, host.IP(), targetips ) ) + ( host.name, host.IP(), targetips ) ) host.cmd( cmd ) diff --git a/examples/multipoll.py b/examples/multipoll.py index f670827..aef1b10 100755 --- a/examples/multipoll.py +++ b/examples/multipoll.py @@ -19,7 +19,7 @@ def monitorFiles( outfiles, seconds, timeoutms ): tails, fdToFile, fdToHost = {}, {}, {} for h, outfile in outfiles.iteritems(): tail = Popen( [ 'tail', '-f', outfile ], - stdout=PIPE, stderr=devnull ) + stdout=PIPE, stderr=devnull ) fd = tail.stdout.fileno() tails[ h ] = tail fdToFile[ fd ] = tail.stdout diff --git a/examples/treeping64.py b/examples/treeping64.py index b0737da..8385a33 100755 --- a/examples/treeping64.py +++ b/examples/treeping64.py @@ -10,9 +10,9 @@ def treePing64(): "Run ping test on 64-node tree networks." results = {} - switches = { # 'reference kernel': KernelSwitch, - 'reference user': UserSwitch, - 'Open vSwitch kernel': OVSKernelSwitch } + switches = { # 'reference kernel': KernelSwitch, + 'reference user': UserSwitch, + 'Open vSwitch kernel': OVSKernelSwitch } for name in switches: print "*** Testing", name, "datapath" diff --git a/mininet/cli.py b/mininet/cli.py index ce59853..f54d5c3 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -192,7 +192,7 @@ class CLI( Cmd ): "List interfaces." for node in self.nodelist: output( '%s: %s\n' % - ( node.name, ','.join( node.intfNames() ) ) ) + ( node.name, ','.join( node.intfNames() ) ) ) def do_dump( self, _line ): "Dump node info." @@ -303,8 +303,8 @@ class CLI( Cmd ): node = self.nodemap[ first ] # Substitute IP addresses for node names in command rest = [ self.nodemap[ arg ].IP() - if arg in self.nodemap else arg - for arg in rest ] + if arg in self.nodemap else arg + for arg in rest ] rest = ' '.join( rest ) # Run cmd on node: builtin = isShellBuiltin( first ) diff --git a/mininet/link.py b/mininet/link.py index 3930ead..43cd207 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -202,13 +202,13 @@ class TCIntf( Intf ): elif use_tbf: if latency_ms is None: latency_ms = 15 * 8 / bw - cmds += ['%s qdisc add dev %s root handle 1: tbf ' + - 'rate %fMbit burst 15000 latency %fms' % - ( bw, latency_ms ) ] + cmds += [ '%s qdisc add dev %s root handle 1: tbf ' + + 'rate %fMbit burst 15000 latency %fms' % + ( bw, latency_ms ) ] else: cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1', - '%s class add dev %s parent 1:0 classid 1:1 htb ' + - 'rate %fMbit burst 15k' % bw ] + '%s class add dev %s parent 1:0 classid 1:1 htb ' + + 'rate %fMbit burst 15k' % bw ] parent = ' parent 1:1 ' # ECN or RED @@ -282,9 +282,10 @@ class TCIntf( Intf ): # Bandwidth limits via various methods bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup, - use_hfsc=use_hfsc, use_tbf=use_tbf, - latency_ms=latency_ms, enable_ecn=enable_ecn, - enable_red=enable_red ) + use_hfsc=use_hfsc, use_tbf=use_tbf, + latency_ms=latency_ms, + enable_ecn=enable_ecn, + enable_red=enable_red ) cmds += bwcmds # Delay/jitter/loss/max_queue_size using netem diff --git a/mininet/log.py b/mininet/log.py index 3aee5e2..cd00821 100644 --- a/mininet/log.py +++ b/mininet/log.py @@ -11,11 +11,11 @@ import types OUTPUT = 25 LEVELS = { 'debug': logging.DEBUG, - 'info': logging.INFO, - 'output': OUTPUT, - 'warning': logging.WARNING, - 'error': logging.ERROR, - 'critical': logging.CRITICAL } + 'info': logging.INFO, + 'output': OUTPUT, + 'warning': logging.WARNING, + 'error': logging.ERROR, + 'critical': logging.CRITICAL } # change this to logging.INFO to get printouts when running unit tests LOGLEVELDEFAULT = OUTPUT diff --git a/mininet/moduledeps.py b/mininet/moduledeps.py index 584d6c7..862c1f6 100644 --- a/mininet/moduledeps.py +++ b/mininet/moduledeps.py @@ -48,8 +48,8 @@ def moduleDeps( subtract=None, add=None ): modprobeOutput = modprobe( mod ) if modprobeOutput: error( 'Error inserting ' + mod + - ' - is it installed and available via modprobe?\n' + - 'Error was: "%s"\n' % modprobeOutput ) + ' - is it installed and available via modprobe?\n' + + 'Error was: "%s"\n' % modprobeOutput ) if mod not in lsmod(): error( 'Failed to insert ' + mod + ' - quitting.\n' ) exit( 1 ) @@ -63,6 +63,6 @@ def pathCheck( *args, **kwargs ): for arg in args: if not quietRun( 'which ' + arg ): error( 'Cannot find required executable %s.\n' % arg + - 'Please make sure that %s is installed ' % moduleName + - 'and available in your $PATH:\n(%s)\n' % environ[ 'PATH' ] ) + 'Please make sure that %s is installed ' % moduleName + + 'and available in your $PATH:\n(%s)\n' % environ[ 'PATH' ] ) exit( 1 ) diff --git a/mininet/net.py b/mininet/net.py index 9c39bb7..ccce862 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -107,11 +107,11 @@ 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, - build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', - inNamespace=False, - autoSetMacs=False, autoStaticArp=False, autoPinCpus=False, - listenPort=None ): + controller=Controller, link=Link, intf=Intf, + build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', + inNamespace=False, + autoSetMacs=False, autoStaticArp=False, autoPinCpus=False, + listenPort=None ): """Create Mininet object. topo: Topo (topology) object or None switch: default Switch class @@ -307,7 +307,7 @@ class Mininet( object ): def configureControlNetwork( self ): "Control net config hook: override in subclass" raise Exception( 'configureControlNetwork: ' - 'should be overriden in subclass', self ) + 'should be overriden in subclass', self ) def build( self ): "Build mininet." @@ -519,7 +519,7 @@ class Mininet( object ): output('waiting for iperf to start up...') sleep(.5) cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' + - bwArgs ) + bwArgs ) debug( 'Client output: %s\n' % cliout ) server.sendInt() servout += server.waitOutput() @@ -617,7 +617,7 @@ class MininetWithControlNet( Mininet ): # in the control network location. def configureRoutedControlNetwork( self, ip='192.168.123.1', - prefixLen=16 ): + prefixLen=16 ): """Configure a routed control network on controller and switches. For use with the user datapath only right now.""" controller = self.controllers[ 0 ] diff --git a/mininet/node.py b/mininet/node.py index bf5320c..a8b7e30 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -52,7 +52,7 @@ from subprocess import Popen, PIPE, STDOUT from mininet.log import info, error, warn, debug from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin, - numCores, retry, mountCgroups ) + numCores, retry, mountCgroups ) from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN from mininet.link import Link, Intf, TCIntf @@ -120,7 +120,7 @@ class Node( object ): # bash -m: enable job control cmd = [ 'mnexec', opts, 'bash', '-m' ] self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, - close_fds=True ) + close_fds=True ) self.stdin = self.shell.stdin self.stdout = self.shell.stdout self.pid = self.shell.pid @@ -355,7 +355,7 @@ class Node( object ): return self.intfs[ min( ports ) ] else: warn( '*** defaultIntf: warning:', self.name, - 'has no interfaces\n' ) + 'has no interfaces\n' ) def intf( self, intf='' ): """Return our interface object with given name, @@ -513,7 +513,7 @@ class Node( object ): def __repr__( self ): "More informative string representation" intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) - for i in self.intfList() ] ) ) + for i in self.intfList() ] ) ) return '<%s %s: %s pid=%s> ' % ( self.__class__.__name__, self.name, intfs, self.pid ) @@ -775,7 +775,7 @@ class Switch( Node ): def __repr__( self ): "More informative string representation" intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) - for i in self.intfList() ] ) ) + for i in self.intfList() ] ) ) return '<%s %s: %s pid=%s> ' % ( self.__class__.__name__, self.name, intfs, self.pid ) @@ -789,7 +789,8 @@ class UserSwitch( Switch ): name: name for the switch""" Switch.__init__( self, name, **kwargs ) pathCheck( 'ofdatapath', 'ofprotocol', - moduleName='the OpenFlow reference user switch (openflow.org)' ) + moduleName='the OpenFlow reference user switch' + + '(openflow.org)' ) if self.listenPort: self.opts += ' --listen=ptcp:%i ' % self.listenPort @@ -812,18 +813,18 @@ class UserSwitch( Switch ): controllers: list of controller objects""" # Add controllers clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) - for c in controllers ] ) + for c in controllers ] ) ofdlog = '/tmp/' + self.name + '-ofd.log' ofplog = '/tmp/' + self.name + '-ofp.log' self.cmd( 'ifconfig lo up' ) intfs = [ str( i ) for i in self.intfList() if not i.IP() ] self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + - ' punix:/tmp/' + self.name + ' -d ' + self.dpid + - ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) + ' punix:/tmp/' + self.name + ' -d ' + self.dpid + + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) self.cmd( 'ofprotocol unix:/tmp/' + self.name + - ' ' + clist + - ' --fail=closed ' + self.opts + - ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) + ' ' + clist + + ' --fail=closed ' + self.opts + + ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) def stop( self ): "Stop OpenFlow reference user datapath." @@ -846,14 +847,14 @@ class OVSLegacyKernelSwitch( Switch ): self.intf = self.dp if self.inNamespace: error( "OVSKernelSwitch currently only works" - " in the root namespace.\n" ) + " in the root namespace.\n" ) exit( 1 ) @classmethod def setup( cls ): "Ensure any dependencies are loaded; if not, try to load them." pathCheck( 'ovs-dpctl', 'ovs-openflowd', - moduleName='Open vSwitch (openvswitch.org)') + moduleName='Open vSwitch (openvswitch.org)') moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) def start( self, controllers ): @@ -868,12 +869,12 @@ class OVSLegacyKernelSwitch( Switch ): self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) ) # Run protocol daemon clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) - for c in controllers ] ) + for c in controllers ] ) self.cmd( 'ovs-openflowd ' + self.dp + - ' ' + clist + - ' --fail=secure ' + self.opts + - ' --datapath-id=' + self.dpid + - ' 1>' + ofplog + ' 2>' + ofplog + '&' ) + ' ' + clist + + ' --fail=secure ' + self.opts + + ' --datapath-id=' + self.dpid + + ' 1>' + ofplog + ' 2>' + ofplog + '&' ) self.execed = False def stop( self ): @@ -897,7 +898,7 @@ class OVSSwitch( Switch ): def setup( cls ): "Make sure Open vSwitch is installed and working" pathCheck( 'ovs-vsctl', - moduleName='Open vSwitch (openvswitch.org)') + moduleName='Open vSwitch (openvswitch.org)') # This should no longer be needed, and it breaks # with OVS 1.7 which has renamed the kernel module: # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) @@ -970,15 +971,15 @@ class Controller( Node ): OpenFlow controller.""" def __init__( self, name, inNamespace=False, command='controller', - cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1", - port=6633, **params ): + cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1", + port=6633, **params ): self.command = command self.cargs = cargs self.cdir = cdir self.ip = ip self.port = port Node.__init__( self, name, inNamespace=inNamespace, - ip=ip, **params ) + ip=ip, **params ) self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary self.checkListening() @@ -1002,7 +1003,7 @@ class Controller( Node ): if self.cdir is not None: self.cmd( 'cd ' + self.cdir ) self.cmd( self.command + ' ' + self.cargs % self.port + - ' 1>' + cout + ' 2>' + cout + '&' ) + ' 1>' + cout + ' 2>' + cout + '&' ) self.execed = False def stop( self ): @@ -1050,24 +1051,24 @@ class NOX( Controller ): noxCoreDir = os.environ[ 'NOX_CORE_DIR' ] Controller.__init__( self, name, - command=noxCoreDir + '/nox_core', - cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' + - ' '.join( noxArgs ), - cdir=noxCoreDir, **kwargs ) + command=noxCoreDir + '/nox_core', + cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' + + ' '.join( noxArgs ), + cdir=noxCoreDir, + **kwargs ) class RemoteController( Controller ): "Controller running outside of Mininet's control." def __init__( self, name, ip='127.0.0.1', - port=6633, **kwargs): + port=6633, **kwargs): """Init. name: name to give controller ip: the IP address where the remote controller is listening port: the port where the remote controller is listening""" - Controller.__init__( self, name, ip=ip, port=port, - **kwargs ) + Controller.__init__( self, name, ip=ip, port=port, **kwargs ) def start( self ): "Overridden to do nothing." diff --git a/mininet/util.py b/mininet/util.py index 7e2df6a..1fd9cae 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -181,7 +181,7 @@ def moveIntfNoRetry( intf, node, printError=False ): if not ( ' %s:' % intf ) in links: if printError: error( '*** Error: moveIntf: ' + intf + - ' not successfully moved to ' + node.name + '\n' ) + ' not successfully moved to ' + node.name + '\n' ) return False return True @@ -431,7 +431,7 @@ def customConstructor( constructors, argStr ): if not constructor: raise Exception( "error: %s is unknown - please specify one of %s" % - ( cname, constructors.keys() ) ) + ( cname, constructors.keys() ) ) def customized( name, *args, **params ): "Customized constructor, useful for Node, Link, and other classes" From 2e089b5e4ac3bcc9558105e039d85aaceffab6dd Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 17:17:51 -0800 Subject: [PATCH 238/250] pep8: Fix E127 continuation line over-indented There are a bunch of these remaining, but I don't think the right course is to 'fix' all of them to make pep8 happy, but instead to either change the test in pep8 to consider that a continuation line may itself be continued halfway, OR, to change the code in these lines to be more readable by removing the need for all those nested continuations. Personally, I find multiply-broken lines (aka nested continuations) really hard to read. --- examples/simpleperf.py | 4 ++-- examples/treeping64.py | 6 +++--- mininet/link.py | 8 ++++---- mininet/net.py | 2 +- mininet/node.py | 6 +++--- mininet/topo.py | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/simpleperf.py b/examples/simpleperf.py index 76de0bd..3dad32c 100644 --- a/examples/simpleperf.py +++ b/examples/simpleperf.py @@ -19,10 +19,10 @@ class SingleSwitchTopo(Topo): for h in range(n): # Each host gets 50%/n of system CPU host = self.addHost('h%s' % (h + 1), - cpu=.5 / n) + cpu=.5 / n) # 10 Mbps, 5ms delay, 10% loss self.addLink(host, switch, - bw=10, delay='5ms', loss=10, use_htb=True) + bw=10, delay='5ms', loss=10, use_htb=True) def perfTest(): "Create network and run simple performance test" diff --git a/examples/treeping64.py b/examples/treeping64.py index 8385a33..ba60f1b 100755 --- a/examples/treeping64.py +++ b/examples/treeping64.py @@ -10,9 +10,9 @@ def treePing64(): "Run ping test on 64-node tree networks." results = {} - switches = { # 'reference kernel': KernelSwitch, - 'reference user': UserSwitch, - 'Open vSwitch kernel': OVSKernelSwitch } + switches = { # 'reference kernel': KernelSwitch, + 'reference user': UserSwitch, + 'Open vSwitch kernel': OVSKernelSwitch } for name in switches: print "*** Testing", name, "datapath" diff --git a/mininet/link.py b/mininet/link.py index 43cd207..21e18ba 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -246,11 +246,11 @@ class TCIntf( Intf ): '%s ' % jitter if jitter is not None else '', 'loss %d ' % loss if loss is not None else '', 'limit %d' % max_queue_size if max_queue_size is not None - else '' ) + else '' ) if netemargs: cmds = [ '%s qdisc add dev %s ' + parent + ' handle 10: netem ' + - netemargs ] + netemargs ] return cmds def tc( self, cmd, tc='tc' ): @@ -290,8 +290,8 @@ class TCIntf( Intf ): # Delay/jitter/loss/max_queue_size using netem cmds += self.delayCmds( delay=delay, jitter=jitter, loss=loss, - max_queue_size=max_queue_size, - parent=parent ) + max_queue_size=max_queue_size, + parent=parent ) # Ugly but functional: display configuration info stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + diff --git a/mininet/net.py b/mininet/net.py index ccce862..6a091c3 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -423,7 +423,7 @@ class Mininet( object ): m = re.search( r, pingOutput ) if m is None: error( '*** Error: could not parse ping output: %s\n' % - pingOutput ) + pingOutput ) return (1, 0) sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) return sent, received diff --git a/mininet/node.py b/mininet/node.py index a8b7e30..add10f3 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -770,7 +770,7 @@ class Switch( Node ): return Node.sendCmd( self, *cmd, **kwargs ) else: error( '*** Error: %s has execed and cannot accept commands' % - self.name ) + self.name ) def __repr__( self ): "More informative string representation" @@ -1053,7 +1053,7 @@ class NOX( Controller ): Controller.__init__( self, name, command=noxCoreDir + '/nox_core', cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' + - ' '.join( noxArgs ), + ' '.join( noxArgs ), cdir=noxCoreDir, **kwargs ) @@ -1084,4 +1084,4 @@ class RemoteController( Controller ): ( self.ip, self.port ) ) if 'Unable' in listening: warn( "Unable to contact the remote controller" - " at %s:%d\n" % ( self.ip, self.port ) ) + " at %s:%d\n" % ( self.ip, self.port ) ) diff --git a/mininet/topo.py b/mininet/topo.py index 27df013..fff9604 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -63,7 +63,7 @@ class Topo(object): return result def addLink(self, node1, node2, port1=None, port2=None, - **opts): + **opts): """node1, node2: nodes to link together port1, port2: ports (optional) opts: link options (optional) @@ -205,7 +205,7 @@ class SingleSwitchReversedTopo(Topo): for h in irange(1, k): host = self.addHost('h%s' % h) self.addLink(host, switch, - port1=0, port2=(k - h + 1)) + port1=0, port2=(k - h + 1)) class LinearTopo(Topo): "Linear topology of k switches, with one host per switch." From 9330a33fe57fe92dc40f94780c471f6b781ae843 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 17:21:36 -0800 Subject: [PATCH 239/250] pep8: Fix E501 line-too-long errors incurred fixing other pep8 stuff :-) --- bin/mn | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/mn b/bin/mn index 27cb95d..80def32 100755 --- a/bin/mn +++ b/bin/mn @@ -163,7 +163,8 @@ class MininetRunner( object ): opts.add_option( '--clean', '-c', action='store_true', default=False, help='clean and exit' ) opts.add_option( '--custom', type='string', default=None, - help='read custom topo and node params from .py file' ) + help='read custom topo and node params from .py' + + 'file' ) opts.add_option( '--test', type='choice', choices=TESTS, default=TESTS[ 0 ], help='|'.join( TESTS ) ) @@ -183,7 +184,8 @@ class MininetRunner( object ): opts.add_option( '--listenport', type='int', default=6635, help='base port for passive switch listening' ) opts.add_option( '--nolistenport', action='store_true', - default=False, help="don't use passive listening port") + default=False, help="don't use passive listening " + + "port") opts.add_option( '--pre', type='string', default=None, help='CLI script to run before tests' ) opts.add_option( '--post', type='string', default=None, From d40003e0cd79cb873136e1f1a1c44d4b36e77435 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 18:05:10 -0800 Subject: [PATCH 240/250] examples: Make simpleperf.py executable --- examples/simpleperf.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 examples/simpleperf.py diff --git a/examples/simpleperf.py b/examples/simpleperf.py old mode 100644 new mode 100755 From 2eb0593cd27a7a2ddcd1e83eca5dd6a0b6476744 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 18:06:19 -0800 Subject: [PATCH 241/250] examples/cpu: Fix typo, note existence in README --- examples/cpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cpu.py b/examples/cpu.py index 2eebf20..6dfc936 100755 --- a/examples/cpu.py +++ b/examples/cpu.py @@ -1,7 +1,7 @@ #!/usr/bin/python """ -cpu.py: test iperf bandwidth for varying cpu limtis +cpu.py: test iperf bandwidth for varying cpu limits """ from mininet.net import Mininet From 36c9b040ca001c3bd696a48e55ae81c10c69c4c4 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 18:33:10 -0800 Subject: [PATCH 242/250] examples: Add new tests to README --- examples/README | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/examples/README b/examples/README index b6c199b..dd71bea 100644 --- a/examples/README +++ b/examples/README @@ -21,11 +21,24 @@ controllers.py: This example creates a network and adds multiple controllers to it. +cpu.py: + +This example tests iperf bandwidth for varying CPU limits. + emptynet.py: This example demonstrates creating an empty network (i.e. with no topology object) and adding nodes to it. +hwintf.py: + +This example shows how to add an interface (for example a real +hardware interface) to a network after the network is created. + +limit.py: + +This example shows how to use link and CPU limits. + linearbandwidth.py: This example shows how to create a custom topology programatically @@ -48,6 +61,11 @@ multitest.py: This example creates a network and runs multiple tests on it. +popen.py: + +This example monitors a number of hosts using host.popen() and +pmonitor(). + popenpoll.py: This example demonstrates monitoring output from multiple hosts using From 8a1264e266736d2c7dadd5a2a0e1e6ecf487e405 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 20:53:13 -0800 Subject: [PATCH 243/250] Fix 'cgroups not mounted' error in U12.10 'mount' shows something slightly different in Ubuntu 12.10: cgroup on /sys/fs/cgroup type tmpfs (rw,uid=0,gid=0,mode=0755) Note the lack of a plural on first word cgroup, which has changed. Still mounted at /sys/fs/cgroup, so check for both possibilities when instantiated CPU-limited hosts. --- mininet/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mininet/util.py b/mininet/util.py index 1fd9cae..0c7c870 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -361,7 +361,8 @@ def mountCgroups(): mounts = quietRun( 'mount' ) cgdir = '/sys/fs/cgroup' csdir = cgdir + '/cpuset' - if 'cgroups on %s' % cgdir not in mounts: + if ('cgroup on %s' % cgdir not in mounts and + 'cgroups on %s' % cgdir not in mounts): raise Exception( "cgroups not mounted on " + cgdir ) if 'cpuset on %s' % csdir not in mounts: errRun( 'mkdir -p ' + csdir ) From bcfb3009c0b37750b6491dc75f3c529ad7c87b23 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 21:32:41 -0800 Subject: [PATCH 244/250] small refactor: put function to ensure root in util Two benefits: - One place to change if in the future, a more granular method of root access is used (like the BigSwitch patch). - Makes this reusable by stuff like examples/baresshd.py that use the low-level Mininet API. --- mininet/net.py | 9 ++------- mininet/util.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 6a091c3..8b6cf94 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -96,7 +96,7 @@ from mininet.cli import CLI from mininet.log import info, error, debug, output from mininet.node import Host, OVSKernelSwitch, Controller from mininet.link import Link, Intf -from mininet.util import quietRun, fixLimits, numCores +from mininet.util import quietRun, fixLimits, numCores, ensureRoot from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd from mininet.term import cleanUpScreens, makeTerms @@ -571,12 +571,7 @@ class Mininet( object ): "Initialize Mininet" if cls.inited: return - if os.getuid() != 0: - # Note: this script must be run as root - # Probably we should only sudo when we need - # to as per Big Switch's patch - print "*** Mininet must run as root." - exit( 1 ) + ensureRoot() fixLimits() cls.inited = True diff --git a/mininet/util.py b/mininet/util.py index 0c7c870..3461a1d 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -9,6 +9,7 @@ from subprocess import call, check_call, Popen, PIPE, STDOUT import re from fcntl import fcntl, F_GETFL, F_SETFL from os import O_NONBLOCK +import os # Command execution support @@ -456,3 +457,13 @@ def buildTopo( topos, topoStr ): if topo not in topos: raise Exception( 'Invalid topo name %s' % topo ) return topos[ topo ]( *args, **kwargs ) + +def ensureRoot(): + """Ensure that we are running as root. + + Probably we should only sudo when needed as per Big Switch's patch. + """ + if os.getuid() != 0: + print "*** Mininet must run as root." + exit( 1 ) + return From 12fea0f6d54b410e4fad4de6433c0a8daaebeee2 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 21:33:44 -0800 Subject: [PATCH 245/250] examples/baresshd: ensure root permissions Prevent idiots like me from getting confused by non-obvious 'broken pipe' errors when they forget to put 'sudo' in front :-) --- examples/baresshd.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/baresshd.py b/examples/baresshd.py index 3b616d1..a714edb 100755 --- a/examples/baresshd.py +++ b/examples/baresshd.py @@ -3,6 +3,9 @@ "This example doesn't use OpenFlow, but attempts to run sshd in a namespace." from mininet.node import Host +from mininet.util import ensureRoot + +ensureRoot() print "*** Creating nodes" h1 = Host( 'h1' ) From bf208cdeb6600f6282651e1b60a2115176cf989c Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 22:44:47 -0800 Subject: [PATCH 246/250] Fix SSHD example by generalizing input intf args A number of functions in node.py look like this: return self.intf( intf ). Previously, self.intf(...) in Node would expect a string name for an interface and return None if an object was passed in instead of a string name. Now, be more permissive and assume that objects passed in are for Intf objects. This makes all such functions in node.py handle more flexible input args, either name or actual Intf object. An alternative and equally valid approach would be to raise an Exception whenever a non-string, non-falsy value was passed in to Node.intf(), and to modify the code in at least one place - examples/sshd.py - to pass the interface name, rather than the interface object. Also fix input args for examples/scratchnetuser.py - the interface name was being passed in as the prefix len, which makes no sense. --- examples/scratchnetuser.py | 8 ++++---- mininet/node.py | 14 ++++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/scratchnetuser.py b/examples/scratchnetuser.py index 4b8b9fd..ccd20e9 100755 --- a/examples/scratchnetuser.py +++ b/examples/scratchnetuser.py @@ -38,12 +38,12 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ): h1intf, sintf2 = linkIntfs( h1, switch ) info( '*** Configuring control network\n' ) - controller.setIP( '10.0.123.1/24', cintf ) - switch.setIP( '10.0.123.2/24', sintf) + controller.setIP( '10.0.123.1/24', intf=cintf ) + switch.setIP( '10.0.123.2/24', intf=sintf) info( '*** Configuring hosts\n' ) - h0.setIP( '192.168.123.1/24', h0intf ) - h1.setIP( '192.168.123.2/24', h1intf ) + h0.setIP( '192.168.123.1/24', intf=h0intf ) + h1.setIP( '192.168.123.2/24', intf=h1intf ) info( '*** Network state:\n' ) for node in controller, switch, h0, h1: diff --git a/mininet/node.py b/mininet/node.py index add10f3..63fd26c 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -358,14 +358,20 @@ class Node( object ): 'has no interfaces\n' ) def intf( self, intf='' ): - """Return our interface object with given name, - or default intf if name is empty""" + """Return our interface object with given string name, + default intf if name is falsy (None, empty string, etc). + or the input intf arg. + + Having this fcn return its arg for Intf objects makes it + easier to construct functions with flexible input args for + interfaces (those that accept both string names and Intf objects). + """ if not intf: return self.defaultIntf() elif type( intf) is str: return self.nameToIntf[ intf ] else: - return None + return intf def connectionsTo( self, node): "Return [ intf1, intf2... ] for all intfs that connect self to node." @@ -425,7 +431,7 @@ class Node( object ): def setIP( self, ip, prefixLen=8, intf=None ): """Set the IP address for an interface. - intf: interface name + intf: intf or intf name ip: IP address as a string prefixLen: prefix length, e.g. 8 for /8 or 16M addrs""" # This should probably be rethought From d7768ab22884f9abeb21dec634b50bd05da9ef0d Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Tue, 13 Nov 2012 23:28:19 -0800 Subject: [PATCH 247/250] examples/simpleperf: Warn in docstring about effects of link settings These include dropped pings and iperf hanging. --- examples/simpleperf.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/simpleperf.py b/examples/simpleperf.py index 3dad32c..1da4b66 100755 --- a/examples/simpleperf.py +++ b/examples/simpleperf.py @@ -2,6 +2,11 @@ """ Simple example of setting network and CPU parameters + +NOTE: link params limit BW, add latency, and loss. +There is a high chance that pings WILL fail and that +iperf will hang indefinitely if the TCP handshake fails +to complete. """ from mininet.topo import Topo From e1205a8a498ee1cf5e606f849a75d7f6aa1e7464 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Wed, 14 Nov 2012 00:43:46 -0800 Subject: [PATCH 248/250] Add a simple unit test for link/host creation with options --- Makefile | 1 + mininet/test/test_hifi.py | 63 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100755 mininet/test/test_hifi.py diff --git a/Makefile b/Makefile index f9e71a3..c0c53f2 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,7 @@ errcheck: $(PYSRC) test: $(MININET) $(TEST) -echo "Running tests" mininet/test/test_nets.py + mininet/test/test_hifi.py mnexec: mnexec.c $(MN) mininet/net.py cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(MN) --version`\" $< -o $@ diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py new file mode 100755 index 0000000..07437e8 --- /dev/null +++ b/mininet/test/test_hifi.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +"""Package: mininet + Test creation and pings for topologies with link and/or CPU options.""" + +import unittest + +from mininet.net import Mininet +from mininet.node import OVSKernelSwitch +from mininet.topo import Topo +from mininet.log import setLogLevel + + +SWITCH = OVSKernelSwitch +# Number of hosts for each test +N = 2 + + +class SingleSwitchOptionsTopo(Topo): + "Single switch connected to n hosts." + def __init__(self, n=2, hopts={}, lopts={}): + Topo.__init__(self, hopts=hopts, lopts=lopts) + switch = self.addSwitch('s1') + for h in range(n): + host = self.addHost('h%s' % (h + 1)) + self.addLink(host, switch) + + +class testOptionsTopo( unittest.TestCase ): + "Verify ability to create networks with host and link options." + + def runOptionsTopoTest( self, n, hopts=None, lopts=None ): + "Generic topology-with-options test runner." + mn = Mininet( SingleSwitchOptionsTopo( n=n, hopts=hopts ) ) + dropped = mn.run( mn.ping ) + self.assertEqual( dropped, 0 ) + + def testCPULimits( self ): + hopts = { 'cpu': 0.5 / N } + self.runOptionsTopoTest(N, hopts=hopts) + + def testLinkBandwidth( self ): + lopts = { 'bw': 10, 'use_htb': True } + self.runOptionsTopoTest(N, lopts=lopts) + + def testLinkDelay( self ): + lopts = { 'delay': '5ms', 'use_htb': True } + self.runOptionsTopoTest(N, lopts=lopts) + + def testLinkLoss( self ): + lopts = { 'loss': 10, 'use_htb': True } + self.runOptionsTopoTest(N, lopts=lopts) + + def testAllOptions( self ): + lopts = { 'bw': 10, 'delay': '5ms', 'loss': 10, 'use_htb': True } + hopts = { 'cpu': 0.5 / N } + self.runOptionsTopoTest(N, hopts=hopts, lopts=lopts) + + + +if __name__ == '__main__': + setLogLevel( 'warning' ) + unittest.main() From fcd01592e11d7799a88abfbb9a2823cbd5fba768 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Wed, 14 Nov 2012 07:55:10 -0800 Subject: [PATCH 249/250] Move CPU limit into net, to be reused in future unit tests --- examples/limit.py | 24 ++++-------------------- mininet/net.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/examples/limit.py b/examples/limit.py index fbda428..0b23ca1 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -8,33 +8,17 @@ from mininet.net import Mininet from mininet.link import TCIntf from mininet.node import CPULimitedHost from mininet.topolib import TreeTopo -from mininet.util import custom, quietRun +from mininet.util import custom from mininet.log import setLogLevel -from time import sleep + def testLinkLimit( net, bw ): "Run bandwidth limit test" print '*** Testing network %.2f Mbps bandwidth limit' % bw net.iperf( ) -def testCpuLimit( net, cpu ): - "run CPU limit test" - pct = cpu * 100 - print '*** Testing CPU %.0f%% bandwidth limit' % pct - h1, h2 = net.hosts - h1.cmd( 'while true; do a=1; done &' ) - h2.cmd( 'while true; do a=1; done &' ) - pid1 = h1.cmd( 'echo $!' ).strip() - pid2 = h2.cmd( 'echo $!' ).strip() - cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 ) - # It's a shame that this is what pylint prefers - for _ in range( 5 ): - sleep( 1 ) - print quietRun( cmd ).strip() - h1.cmd( 'kill %1') - h2.cmd( 'kill %1') -def limit( bw=10, cpu=.4 ): +def limit( bw=10, cpu=.1 ): """Example/test of link and CPU bandwidth limits bw: interface bandwidth limit in Mbps cpu: cpu limit as fraction of overall CPU time""" @@ -46,7 +30,7 @@ def limit( bw=10, cpu=.4 ): net = Mininet( topo=myTopo, intf=intf, host=host ) net.start() testLinkLimit( net, bw=bw ) - testCpuLimit( net, cpu=cpu ) + net.runCpuLimitTest( cpu=cpu ) net.stop() def verySimpleLimit( bw=150 ): diff --git a/mininet/net.py b/mininet/net.py index 8b6cf94..f2b774a 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -530,6 +530,42 @@ class Mininet( object ): output( '*** Results: %s\n' % result ) return result + def runCpuLimitTest( self, cpu, duration=5 ): + """run CPU limit test with 'while true' processes. + cpu: desired CPU fraction of each host + duration: test duration in seconds + returns a single list of measured CPU fractions as floats. + """ + pct = cpu * 100 + info('*** Testing CPU %.0f%% bandwidth limit\n' % pct) + hosts = self.hosts + for h in hosts: + h.cmd( 'while true; do a=1; done &' ) + pids = [h.cmd( 'echo $!' ).strip() for h in hosts] + pids_str = ",".join(["%s" % pid for pid in pids]) + cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str + # It's a shame that this is what pylint prefers + outputs = [] + for _ in range( duration ): + sleep( 1 ) + outputs.append( quietRun( cmd ).strip() ) + for h in hosts: + h.cmd( 'kill %1' ) + cpu_fractions = [] + for test_output in outputs: + # Split by line. Ignore first line, which looks like this: + # PID %CPU COMMAND\n + for line in test_output.split('\n')[1:]: + r = r'\d+ (\d+\.\d+)' + m = re.search( r, line ) + if m is None: + error( '*** Error: could not extract CPU fraction: %s\n' % + line ) + return None + cpu_fractions.append( float( m.group( 1 ) ) ) + output( '*** Results: %s\n' % cpu_fractions ) + return cpu_fractions + # BL: I think this can be rewritten now that we have # a real link class. def configLinkStatus( self, src, dst, status ): From 1f1d590c7a7bd8007c34cd0e7a94baf5c4418cb9 Mon Sep 17 00:00:00 2001 From: Brandon Heller Date: Wed, 14 Nov 2012 07:57:17 -0800 Subject: [PATCH 250/250] test: Improve unit tests to verify basic functionality Also a more complete ping test that parses all output to the CLI. These tests expand the hifi-specific ones to not just cover whether a topology can be created with options, but whether those options are properly implemented within some tolerance, like CPU limits, link bandwidth, delays, and even drops. --- mininet/cli.py | 8 ++++ mininet/net.py | 74 ++++++++++++++++++++++++++++++- mininet/test/test_hifi.py | 92 +++++++++++++++++++++++++++++++++------ 3 files changed, 158 insertions(+), 16 deletions(-) diff --git a/mininet/cli.py b/mininet/cli.py index f54d5c3..efb1808 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -148,6 +148,14 @@ class CLI( Cmd ): "Ping between first two hosts, useful for testing." self.mn.pingPair() + def do_pingallfull( self, _line ): + "Ping between first two hosts, returns all ping results." + self.mn.pingAllFull() + + def do_pingpairfull( self, _line ): + "Ping between first two hosts, returns all ping results." + self.mn.pingPairFull() + def do_iperf( self, line ): "Simple iperf TCP test between two (optionally specified) hosts." args = line.split() diff --git a/mininet/net.py b/mininet/net.py index f2b774a..066751c 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -428,9 +428,10 @@ class Mininet( object ): sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) return sent, received - def ping( self, hosts=None ): + def ping( self, hosts=None, timeout=None ): """Ping between all specified hosts. hosts: list of hosts + timeout: time to wait for a response, as string returns: ploss packet loss percentage""" # should we check if running? packets = 0 @@ -443,7 +444,10 @@ class Mininet( object ): output( '%s -> ' % node.name ) for dest in hosts: if node != dest: - result = node.cmd( 'ping -c1 ' + dest.IP() ) + opts = '' + if timeout: + opts = '-W %s' % timeout + result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) ) sent, received = self._parsePing( result ) packets += sent if received > sent: @@ -459,6 +463,61 @@ class Mininet( object ): ( ploss, lost, packets ) ) return ploss + @staticmethod + def _parsePingFull( pingOutput ): + "Parse ping output and return all data." + # Check for downed link + if 'connect: Network is unreachable' in pingOutput: + 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, 0, 0, 0, 0) + sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) + r = r'rtt min/avg/max/mdev = ' + r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms' + m = re.search( r, pingOutput ) + rttmin = float( m.group( 1 ) ) + rttavg = float( m.group( 2 ) ) + rttmax = float( m.group( 3 ) ) + rttdev = float( m.group( 4 ) ) + return sent, received, rttmin, rttavg, rttmax, rttdev + + def pingFull( self, hosts=None, timeout=None ): + """Ping between all specified hosts and return all data. + hosts: list of hosts + timeout: time to wait for a response, as string + returns: all ping data; see function body.""" + # should we check if running? + # Each value is a tuple: (src, dsd, [all ping outputs]) + all_outputs = [] + if not hosts: + hosts = self.hosts + output( '*** Ping: testing ping reachability\n' ) + for node in hosts: + output( '%s -> ' % node.name ) + for dest in hosts: + if node != dest: + opts = '' + if timeout: + opts = '-W %s' % timeout + result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) ) + outputs = self._parsePingFull( result ) + sent, received, rttmin, rttavg, rttmax, rttdev = outputs + all_outputs.append( (node, dest, outputs) ) + output( ( '%s ' % dest.name ) if received else 'X ' ) + output( '\n' ) + output( "*** Results: \n" ) + for outputs in all_outputs: + src, dest, ping_outputs = outputs + sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs + output( " %s->%s: %s/%s, " % (src, dest, sent, received ) ) + output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" % + (rttmin, rttavg, rttmax, rttdev) ) + return all_outputs + def pingAll( self ): """Ping between all hosts. returns: ploss packet loss percentage""" @@ -470,6 +529,17 @@ class Mininet( object ): hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] return self.ping( hosts=hosts ) + def pingAllFull( self ): + """Ping between all hosts. + returns: ploss packet loss percentage""" + return self.pingFull() + + def pingPairFull( self ): + """Ping between first two hosts, useful for testing. + returns: ploss packet loss percentage""" + hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] + return self.pingFull( hosts=hosts ) + @staticmethod def _parseIperf( iperfOutput ): """Parse iperf output and return bandwidth. diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py index 07437e8..ace7bb5 100755 --- a/mininet/test/test_hifi.py +++ b/mininet/test/test_hifi.py @@ -7,6 +7,8 @@ import unittest from mininet.net import Mininet from mininet.node import OVSKernelSwitch +from mininet.node import CPULimitedHost +from mininet.link import TCLink from mininet.topo import Topo from mininet.log import setLogLevel @@ -18,7 +20,11 @@ N = 2 class SingleSwitchOptionsTopo(Topo): "Single switch connected to n hosts." - def __init__(self, n=2, hopts={}, lopts={}): + def __init__(self, n=2, hopts=None, lopts=None): + if not hopts: + hopts = {} + if not lopts: + lopts = {} Topo.__init__(self, hopts=hopts, lopts=lopts) switch = self.addSwitch('s1') for h in range(n): @@ -31,31 +37,89 @@ class testOptionsTopo( unittest.TestCase ): def runOptionsTopoTest( self, n, hopts=None, lopts=None ): "Generic topology-with-options test runner." - mn = Mininet( SingleSwitchOptionsTopo( n=n, hopts=hopts ) ) + mn = Mininet( topo=SingleSwitchOptionsTopo( n=n, hopts=hopts, + lopts=lopts ), + host=CPULimitedHost, link=TCLink ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) + def assertWithinTolerance(self, measured, expected, tolerance_frac): + """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 ) + def testCPULimits( self ): - hopts = { 'cpu': 0.5 / N } - self.runOptionsTopoTest(N, hopts=hopts) + "Verify topology creation with CPU limits set for both schedulers." + CPU_FRACTION = 0.1 + CPU_TOLERANCE = 0.8 # CPU fraction below which test should fail + hopts = { 'cpu': CPU_FRACTION } + #self.runOptionsTopoTest( N, hopts=hopts ) + + mn = Mininet( SingleSwitchOptionsTopo( n=N, hopts=hopts ), + host=CPULimitedHost ) + mn.start() + results = mn.runCpuLimitTest( cpu=CPU_FRACTION ) + mn.stop() + for cpu in results: + self.assertWithinTolerance( cpu, CPU_FRACTION, CPU_TOLERANCE ) def testLinkBandwidth( self ): - lopts = { 'bw': 10, 'use_htb': True } - self.runOptionsTopoTest(N, lopts=lopts) + "Verify that link bandwidths are accurate within a bound." + 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 ) + bw_strs = mn.run( mn.iperf ) + for bw_str in bw_strs: + bw = float( bw_str.split(' ')[0] ) + self.assertWithinTolerance( bw, BW, BW_TOLERANCE ) def testLinkDelay( self ): - lopts = { 'delay': '5ms', 'use_htb': True } - self.runOptionsTopoTest(N, lopts=lopts) + "Verify that link delays are accurate within a bound." + DELAY_MS = 15 + 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 ) + ping_delays = mn.run( mn.pingFull ) + test_outputs = ping_delays[0] + # Ignore unused variables below + # pylint: disable-msg=W0612 + node, dest, ping_outputs = test_outputs + sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs + 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, + DELAY_TOLERANCE) def testLinkLoss( self ): - lopts = { 'loss': 10, 'use_htb': True } - self.runOptionsTopoTest(N, lopts=lopts) + "Verify that we see packet drops with a high configured loss rate." + LOSS_PERCENT = 99 + REPS = 1 + lopts = { 'loss': LOSS_PERCENT, 'use_htb': True } + mn = Mininet( topo=SingleSwitchOptionsTopo( n=N, lopts=lopts ), + host=CPULimitedHost, link=TCLink ) + # Drops are probabilistic, but the chance of no dropped packets is + # 1 in 100 million with 4 hops for a link w/99% loss. + dropped_total = 0 + mn.start() + for _ in range(REPS): + dropped_total += mn.ping(timeout='1') + mn.stop() + self.assertTrue(dropped_total > 0) - def testAllOptions( self ): - lopts = { 'bw': 10, 'delay': '5ms', 'loss': 10, 'use_htb': True } + def testMostOptions( self ): + "Verify topology creation with most link options and CPU limits." + lopts = { 'bw': 10, 'delay': '5ms', 'use_htb': True } hopts = { 'cpu': 0.5 / N } - self.runOptionsTopoTest(N, hopts=hopts, lopts=lopts) - + self.runOptionsTopoTest( N, hopts=hopts, lopts=lopts ) if __name__ == '__main__':