Kinder, gentler Python 3 compatibility

Looking at trying to minimize code changes for Python 3.

For now we are keeping byte str() in py2 and unicode str()
in py3. It's a pain to rewrite all string code to usee
u'' for py2 compatibility. However we may need to revisit
this for pexpect().

Created compatibility wrappers in util.py:

BaseString (replacement for basestring)
decode() (optionally decode utf-8 for py3)
encode() (optionally encode utf-8 for py3)

Also we've switched from the .iter*() flavors to their
static alternatives for py2 (note they are iterators in py3!)

Changed util.errRun and clean.sh to call decode()
This commit is contained in:
Bob Lantz
2018-07-25 19:44:44 -07:00
parent 26b3959968
commit 88cbf4ec42
8 changed files with 44 additions and 27 deletions
+2 -2
View File
@@ -256,7 +256,7 @@ class MininetRunner( object ):
opts.add_option( '--arp', action='store_true', 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', opts.add_option( '--verbosity', '-v', type='choice',
choices=LEVELS.keys(), default = 'info', choices=list( LEVELS.keys() ), default = 'info',
help = '|'.join( LEVELS.keys() ) ) help = '|'.join( LEVELS.keys() ) )
opts.add_option( '--innamespace', action='store_true', opts.add_option( '--innamespace', action='store_true',
default=False, help='sw and ctrl in namespace?' ) default=False, help='sw and ctrl in namespace?' )
@@ -286,7 +286,7 @@ class MininetRunner( object ):
metavar='server1,server2...', metavar='server1,server2...',
help=( 'run on multiple servers (experimental!)' ) ) help=( 'run on multiple servers (experimental!)' ) )
opts.add_option( '--placement', type='choice', opts.add_option( '--placement', type='choice',
choices=PLACEMENT.keys(), default='block', choices=list( PLACEMENT.keys() ), default='block',
metavar='block|random', metavar='block|random',
help=( 'node placement for --cluster ' help=( 'node placement for --cluster '
'(experimental!) ' ) ) '(experimental!) ' ) )
+3 -3
View File
@@ -16,12 +16,13 @@ import time
from mininet.log import info from mininet.log import info
from mininet.term import cleanUpScreens from mininet.term import cleanUpScreens
from mininet.util import decode
def sh( cmd ): def sh( cmd ):
"Print a command and send it to the shell" "Print a command and send it to the shell"
info( cmd + '\n' ) 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 ): def killprocs( pattern ):
"Reliably terminate processes matching a pattern (including args)" "Reliably terminate processes matching a pattern (including args)"
@@ -76,7 +77,6 @@ class Cleanup( object ):
for dp in dps: for dp in dps:
if dp: if dp:
sh( 'dpctl deldp ' + dp ) sh( 'dpctl deldp ' + dp )
info( "*** Removing OVS datapaths\n" ) info( "*** Removing OVS datapaths\n" )
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines() dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
if dps: if dps:
+1 -1
View File
@@ -164,7 +164,7 @@ class Intf( object ):
method: config method name method: config method name
param: arg=value (ignore if value=None) param: arg=value (ignore if value=None)
value may also be list or dict""" value may also be list or dict"""
name, value = param.items()[ 0 ] name, value = list( param.items() )[ 0 ]
f = getattr( self, method, None ) f = getattr( self, method, None )
if not f or value is None: if not f or value is None:
return return
+3 -3
View File
@@ -1,6 +1,6 @@
"Module dependency utility functions for Mininet." "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 mininet.log import info, error, debug
from os import environ 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""" add: string or list of module names to add, if not already loaded"""
subtract = subtract if subtract is not None else [] subtract = subtract if subtract is not None else []
add = add if add is not None else [] add = add if add is not None else []
if isinstance( subtract, basestring ): if isinstance( subtract, BaseString ):
subtract = [ subtract ] subtract = [ subtract ]
if isinstance( add, basestring ): if isinstance( add, BaseString ):
add = [ add ] add = [ add ]
for mod in subtract: for mod in subtract:
if mod in lsmod(): if mod in lsmod():
+3 -3
View File
@@ -104,7 +104,7 @@ from mininet.nodelib import NAT
from mininet.link import Link, Intf from mininet.link import Link, Intf
from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot, from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot,
macColonHex, ipStr, ipParse, netParse, ipAdd, macColonHex, ipStr, ipParse, netParse, ipAdd,
waitListening ) waitListening, BaseString )
from mininet.term import cleanUpScreens, makeTerms from mininet.term import cleanUpScreens, makeTerms
# Mininet version: should be consistent with README and LICENSE # Mininet version: should be consistent with README and LICENSE
@@ -383,8 +383,8 @@ class Mininet( object ):
params: additional link params (optional) params: additional link params (optional)
returns: link object""" returns: link object"""
# Accept node objects or names # Accept node objects or names
node1 = node1 if not isinstance( node1, basestring ) else self[ node1 ] node1 = node1 if not isinstance( node1, BaseString ) else self[ node1 ]
node2 = node2 if not isinstance( node2, basestring ) else self[ node2 ] node2 = node2 if not isinstance( node2, BaseString ) else self[ node2 ]
options = dict( params ) options = dict( params )
# Port is optional # Port is optional
if port1 is not None: if port1 is not None:
+14 -11
View File
@@ -62,7 +62,8 @@ from time import sleep
from mininet.log import info, error, warn, debug from mininet.log import info, error, warn, debug
from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin, from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
numCores, retry, mountCgroups ) numCores, retry, mountCgroups, BaseString, decode,
encode )
from mininet.moduledeps import moduleDeps, pathCheck, TUN from mininet.moduledeps import moduleDeps, pathCheck, TUN
from mininet.link import Link, Intf, TCIntf, OVSIntf from mininet.link import Link, Intf, TCIntf, OVSIntf
from re import findall from re import findall
@@ -144,7 +145,9 @@ class Node( object ):
master, slave = pty.openpty() master, slave = pty.openpty()
self.shell = self._popen( cmd, stdin=slave, stdout=slave, stderr=slave, self.shell = self._popen( cmd, stdin=slave, stdout=slave, stderr=slave,
close_fds=False ) 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.stdout = self.stdin
self.pid = self.shell.pid self.pid = self.shell.pid
self.pollOut = select.poll() self.pollOut = select.poll()
@@ -171,7 +174,7 @@ class Node( object ):
def mountPrivateDirs( self ): def mountPrivateDirs( self ):
"mount private directories" "mount private directories"
# Avoid expanding a string into a list of chars # 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: for directory in self.privateDirs:
if isinstance( directory, tuple ): if isinstance( directory, tuple ):
# mount given private directory # mount given private directory
@@ -218,7 +221,7 @@ class Node( object ):
maxbytes: maximum number of bytes to return""" maxbytes: maximum number of bytes to return"""
count = len( self.readbuf ) count = len( self.readbuf )
if count < maxbytes: if count < maxbytes:
data = os.read( self.stdout.fileno(), maxbytes - count ) data = decode( os.read( self.stdout.fileno(), maxbytes - count ) )
self.readbuf += data self.readbuf += data
if maxbytes >= len( self.readbuf ): if maxbytes >= len( self.readbuf ):
result = self.readbuf result = self.readbuf
@@ -242,7 +245,7 @@ class Node( object ):
def write( self, data ): def write( self, data ):
"""Write data to node. """Write data to node.
data: string""" data: string"""
os.write( self.stdin.fileno(), data ) os.write( self.stdin.fileno(), encode( data ) )
def terminate( self ): def terminate( self ):
"Send kill signal to Node and clean up after it." "Send kill signal to Node and clean up after it."
@@ -376,7 +379,7 @@ class Node( object ):
if isinstance( args[ 0 ], list ): if isinstance( args[ 0 ], list ):
# popen([cmd, arg1, arg2...]) # popen([cmd, arg1, arg2...])
cmd = args[ 0 ] cmd = args[ 0 ]
elif isinstance( args[ 0 ], basestring ): elif isinstance( args[ 0 ], BaseString ):
# popen("cmd arg1 arg2...") # popen("cmd arg1 arg2...")
cmd = args[ 0 ].split() cmd = args[ 0 ].split()
else: else:
@@ -462,7 +465,7 @@ class Node( object ):
""" """
if not intf: if not intf:
return self.defaultIntf() return self.defaultIntf()
elif isinstance( intf, basestring): elif isinstance( intf, BaseString):
return self.nameToIntf[ intf ] return self.nameToIntf[ intf ]
else: else:
return intf return intf
@@ -514,7 +517,7 @@ class Node( object ):
"""Set the default route to go through intf. """Set the default route to go through intf.
intf: Intf or {dev <intfname> via <gw-ip> ...}""" intf: Intf or {dev <intfname> via <gw-ip> ...}"""
# Note setParam won't call us if intf is none # 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 params = intf
else: else:
params = 'dev %s' % intf params = 'dev %s' % intf
@@ -561,7 +564,7 @@ class Node( object ):
method: config method name method: config method name
param: arg=value (ignore if value=None) param: arg=value (ignore if value=None)
value may also be list or dict""" value may also be list or dict"""
name, value = param.items()[ 0 ] name, value = list( param.items() )[ 0 ]
if value is None: if value is None:
return return
f = getattr( self, method, None ) f = getattr( self, method, None )
@@ -610,7 +613,7 @@ class Node( object ):
def intfList( self ): def intfList( self ):
"List of our interfaces sorted by port number" "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 ): def intfNames( self ):
"The names of our interfaces sorted by port number" "The names of our interfaces sorted by port number"
@@ -1232,7 +1235,7 @@ class OVSSwitch( Switch ):
run( cmds, shell=True ) run( cmds, shell=True )
# Reapply link config if necessary... # Reapply link config if necessary...
for switch in switches: for switch in switches:
for intf in switch.intfs.itervalues(): for intf in switch.intfs.values():
if isinstance( intf, TCIntf ): if isinstance( intf, TCIntf ):
intf.config( **intf.params ) intf.config( **intf.params )
return switches return switches
+3 -3
View File
@@ -57,12 +57,12 @@ class MultiGraph( object ):
def edges_iter( self, data=False, keys=False ): def edges_iter( self, data=False, keys=False ):
"Iterator: return graph edges, optionally with data and keys" "Iterator: return graph edges, optionally with data and keys"
for src, entry in self.edge.iteritems(): for src, entry in self.edge.items():
for dst, entrykeys in entry.iteritems(): for dst, entrykeys in entry.items():
if src > dst: if src > dst:
# Skip duplicate edges # Skip duplicate edges
continue continue
for k, attrs in entrykeys.iteritems(): for k, attrs in entrykeys.items():
if data: if data:
if keys: if keys:
yield( src, dst, k, attrs ) yield( src, dst, k, attrs )
+15 -1
View File
@@ -12,6 +12,18 @@ from fcntl import fcntl, F_GETFL, F_SETFL
from os import O_NONBLOCK from os import O_NONBLOCK
import os import os
from functools import partial from functools import partial
import sys
# Python 2/3 compatibility
Python3 = sys.version_info[0] == 3
BaseString = str if Python3 else basestring
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
# Command execution support # Command execution support
@@ -98,6 +110,8 @@ def errRun( *cmd, **kwargs ):
f = fdtofile[ fd ] f = fdtofile[ fd ]
if event & POLLIN: if event & POLLIN:
data = f.read( 1024 ) data = f.read( 1024 )
if Python3:
data = data.decode( Encoding )
if echo: if echo:
output( data ) output( data )
if f == popen.stdout: if f == popen.stdout:
@@ -374,7 +388,7 @@ def pmonitor(popens, timeoutms=500, readline=True,
terminates: when all EOFs received""" terminates: when all EOFs received"""
poller = poll() poller = poll()
fdToHost = {} fdToHost = {}
for host, popen in popens.iteritems(): for host, popen in popens.items():
fd = popen.stdout.fileno() fd = popen.stdout.fileno()
fdToHost[ fd ] = host fdToHost[ fd ] = host
poller.register( fd, POLLIN ) poller.register( fd, POLLIN )