Python 3 Compatibility (Merge pull request #817 from lantz/py3-compat)

Changes for compatibility with Python 3.

The approach is to make as few changes as possible to maintain compatibility with both Python 2 and Python 3.

We use whatever python is installed, and also support a PYTHON environment variable for installing another version.

For simplicity, we provide mininet.util.pexpect which works out of the box with Python 3 utf-8 strings.

Thanks to @cuihantao for looking at this as well and also for changes to MiniEdit.

Closes #794
This commit is contained in:
lantz
2018-07-26 14:03:34 -07:00
committed by GitHub
45 changed files with 260 additions and 151 deletions
+15 -11
View File
@@ -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
+4 -3
View File
@@ -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)
+6 -4
View File
@@ -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!) ' ) )
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+5 -4
View File
@@ -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()
+31 -16
View File
@@ -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 )
+3 -2
View File
@@ -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, ''
+7 -3
View File
@@ -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' )
+1 -1
View File
@@ -5,7 +5,7 @@ Tests for bind.py
"""
import unittest
import pexpect
from mininet.util import pexpect
class testBind( unittest.TestCase ):
+1 -1
View File
@@ -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 ):
+1 -1
View File
@@ -5,7 +5,7 @@ Tests for controllers.py and controllers2.py
"""
import unittest
import pexpect
from mininet.util import pexpect
class testControllers( unittest.TestCase ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for controlnet.py
"""
import unittest
import pexpect
from mininet.util import pexpect
class testControlNet( unittest.TestCase ):
+1 -1
View File
@@ -15,7 +15,7 @@ cfs 10% 1.29e+09
"""
import unittest
import pexpect
from mininet.util import pexpect
import sys
class testCPU( unittest.TestCase ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for emptynet.py
"""
import unittest
import pexpect
from mininet.util import pexpect
class testEmptyNet( unittest.TestCase ):
+1 -1
View File
@@ -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
+1 -1
View File
@@ -5,7 +5,7 @@ Test for intfOptions.py
"""
import unittest
import pexpect
from mininet.util import pexpect
import sys
class testIntfOptions( unittest.TestCase ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for limit.py
"""
import unittest
import pexpect
from mininet.util import pexpect
import sys
class testLimit( unittest.TestCase ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for linearbandwidth.py
"""
import unittest
import pexpect
from mininet.util import pexpect
import sys
class testLinearBandwidth( unittest.TestCase ):
+1 -1
View File
@@ -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 ):
+1 -1
View File
@@ -6,7 +6,7 @@ validates mininet interfaces against systems interfaces
'''
import unittest
import pexpect
from mininet.util import pexpect
class testMultiLink( unittest.TestCase ):
+1 -1
View File
@@ -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 ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for multipoll.py
"""
import unittest
import pexpect
from mininet.util import pexpect
class testMultiPoll( unittest.TestCase ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for multitest.py
"""
import unittest
import pexpect
from mininet.util import pexpect
class testMultiTest( unittest.TestCase ):
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 ):
+1 -1
View File
@@ -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
+1 -1
View File
@@ -5,7 +5,7 @@ Test for popen.py and popenpoll.py
"""
import unittest
import pexpect
from mininet.util import pexpect
class testPopen( unittest.TestCase ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for scratchnet.py
"""
import unittest
import pexpect
from mininet.util import pexpect
class testScratchNet( unittest.TestCase ):
+2 -2
View File
@@ -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 )
+1 -1
View File
@@ -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 ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for tree1024.py
"""
import unittest
import pexpect
from mininet.util import pexpect
import sys
class testTree1024( unittest.TestCase ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for treeping64.py
"""
import unittest
import pexpect
from mininet.util import pexpect
import sys
class testTreePing64( unittest.TestCase ):
+1 -1
View File
@@ -5,7 +5,7 @@ Test for vlanhost.py
"""
import unittest
import pexpect
from mininet.util import pexpect
import sys
from mininet.util import quietRun
+3 -3
View File
@@ -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:
+2 -6
View File
@@ -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
+3 -3
View File
@@ -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():
+7 -5
View File
@@ -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 )
+28 -16
View File
@@ -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 <intfname> via <gw-ip> ...}"""
# 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
+17 -13
View File
@@ -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()
+16 -9
View File
@@ -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:
+3 -3
View File
@@ -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 )
+58 -12
View File
@@ -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 )
+16 -7
View File
@@ -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
}
+8 -3
View File
@@ -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