diff --git a/.travis.yml b/.travis.yml index 735a97c..8c9b987 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,28 +4,32 @@ sudo: required matrix: include: - dist: trusty + python: 2.7 + env: dist="14.04 LTS trusty" + - dist: trusty + python: 3.6 env: dist="14.04 LTS trusty" -# - dist: xenial -# env: dist="16.04 LTS xenial" -# Travis-CI only proposes 14.04 LTS Trusty and there is no plan to update to 16.04 xenial -# (c.f. https://github.com/travis-ci/travis-ci/issues/5821) -# It is useless to add a second job because it will run in the same Ubuntu version (14.04) before_install: - sudo apt-get update -qq - sudo apt-get install -qq vlan -- sudo util/install.sh -n +- PYTHON=`which python` util/install.sh -n install: - bash -c "if [ `lsb_release -rs` == '14.04' ]; then make codecheck; fi" -- sudo util/install.sh -fnvw +- pip install pexpect || pip3 install pexpect +- util/install.sh -nfvw script: -- sudo mn --test pingall -- sudo python mininet/test/runner.py -v -quick -- sudo python examples/test/runner.py -v -quick +- alias sudo="sudo env PATH=$PATH" +- export PYTHON=`which python` +- echo 'px import sys; print(sys.version_info)' | sudo $PYTHON bin/mn -v output +- sudo $PYTHON bin/mn --test pingall +- sudo $PYTHON mininet/test/runner.py -v -quick +- sudo $PYTHON examples/test/runner.py -v -quick notifications: email: on_success: never -# More details: https://docs.travis-ci.com/user/notifications#Configuring-email-notifications + +# More details: https://docs.travis-ci.com/user/notifications diff --git a/Makefile b/Makefile index d8c828e..c97ea7a 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ MININET = mininet/*.py TEST = mininet/test/*.py EXAMPLES = mininet/examples/*.py MN = bin/mn -PYMN = python -B bin/mn +PYTHON ?= python +PYMN = $(PYTHON) -B bin/mn BIN = $(MN) PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN) MNEXEC = mnexec @@ -54,13 +55,13 @@ install-manpages: $(MANPAGES) install -D -t $(MANDIR) $(MANPAGES) install: install-mnexec install-manpages - python setup.py install + $(PYTHON) setup.py install develop: $(MNEXEC) $(MANPAGES) # Perhaps we should link these as well install $(MNEXEC) $(BINDIR) install $(MANPAGES) $(MANDIR) - python setup.py develop + $(PYTHON) setup.py develop man: $(MANPAGES) diff --git a/bin/mn b/bin/mn index a7b1479..4c5cf3d 100755 --- a/bin/mn +++ b/bin/mn @@ -186,8 +186,10 @@ class MininetRunner( object ): for fileName in files: customs = {} if os.path.isfile( fileName ): - execfile( fileName, customs, customs ) - for name, val in customs.iteritems(): + # pylint: disable=exec-used + exec( compile( open( fileName ).read(), fileName, 'exec' ), + customs, customs ) + for name, val in customs.items(): self.setCustom( name, val ) else: raise Exception( 'could not find custom file: %s' % fileName ) @@ -256,7 +258,7 @@ class MininetRunner( object ): 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', + choices=list( LEVELS.keys() ), default = 'info', help = '|'.join( LEVELS.keys() ) ) opts.add_option( '--innamespace', action='store_true', default=False, help='sw and ctrl in namespace?' ) @@ -286,7 +288,7 @@ class MininetRunner( object ): metavar='server1,server2...', help=( 'run on multiple servers (experimental!)' ) ) opts.add_option( '--placement', type='choice', - choices=PLACEMENT.keys(), default='block', + choices=list( PLACEMENT.keys() ), default='block', metavar='block|random', help=( 'node placement for --cluster ' '(experimental!) ' ) ) diff --git a/examples/cluster.py b/examples/cluster.py index 8959868..078dca4 100755 --- a/examples/cluster.py +++ b/examples/cluster.py @@ -126,7 +126,7 @@ class ClusterCleanup( object ): def cleanup( cls ): "Clean up" info( '*** Cleaning up cluster\n' ) - for server, user in cls.serveruser.iteritems(): + for server, user in cls.serveruser.items(): if server == 'localhost': # Handled by mininet.clean.cleanup() continue diff --git a/examples/controlnet.py b/examples/controlnet.py index 9633089..e5cf765 100755 --- a/examples/controlnet.py +++ b/examples/controlnet.py @@ -49,7 +49,7 @@ class MininetFacade( object ): args: unnamed networks passed as arguments kwargs: named networks passed as arguments""" self.net = net - self.nets = [ net ] + list( args ) + kwargs.values() + self.nets = [ net ] + list( args ) + list( kwargs.values() ) self.nameToNet = kwargs self.nameToNet['net'] = net diff --git a/examples/cpu.py b/examples/cpu.py index c83177b..accb0ea 100755 --- a/examples/cpu.py +++ b/examples/cpu.py @@ -31,9 +31,9 @@ rate includes buffering. from mininet.net import Mininet from mininet.node import CPULimitedHost from mininet.topolib import TreeTopo -from mininet.util import custom, waitListening +from mininet.util import custom, waitListening, decode from mininet.log import setLogLevel, info - +from mininet.clean import cleanup def bwtest( cpuLimits, period_us=100000, seconds=10 ): """Example/test of link and CPU bandwidth limits @@ -55,7 +55,8 @@ def bwtest( cpuLimits, period_us=100000, seconds=10 ): net = Mininet( topo=topo, host=host ) # pylint: disable=bare-except except: - info( '*** Skipping scheduler %s\n' % sched ) + info( '*** Skipping scheduler %s and cleaning up\n' % sched ) + cleanup() break net.start() net.pingAll() @@ -70,7 +71,7 @@ def bwtest( cpuLimits, period_us=100000, seconds=10 ): # ignore empty result from waitListening/telnet popen.stdout.readline() client.cmd( 'iperf -yc -t %s -c %s' % ( seconds, server.IP() ) ) - result = popen.stdout.readline().split( ',' ) + result = decode( popen.stdout.readline() ).split( ',' ) bps = float( result[ -1 ] ) popen.terminate() net.stop() diff --git a/examples/miniedit.py b/examples/miniedit.py index 54c31f8..c1057c6 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -20,24 +20,39 @@ OpenFlow icon from https://www.opennetworking.org/ MINIEDIT_VERSION = '2.2.0.1' +import sys from optparse import OptionParser -# from Tkinter import * -from Tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu, Checkbutton, - Menu, Toplevel, Button, BitmapImage, PhotoImage, Canvas, - Scrollbar, Wm, TclError, StringVar, IntVar, - E, W, EW, NW, Y, VERTICAL, SOLID, CENTER, - RIGHT, LEFT, BOTH, TRUE, FALSE ) -from ttk import Notebook -from tkMessageBox import showerror from subprocess import call -import tkFont -import tkFileDialog -import tkSimpleDialog + +# pylint: disable=import-error +if sys.version_info[0] == 2: + from Tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu, + Checkbutton, Menu, Toplevel, Button, BitmapImage, + PhotoImage, Canvas, Scrollbar, Wm, TclError, + StringVar, IntVar, E, W, EW, NW, Y, VERTICAL, SOLID, + CENTER, RIGHT, LEFT, BOTH, TRUE, FALSE ) + from ttk import Notebook + from tkMessageBox import showerror + import tkFont + import tkFileDialog + import tkSimpleDialog +else: + from tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu, + Checkbutton, Menu, Toplevel, Button, BitmapImage, + PhotoImage, Canvas, Scrollbar, Wm, TclError, + StringVar, IntVar, E, W, EW, NW, Y, VERTICAL, SOLID, + CENTER, RIGHT, LEFT, BOTH, TRUE, FALSE ) + from tkinter.ttk import Notebook + from tkinter.messagebox import showerror + from tkinter import font as tkFont + from tkinter import simpledialog as tkSimpleDialog + from tkinter import filedialog as tkFileDialog +# pylint: enable=import-error + import re import json from distutils.version import StrictVersion import os -import sys from functools import partial if 'PYTHONPATH' in os.environ: @@ -1408,7 +1423,7 @@ class MiniEdit( Frame ): def convertJsonUnicode(self, text): "Some part of Mininet don't like Unicode" if isinstance(text, dict): - return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in text.iteritems()} + return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in text.items()} elif isinstance(text, list): return [self.convertJsonUnicode(element) for element in text] elif isinstance(text, unicode): @@ -1835,7 +1850,7 @@ class MiniEdit( Frame ): # Save Links f.write(" info( '*** Add links\\n')\n") - for key,linkDetail in self.links.iteritems(): + for key,linkDetail in self.links.items(): tags = self.canvas.gettags(key) if 'data' in tags: optsExist = False @@ -2875,7 +2890,7 @@ class MiniEdit( Frame ): def buildLinks( self, net): # Make links info( "Getting Links.\n" ) - for key,link in self.links.iteritems(): + for key,link in self.links.items(): tags = self.canvas.gettags(key) if 'data' in tags: src=link['src'] @@ -3211,7 +3226,7 @@ class MiniEdit( Frame ): customs = {} if os.path.isfile( fileName ): execfile( fileName, customs, customs ) - for name, val in customs.iteritems(): + for name, val in customs.items(): self.setCustom( name, val ) else: raise Exception( 'could not find custom file: %s' % fileName ) diff --git a/examples/multipoll.py b/examples/multipoll.py index 0b735ba..bb1c9d3 100755 --- a/examples/multipoll.py +++ b/examples/multipoll.py @@ -9,6 +9,7 @@ monitoring them from mininet.topo import SingleSwitchTopo from mininet.net import Mininet from mininet.log import info, setLogLevel +from mininet.util import decode from time import time from select import poll, POLLIN @@ -19,7 +20,7 @@ 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(): + for h, outfile in outfiles.items(): tail = Popen( [ 'tail', '-f', outfile ], stdout=PIPE, stderr=devnull ) fd = tail.stdout.fileno() @@ -40,7 +41,7 @@ def monitorFiles( outfiles, seconds, timeoutms ): host = fdToHost[ fd ] # Wait for a line of output line = f.readline().strip() - yield host, line + yield host, decode( line ) else: # If we timed out, return nothing yield None, '' diff --git a/examples/test/test_baresshd.py b/examples/test/test_baresshd.py index 3d5df93..251b75d 100755 --- a/examples/test/test_baresshd.py +++ b/examples/test/test_baresshd.py @@ -5,8 +5,9 @@ Tests for baresshd.py """ import unittest -import pexpect +from mininet.util import pexpect from mininet.clean import cleanup, sh +from sys import stdout class testBareSSHD( unittest.TestCase ): @@ -14,7 +15,9 @@ class testBareSSHD( unittest.TestCase ): def connected( self ): "Log into ssh server, check banner, then exit" - p = pexpect.spawn( 'ssh 10.0.0.1 -o StrictHostKeyChecking=no -i /tmp/ssh/test_rsa exit' ) + p = pexpect.spawn( 'ssh 10.0.0.1 -o ConnectTimeout=1 ' + '-o StrictHostKeyChecking=no ' + '-i /tmp/ssh/test_rsa exit' ) while True: index = p.expect( self.opts ) if index == 0: @@ -22,6 +25,7 @@ class testBareSSHD( unittest.TestCase ): else: return False + def setUp( self ): # verify that sshd is not running self.assertFalse( self.connected() ) @@ -55,7 +59,7 @@ class testBareSSHD( unittest.TestCase ): def tearDown( self ): # kill the ssh process - sh( "ps aux | grep 'ssh.*Banner' | awk '{ print $2 }' | xargs kill" ) + sh( "ps aux | grep ssh |grep Banner| awk '{ print $2 }' | xargs kill" ) cleanup() # remove public key pair sh( 'rm -rf /tmp/ssh' ) diff --git a/examples/test/test_bind.py b/examples/test/test_bind.py index dcc9fbf..e423e39 100755 --- a/examples/test/test_bind.py +++ b/examples/test/test_bind.py @@ -5,7 +5,7 @@ Tests for bind.py """ import unittest -import pexpect +from mininet.util import pexpect class testBind( unittest.TestCase ): diff --git a/examples/test/test_clusterSanity.py b/examples/test/test_clusterSanity.py index cdbc390..13e51e2 100755 --- a/examples/test/test_clusterSanity.py +++ b/examples/test/test_clusterSanity.py @@ -5,7 +5,7 @@ A simple sanity check test for cluster edition ''' import unittest -import pexpect +from mininet.util import pexpect class clusterSanityCheck( unittest.TestCase ): diff --git a/examples/test/test_controllers.py b/examples/test/test_controllers.py index b9b93d9..abc79eb 100755 --- a/examples/test/test_controllers.py +++ b/examples/test/test_controllers.py @@ -5,7 +5,7 @@ Tests for controllers.py and controllers2.py """ import unittest -import pexpect +from mininet.util import pexpect class testControllers( unittest.TestCase ): diff --git a/examples/test/test_controlnet.py b/examples/test/test_controlnet.py index 3a99ea4..ba22740 100755 --- a/examples/test/test_controlnet.py +++ b/examples/test/test_controlnet.py @@ -5,7 +5,7 @@ Test for controlnet.py """ import unittest -import pexpect +from mininet.util import pexpect class testControlNet( unittest.TestCase ): diff --git a/examples/test/test_cpu.py b/examples/test/test_cpu.py index c7a6266..8b36c6e 100755 --- a/examples/test/test_cpu.py +++ b/examples/test/test_cpu.py @@ -15,7 +15,7 @@ cfs 10% 1.29e+09 """ import unittest -import pexpect +from mininet.util import pexpect import sys class testCPU( unittest.TestCase ): diff --git a/examples/test/test_emptynet.py b/examples/test/test_emptynet.py index f8e7c4a..aead619 100755 --- a/examples/test/test_emptynet.py +++ b/examples/test/test_emptynet.py @@ -5,7 +5,7 @@ Test for emptynet.py """ import unittest -import pexpect +from mininet.util import pexpect class testEmptyNet( unittest.TestCase ): diff --git a/examples/test/test_hwintf.py b/examples/test/test_hwintf.py index 843fc40..e242ca3 100755 --- a/examples/test/test_hwintf.py +++ b/examples/test/test_hwintf.py @@ -7,7 +7,7 @@ Test for hwintf.py import unittest import re -import pexpect +from mininet.util import pexpect from mininet.log import setLogLevel from mininet.node import Node diff --git a/examples/test/test_intfoptions.py b/examples/test/test_intfoptions.py index 6f1ebec..2d72471 100755 --- a/examples/test/test_intfoptions.py +++ b/examples/test/test_intfoptions.py @@ -5,7 +5,7 @@ Test for intfOptions.py """ import unittest -import pexpect +from mininet.util import pexpect import sys class testIntfOptions( unittest.TestCase ): diff --git a/examples/test/test_limit.py b/examples/test/test_limit.py index 2052e7e..47ceaa1 100755 --- a/examples/test/test_limit.py +++ b/examples/test/test_limit.py @@ -5,7 +5,7 @@ Test for limit.py """ import unittest -import pexpect +from mininet.util import pexpect import sys class testLimit( unittest.TestCase ): diff --git a/examples/test/test_linearbandwidth.py b/examples/test/test_linearbandwidth.py index a18d97a..9e876bc 100755 --- a/examples/test/test_linearbandwidth.py +++ b/examples/test/test_linearbandwidth.py @@ -5,7 +5,7 @@ Test for linearbandwidth.py """ import unittest -import pexpect +from mininet.util import pexpect import sys class testLinearBandwidth( unittest.TestCase ): diff --git a/examples/test/test_linuxrouter.py b/examples/test/test_linuxrouter.py index 60af201..f9cf648 100644 --- a/examples/test/test_linuxrouter.py +++ b/examples/test/test_linuxrouter.py @@ -5,7 +5,7 @@ Test for linuxrouter.py """ import unittest -import pexpect +from mininet.util import pexpect from mininet.util import quietRun class testLinuxRouter( unittest.TestCase ): diff --git a/examples/test/test_multilink.py b/examples/test/test_multilink.py index 4bea8b0..0925548 100755 --- a/examples/test/test_multilink.py +++ b/examples/test/test_multilink.py @@ -6,7 +6,7 @@ validates mininet interfaces against systems interfaces ''' import unittest -import pexpect +from mininet.util import pexpect class testMultiLink( unittest.TestCase ): diff --git a/examples/test/test_multiping.py b/examples/test/test_multiping.py index bb5440a..2519121 100755 --- a/examples/test/test_multiping.py +++ b/examples/test/test_multiping.py @@ -5,7 +5,7 @@ Test for multiping.py """ import unittest -import pexpect +from mininet.util import pexpect from collections import defaultdict class testMultiPing( unittest.TestCase ): diff --git a/examples/test/test_multipoll.py b/examples/test/test_multipoll.py index 61e5cec..8815033 100755 --- a/examples/test/test_multipoll.py +++ b/examples/test/test_multipoll.py @@ -5,7 +5,7 @@ Test for multipoll.py """ import unittest -import pexpect +from mininet.util import pexpect class testMultiPoll( unittest.TestCase ): diff --git a/examples/test/test_multitest.py b/examples/test/test_multitest.py index 09172c5..4352b8e 100755 --- a/examples/test/test_multitest.py +++ b/examples/test/test_multitest.py @@ -5,7 +5,7 @@ Test for multitest.py """ import unittest -import pexpect +from mininet.util import pexpect class testMultiTest( unittest.TestCase ): diff --git a/examples/test/test_nat.py b/examples/test/test_nat.py index c4bb096..865615f 100755 --- a/examples/test/test_nat.py +++ b/examples/test/test_nat.py @@ -5,7 +5,7 @@ Test for nat.py """ import unittest -import pexpect +from mininet.util import pexpect from mininet.util import quietRun destIP = '8.8.8.8' # Google DNS diff --git a/examples/test/test_natnet.py b/examples/test/test_natnet.py index 3addc92..4b9e099 100644 --- a/examples/test/test_natnet.py +++ b/examples/test/test_natnet.py @@ -5,7 +5,7 @@ Test for natnet.py """ import unittest -import pexpect +from mininet.util import pexpect from mininet.util import quietRun class testNATNet( unittest.TestCase ): diff --git a/examples/test/test_numberedports.py b/examples/test/test_numberedports.py index 81ee187..ea7d61e 100755 --- a/examples/test/test_numberedports.py +++ b/examples/test/test_numberedports.py @@ -5,7 +5,7 @@ Test for numberedports.py """ import unittest -import pexpect +from mininet.util import pexpect from collections import defaultdict from mininet.node import OVSSwitch diff --git a/examples/test/test_popen.py b/examples/test/test_popen.py index c7f83f0..f41d531 100755 --- a/examples/test/test_popen.py +++ b/examples/test/test_popen.py @@ -5,7 +5,7 @@ Test for popen.py and popenpoll.py """ import unittest -import pexpect +from mininet.util import pexpect class testPopen( unittest.TestCase ): diff --git a/examples/test/test_scratchnet.py b/examples/test/test_scratchnet.py index 43d8286..3d44955 100755 --- a/examples/test/test_scratchnet.py +++ b/examples/test/test_scratchnet.py @@ -5,7 +5,7 @@ Test for scratchnet.py """ import unittest -import pexpect +from mininet.util import pexpect class testScratchNet( unittest.TestCase ): diff --git a/examples/test/test_simpleperf.py b/examples/test/test_simpleperf.py index f8e94d5..93a36aa 100755 --- a/examples/test/test_simpleperf.py +++ b/examples/test/test_simpleperf.py @@ -5,7 +5,7 @@ Test for simpleperf.py """ import unittest -import pexpect +from mininet.util import pexpect import sys from mininet.log import setLogLevel @@ -18,7 +18,7 @@ class testSimplePerf( unittest.TestCase ): "Run the example and verify iperf results" # 10 Mb/s, plus or minus 20% tolerance BW = 10 - TOLERANCE = .2 + TOLERANCE = .2 p = pexpect.spawn( 'python -m mininet.examples.simpleperf testmode' ) # check iperf results p.expect( "Results: \['10M', '([\d\.]+) .bits/sec", timeout=480 ) diff --git a/examples/test/test_sshd.py b/examples/test/test_sshd.py index 6a39bba..193b1ea 100755 --- a/examples/test/test_sshd.py +++ b/examples/test/test_sshd.py @@ -5,7 +5,7 @@ Test for sshd.py """ import unittest -import pexpect +from mininet.util import pexpect from mininet.clean import sh class testSSHD( unittest.TestCase ): diff --git a/examples/test/test_tree1024.py b/examples/test/test_tree1024.py index e26af2c..06f8e49 100755 --- a/examples/test/test_tree1024.py +++ b/examples/test/test_tree1024.py @@ -5,7 +5,7 @@ Test for tree1024.py """ import unittest -import pexpect +from mininet.util import pexpect import sys class testTree1024( unittest.TestCase ): diff --git a/examples/test/test_treeping64.py b/examples/test/test_treeping64.py index ae02afc..8c346cd 100755 --- a/examples/test/test_treeping64.py +++ b/examples/test/test_treeping64.py @@ -5,7 +5,7 @@ Test for treeping64.py """ import unittest -import pexpect +from mininet.util import pexpect import sys class testTreePing64( unittest.TestCase ): diff --git a/examples/test/test_vlanhost.py b/examples/test/test_vlanhost.py index d68448f..6781cce 100644 --- a/examples/test/test_vlanhost.py +++ b/examples/test/test_vlanhost.py @@ -5,7 +5,7 @@ Test for vlanhost.py """ import unittest -import pexpect +from mininet.util import pexpect import sys from mininet.util import quietRun diff --git a/mininet/clean.py b/mininet/clean.py index 248de8b..f66d633 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -16,12 +16,13 @@ import time from mininet.log import info from mininet.term import cleanUpScreens - +from mininet.util import decode def sh( cmd ): "Print a command and send it to the shell" info( cmd + '\n' ) - return Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE ).communicate()[ 0 ] + result = Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE ).communicate()[ 0 ] + return decode( result ) def killprocs( pattern ): "Reliably terminate processes matching a pattern (including args)" @@ -76,7 +77,6 @@ class Cleanup( object ): for dp in dps: if dp: sh( 'dpctl deldp ' + dp ) - info( "*** Removing OVS datapaths\n" ) dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines() if dps: diff --git a/mininet/link.py b/mininet/link.py index d514de6..59cb9ec 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -164,7 +164,7 @@ class Intf( object ): method: config method name param: arg=value (ignore if value=None) value may also be list or dict""" - name, value = param.items()[ 0 ] + name, value = list( param.items() )[ 0 ] f = getattr( self, method, None ) if not f or value is None: return @@ -285,11 +285,7 @@ class TCIntf( Intf ): 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 ): + if loss and ( loss < 0 or loss > 100 ): error( 'Bad loss percentage', loss, '%%\n' ) else: # Delay/jitter/loss/max queue size diff --git a/mininet/moduledeps.py b/mininet/moduledeps.py index 860c21c..fda2d70 100644 --- a/mininet/moduledeps.py +++ b/mininet/moduledeps.py @@ -1,6 +1,6 @@ "Module dependency utility functions for Mininet." -from mininet.util import quietRun +from mininet.util import quietRun, BaseString from mininet.log import info, error, debug from os import environ @@ -28,9 +28,9 @@ def moduleDeps( subtract=None, add=None ): add: string or list of module names to add, if not already loaded""" subtract = subtract if subtract is not None else [] add = add if add is not None else [] - if isinstance( subtract, basestring ): + if isinstance( subtract, BaseString ): subtract = [ subtract ] - if isinstance( add, basestring ): + if isinstance( add, BaseString ): add = [ add ] for mod in subtract: if mod in lsmod(): diff --git a/mininet/net.py b/mininet/net.py index 2c7668d..29d3876 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -104,7 +104,7 @@ from mininet.nodelib import NAT from mininet.link import Link, Intf from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot, macColonHex, ipStr, ipParse, netParse, ipAdd, - waitListening ) + waitListening, BaseString ) from mininet.term import cleanUpScreens, makeTerms # Mininet version: should be consistent with README and LICENSE @@ -383,8 +383,8 @@ class Mininet( object ): params: additional link params (optional) returns: link object""" # Accept node objects or names - node1 = node1 if not isinstance( node1, basestring ) else self[ node1 ] - node2 = node2 if not isinstance( node2, basestring ) else self[ node2 ] + node1 = node1 if not isinstance( node1, BaseString ) else self[ node1 ] + node2 = node2 if not isinstance( node2, BaseString ) else self[ node2 ] options = dict( params ) # Port is optional if port1 is not None: @@ -549,7 +549,8 @@ class Mininet( object ): switch.start( self.controllers ) started = {} for swclass, switches in groupby( - sorted( self.switches, key=type ), type ): + sorted( self.switches, + key=lambda s: str( type( s ) ) ), type ): switches = tuple( switches ) if hasattr( swclass, 'batchStartup' ): success = swclass.batchStartup( switches ) @@ -576,7 +577,8 @@ class Mininet( object ): info( '*** Stopping %i switches\n' % len( self.switches ) ) stopped = {} for swclass, switches in groupby( - sorted( self.switches, key=type ), type ): + sorted( self.switches, + key=lambda s: str( type( s ) ) ), type ): switches = tuple( switches ) if hasattr( swclass, 'batchShutdown' ): success = swclass.batchShutdown( switches ) diff --git a/mininet/node.py b/mininet/node.py index 42b3780..3c61393 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -62,7 +62,8 @@ from time import sleep from mininet.log import info, error, warn, debug from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin, - numCores, retry, mountCgroups ) + numCores, retry, mountCgroups, BaseString, decode, + encode, Python3 ) from mininet.moduledeps import moduleDeps, pathCheck, TUN from mininet.link import Link, Intf, TCIntf, OVSIntf from re import findall @@ -87,6 +88,9 @@ class Node( object ): self.privateDirs = params.get( 'privateDirs', [] ) self.inNamespace = params.get( 'inNamespace', inNamespace ) + # Python 3 complains if we don't wait for shell exit + self.waitExited = params.get( 'waitExited', Python3 ) + # Stash configuration parameters for future reference self.params = params @@ -144,7 +148,9 @@ class Node( object ): master, slave = pty.openpty() self.shell = self._popen( cmd, stdin=slave, stdout=slave, stderr=slave, close_fds=False ) - self.stdin = os.fdopen( master, 'rw' ) + # XXX BL: This doesn't seem right, and we should also probably + # close our files when we exit... + self.stdin = os.fdopen( master, 'r' ) self.stdout = self.stdin self.pid = self.shell.pid self.pollOut = select.poll() @@ -171,7 +177,7 @@ class Node( object ): def mountPrivateDirs( self ): "mount private directories" # Avoid expanding a string into a list of chars - assert not isinstance( self.privateDirs, basestring ) + assert not isinstance( self.privateDirs, BaseString ) for directory in self.privateDirs: if isinstance( directory, tuple ): # mount given private directory @@ -200,7 +206,9 @@ class Node( object ): params: parameters to Popen()""" # Leave this is as an instance method for now assert self - return Popen( cmd, **params ) + popen = Popen( cmd, **params ) + debug( '_popen', cmd, popen.pid ) + return popen def cleanup( self ): "Help python collect its garbage." @@ -209,6 +217,9 @@ class Node( object ): # for intfName in self.intfNames(): # if self.name in intfName: # quietRun( 'ip link del ' + intfName ) + if self.waitExited and self.shell: + debug( 'waiting for', self.pid, 'to terminate\n' ) + self.shell.wait() self.shell = None # Subshell I/O, commands and control @@ -218,7 +229,7 @@ class Node( object ): maxbytes: maximum number of bytes to return""" count = len( self.readbuf ) if count < maxbytes: - data = os.read( self.stdout.fileno(), maxbytes - count ) + data = decode( os.read( self.stdout.fileno(), maxbytes - count ) ) self.readbuf += data if maxbytes >= len( self.readbuf ): result = self.readbuf @@ -242,7 +253,7 @@ class Node( object ): def write( self, data ): """Write data to node. data: string""" - os.write( self.stdin.fileno(), data ) + os.write( self.stdin.fileno(), encode( data ) ) def terminate( self ): "Send kill signal to Node and clean up after it." @@ -376,7 +387,7 @@ class Node( object ): if isinstance( args[ 0 ], list ): # popen([cmd, arg1, arg2...]) cmd = args[ 0 ] - elif isinstance( args[ 0 ], basestring ): + elif isinstance( args[ 0 ], BaseString ): # popen("cmd arg1 arg2...") cmd = args[ 0 ].split() else: @@ -400,7 +411,7 @@ class Node( object ): # Warning: this can fail with large numbers of fds! out, err = popen.communicate() exitcode = popen.wait() - return out, err, exitcode + return decode( out ), decode( err ), exitcode # Interface management, configuration, and routing @@ -462,7 +473,7 @@ class Node( object ): """ if not intf: return self.defaultIntf() - elif isinstance( intf, basestring): + elif isinstance( intf, BaseString): return self.nameToIntf[ intf ] else: return intf @@ -489,7 +500,7 @@ class Node( object ): # explicitly so that we won't get errors if we run before they # 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(): + for intf in list( self.intfs.values() ): # Protect against deleting hardware interfaces if ( self.name in intf.name ) or ( not checkName ): intf.delete() @@ -514,7 +525,7 @@ class Node( object ): """Set the default route to go through intf. intf: Intf or {dev via ...}""" # Note setParam won't call us if intf is none - if isinstance( intf, basestring ) and ' ' in intf: + if isinstance( intf, BaseString ) and ' ' in intf: params = intf else: params = 'dev %s' % intf @@ -561,7 +572,7 @@ class Node( object ): method: config method name param: arg=value (ignore if value=None) value may also be list or dict""" - name, value = param.items()[ 0 ] + name, value = list( param.items() )[ 0 ] if value is None: return f = getattr( self, method, None ) @@ -610,7 +621,7 @@ class Node( object ): def intfList( self ): "List of our interfaces sorted by port number" - return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ] + return [ self.intfs[ p ] for p in sorted( self.intfs.keys() ) ] def intfNames( self ): "The names of our interfaces sorted by port number" @@ -881,7 +892,7 @@ class Switch( Node ): "Return correctly formatted dpid from dpid or switch name (s1 -> 1)" if dpid: # Remove any colons and make sure it's a good hex number - dpid = dpid.translate( None, ':' ) + dpid = dpid.replace( ':', '' ) assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0 else: # Use hex of the first number in the switch name @@ -889,6 +900,7 @@ class Switch( Node ): if nums: dpid = hex( int( nums[ 0 ] ) )[ 2: ] else: + self.terminate() # Python 3.6 crash workaround raise Exception( 'Unable to derive default datapath ID - ' 'please either specify a dpid or use a ' 'canonical switch name such as s23.' ) @@ -1232,7 +1244,7 @@ class OVSSwitch( Switch ): run( cmds, shell=True ) # Reapply link config if necessary... for switch in switches: - for intf in switch.intfs.itervalues(): + for intf in switch.intfs.values(): if isinstance( intf, TCIntf ): intf.config( **intf.params ) return switches @@ -1258,7 +1270,7 @@ class OVSSwitch( Switch ): pids = ' '.join( str( switch.pid ) for switch in switches ) run( 'kill -HUP ' + pids ) for switch in switches: - switch.shell = None + switch.terminate() return switches diff --git a/mininet/test/test_switchdpidassignment.py b/mininet/test/test_switchdpidassignment.py index 9364e64..62ef493 100755 --- a/mininet/test/test_switchdpidassignment.py +++ b/mininet/test/test_switchdpidassignment.py @@ -30,10 +30,10 @@ class TestSwitchDpidAssignmentOVS( unittest.TestCase ): def testDefaultDpid( self ): """Verify that the default dpid is assigned using a valid provided canonical switchname if no dpid is passed in switch creation.""" - switch = Mininet( Topo(), - self.switchClass, - Host, Controller ).addSwitch( 's1' ) + net = Mininet( Topo(), self.switchClass, Host, Controller ) + switch = net.addSwitch( 's1' ) self.assertEqual( switch.defaultDpid(), switch.dpid ) + net.stop() def dpidFrom( self, num ): "Compute default dpid from number" @@ -44,31 +44,34 @@ class TestSwitchDpidAssignmentOVS( unittest.TestCase ): """Verify that Switch dpid is the actual dpid assigned if dpid is passed in switch creation.""" dpid = self.dpidFrom( 0xABCD ) - switch = Mininet( Topo(), self.switchClass, - Host, Controller ).addSwitch( - 's1', dpid=dpid ) + net = Mininet( Topo(), self.switchClass, Host, Controller ) + switch = net.addSwitch( 's1', dpid=dpid ) self.assertEqual( switch.dpid, dpid ) + net.stop() def testDefaultDpidAssignmentFailure( self ): """Verify that Default dpid assignment raises an Exception if the name of the switch does not contin a digit. Also verify the exception message.""" + net = Mininet( Topo(), self.switchClass, Host, Controller ) with self.assertRaises( Exception ) as raises_cm: - Mininet( Topo(), self.switchClass, - Host, Controller ).addSwitch( 'A' ) - self.assertEqual(raises_cm.exception.message, 'Unable to derive ' + net.addSwitch( 'A' ) + self.assertTrue( 'Unable to derive ' 'default datapath ID - please either specify a dpid ' - 'or use a canonical switch name such as s23.') + 'or use a canonical switch name such as s23.' + in str( raises_cm.exception ) ) + net.stop() def testDefaultDpidLen( self ): """Verify that Default dpid length is 16 characters consisting of 16 - len(hex of first string of contiguous digits passed in switch name) 0's followed by hex of first string of contiguous digits passed in switch name.""" - switch = Mininet( Topo(), self.switchClass, - Host, Controller ).addSwitch( 's123' ) - + net = Mininet( Topo(), self.switchClass, Host, Controller ) + switch = net.addSwitch( 's123' ) self.assertEqual( switch.dpid, self.dpidFrom( 123 ) ) + net.stop() + class OVSUser( OVSSwitch): "OVS User Switch convenience class" @@ -95,3 +98,4 @@ class testSwitchUserspace( TestSwitchDpidAssignmentOVS ): if __name__ == '__main__': setLogLevel( 'warning' ) unittest.main() + cleanup() diff --git a/mininet/test/test_walkthrough.py b/mininet/test/test_walkthrough.py index 8738537..c322c13 100755 --- a/mininet/test/test_walkthrough.py +++ b/mininet/test/test_walkthrough.py @@ -7,13 +7,13 @@ TODO: missing xterm test """ import unittest -import pexpect import os import re -from mininet.util import quietRun +from mininet.util import quietRun, pexpect from distutils.version import StrictVersion from time import sleep + def tsharkVersion(): "Return tshark version" versionStr = quietRun( 'tshark -v' ) @@ -95,7 +95,8 @@ class testWalkthrough( unittest.TestCase ): p = pexpect.spawn( 'mn' ) p.expect( self.prompt ) # Third pattern is a local interface beginning with 'eth' or 'en' - interfaces = [ 'h1-eth0', 's1-eth1', r'[^-](eth|en)\w*\d', 'lo', + interfaces = [ r'h1-eth0[:\s]', r's1-eth1[:\s]', + r'[^-](eth|en)\w*\d[:\s]', r'lo[:\s]', self.prompt ] # h1 ifconfig p.sendline( 'h1 ifconfig -a' ) @@ -122,7 +123,7 @@ class testWalkthrough( unittest.TestCase ): ifcount += 1 else: break - self.assertTrue( ifcount >= 3, 'Missing interfaces on s1') + self.assertTrue( ifcount <= 3, 'Missing interfaces on s1') # h1 ps p.sendline( "h1 ps -a | egrep -v 'ps|grep'" ) p.expect( self.prompt ) @@ -156,9 +157,13 @@ class testWalkthrough( unittest.TestCase ): def testSimpleHTTP( self ): "Start an HTTP server on h1 and wget from h2" + if 'Python 2' in quietRun( 'python --version' ): + httpserver = 'SimpleHTTPServer' + else: + httpserver = 'http.server' p = pexpect.spawn( 'mn' ) p.expect( self.prompt ) - p.sendline( 'h1 python -m SimpleHTTPServer 80 &' ) + p.sendline( 'h1 python -m %s 80 &' % httpserver ) # The walkthrough doesn't specify a delay here, and # we also don't read the output (also a possible problem), # but for now let's wait a couple of seconds to make @@ -222,8 +227,8 @@ class testWalkthrough( unittest.TestCase ): p.expect( r'rtt min/avg/max/mdev = ' r'([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms' ) delay = float( p.match.group( 2 ) ) - self.assertTrue( delay > 40, 'Delay < 40ms' ) - self.assertTrue( delay < 45, 'Delay > 40ms' ) + self.assertTrue( delay >= 40, 'Delay < 40ms' ) + self.assertTrue( delay <= 50, 'Delay > 50ms' ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() @@ -260,7 +265,7 @@ class testWalkthrough( unittest.TestCase ): p.expect( self.prompt ) for i in range( 1, 3 ): p.sendline( 'h%d ifconfig' % i ) - p.expect( 'HWaddr 00:00:00:00:00:0%d' % i ) + p.expect( r'\s00:00:00:00:00:0%d\s' % i ) p.expect( self.prompt ) p.sendline( 'exit' ) p.expect( pexpect.EOF ) @@ -286,7 +291,9 @@ class testWalkthrough( unittest.TestCase ): "Test running user switch in its own namespace" p = pexpect.spawn( 'mn --innamespace --switch user' ) p.expect( self.prompt ) - interfaces = [ 'h1-eth0', 's1-eth1', '[^-]eth0', 'lo', self.prompt ] + interfaces = [ r'h1-eth0[:\s]', r's1-eth1[:\s]', + r'[^-](eth|en)\w*\d[:\s]', r'lo[:\s]', + self.prompt ] p.sendline( 's1 ifconfig -a' ) ifcount = 0 while True: diff --git a/mininet/topo.py b/mininet/topo.py index 629632f..bc60c05 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -57,12 +57,12 @@ class MultiGraph( object ): def edges_iter( self, data=False, keys=False ): "Iterator: return graph edges, optionally with data and keys" - for src, entry in self.edge.iteritems(): - for dst, entrykeys in entry.iteritems(): + for src, entry in self.edge.items(): + for dst, entrykeys in entry.items(): if src > dst: # Skip duplicate edges continue - for k, attrs in entrykeys.iteritems(): + for k, attrs in entrykeys.items(): if data: if keys: yield( src, dst, k, attrs ) diff --git a/mininet/util.py b/mininet/util.py index 6cccddc..cb09f27 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -12,6 +12,39 @@ from fcntl import fcntl, F_GETFL, F_SETFL from os import O_NONBLOCK import os from functools import partial +import sys + +# Python 2/3 compatibility +Python3 = sys.version_info[0] == 3 +BaseString = str if Python3 else getattr( str, '__base__' ) +Encoding = 'utf-8' if Python3 else None +def decode( s ): + "Decode a byte string if needed for Python 3" + return s.decode( Encoding ) if Python3 else s +def encode( s ): + "Encode a byte string if needed for Python 3" + return s.encode( Encoding ) if Python3 else s +try: + # pylint: disable=import-error + oldpexpect = None + import pexpect as oldpexpect + # pylint: enable=import-error + + class Pexpect( object ): + "Custom pexpect that is compatible with str" + @staticmethod + def spawn( *args, **kwargs): + "pexpect.spawn that is compatible with str" + if Python3 and 'encoding' not in kwargs: + kwargs.update( encoding='utf-8' ) + return oldpexpect.spawn( *args, **kwargs ) + + def __getattr__( self, name ): + return getattr( oldpexpect, name ) + pexpect = Pexpect() +except ImportError: + pass + # Command execution support @@ -57,7 +90,7 @@ def oldQuietRun( *cmd ): # This is a bit complicated, but it enables us to # monitor command output as it is happening -# pylint: disable=too-many-branches +# pylint: disable=too-many-branches,too-many-statements def errRun( *cmd, **kwargs ): """Run a command and return stdout, stderr and return code cmd: string or list of command and args @@ -98,6 +131,8 @@ def errRun( *cmd, **kwargs ): f = fdtofile[ fd ] if event & POLLIN: data = f.read( 1024 ) + if Python3: + data = data.decode( Encoding ) if echo: output( data ) if f == popen.stdout: @@ -116,6 +151,10 @@ def errRun( *cmd, **kwargs ): poller.unregister( fd ) returncode = popen.wait() + # Python 3 complains if we don't explicitly close these + popen.stdout.close() + if stderr == PIPE: + popen.stderr.close() debug( out, err, returncode ) return out, err, returncode # pylint: enable=too-many-branches @@ -374,7 +413,7 @@ def pmonitor(popens, timeoutms=500, readline=True, terminates: when all EOFs received""" poller = poll() fdToHost = {} - for host, popen in popens.iteritems(): + for host, popen in popens.items(): fd = popen.stdout.fileno() fdToHost[ fd ] = host poller.register( fd, POLLIN ) @@ -382,6 +421,13 @@ def pmonitor(popens, timeoutms=500, readline=True, # Use non-blocking reads flags = fcntl( fd, F_GETFL ) fcntl( fd, F_SETFL, flags | O_NONBLOCK ) + + def readit( f ): + "Helper function - read line or data" + # Note this will block if readline is True + line = f.readline() if readline else f.read( readmax ) + return decode( line ) + while popens: fds = poller.poll( timeoutms ) if fds: @@ -389,15 +435,15 @@ def pmonitor(popens, timeoutms=500, readline=True, host = fdToHost[ fd ] popen = popens[ host ] if event & POLLIN: - 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 ) + line = readit( popen.stdout ) yield host, line - # Check for EOF - elif event & POLLHUP: + if event & POLLHUP: + while True: + # Drain buffer + line = readit( popen.stdout ) + yield host, line + if line == '': + break poller.unregister( fd ) del popens[ host ] else: @@ -460,7 +506,7 @@ def fixLimits(): def mountCgroups(): "Make sure cgroups file system is mounted" - mounts = quietRun( 'cat /proc/mounts' ) + mounts = quietRun( 'grep cgroup /proc/mounts' ) cgdir = '/sys/fs/cgroup' csdir = cgdir + '/cpuset' if ('cgroup %s' % cgdir not in mounts and @@ -600,7 +646,7 @@ def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ): if not runCmd( 'which telnet' ): raise Exception('Could not find telnet' ) # pylint: disable=maybe-no-member - serverIP = server if isinstance( server, basestring ) else server.IP() + serverIP = server if isinstance( server, BaseString ) else server.IP() cmd = ( 'echo A | telnet -e A %s %s' % ( serverIP, port ) ) time = 0 result = runCmd( cmd ) diff --git a/util/install.sh b/util/install.sh index 749accc..492c46c 100755 --- a/util/install.sh +++ b/util/install.sh @@ -102,6 +102,14 @@ function version_ge { [ "$1" == "$latest" ] } +# Attempt to identify Python version +PYTHON=${PYTHON:-python} +if $PYTHON --version |& grep 'Python 2' > /dev/null; then + PYTHON_VERSION=2; PYPKG=python +else + PYTHON_VERSION=3; PYPKG=python3 +fi +echo "${PYTHON} is version ${PYTHON_VERSION}" # Kernel Deb pkg to be removed: KERNEL_IMAGE_OLD=linux-image-2.6.26-33-generic @@ -145,19 +153,20 @@ function mn_deps { $install gcc make socat psmisc xterm openssh-clients iperf \ iproute telnet python-setuptools libcgroup-tools \ ethtool help2man pyflakes pylint python-pep8 python-pexpect - elif [ "$DIST" = "SUSE LINUX" ]; then + elif [ "$DIST" = "SUSE LINUX" ]; then $install gcc make socat psmisc xterm openssh iperf \ - iproute telnet python-setuptools libcgroup-tools \ - ethtool help2man python-pyflakes python3-pylint python-pep8 python-pexpect - else + iproute telnet ${PYPKG}-setuptools libcgroup-tools \ + ethtool help2man python-pyflakes python3-pylint \ + python-pep8 ${PYPKG}-pexpect ${PYPKG}-tk + else # Debian/Ubuntu $install gcc make socat psmisc xterm ssh iperf iproute2 telnet \ - python-setuptools cgroup-bin ethtool help2man \ - pyflakes pylint pep8 python-pexpect + cgroup-bin ethtool help2man pyflakes pylint pep8 \ + ${PYPKG}-setuptools ${PYPKG}-pexpect ${PYPKG}-tk fi echo "Installing Mininet core" pushd $MININET_DIR/mininet - sudo make install + sudo PYTHON=${PYTHON} make install popd } diff --git a/util/versioncheck.py b/util/versioncheck.py index c418bf9..0a4af2b 100755 --- a/util/versioncheck.py +++ b/util/versioncheck.py @@ -1,14 +1,19 @@ #!/usr/bin/python from subprocess import check_output as co -from sys import exit +from sys import exit, version_info + +def run(*args, **kwargs): + "Run co and decode for python3" + result = co(*args, **kwargs) + return result.decode() if version_info[ 0 ] >= 3 else result # Actually run bin/mn rather than importing via python path -version = 'Mininet ' + co( 'PYTHONPATH=. bin/mn --version 2>&1', shell=True ) +version = 'Mininet ' + run( 'PYTHONPATH=. bin/mn --version 2>&1', shell=True ) version = version.strip() # Find all Mininet path references -lines = co( "egrep -or 'Mininet [0-9\.\+]+\w*' *", shell=True ) +lines = run( "egrep -or 'Mininet [0-9\.\+]+\w*' *", shell=True ) error = False