Fixes for cluster.py (#1035)

- use decode() for python 3 compatibility
- try to identify non-loopback interface for controllers
-- avoid hardwired 'eth0'
- use remoteServer consistently in tests
- pass tests on ubuntu 20/python 3
- pass hybrid test with hybrid python 2 and python 3 mininet
This commit is contained in:
lantz
2021-02-08 22:28:12 -08:00
committed by GitHub
parent 5d4ec1ab9e
commit 39ed456de2
+57 -31
View File
@@ -89,7 +89,7 @@ from mininet.link import Link, Intf
from mininet.net import Mininet from mininet.net import Mininet
from mininet.topo import LinearTopo from mininet.topo import LinearTopo
from mininet.topolib import TreeTopo from mininet.topolib import TreeTopo
from mininet.util import quietRun, errRun from mininet.util import quietRun, errRun, decode
from mininet.examples.clustercli import CLI from mininet.examples.clustercli import CLI
from mininet.log import setLogLevel, debug, info, error from mininet.log import setLogLevel, debug, info, error
from mininet.clean import addCleanupCallback from mininet.clean import addCleanupCallback
@@ -247,7 +247,7 @@ class RemoteMixin( object ):
result = '' result = ''
while True: while True:
poll = popen.poll() poll = popen.poll()
result += popen.stdout.read() result += decode( popen.stdout.read() )
if poll is not None: if poll is not None:
break break
return result return result
@@ -440,13 +440,16 @@ class RemoteLink( Link ):
# When we receive the character '@', it means that our # When we receive the character '@', it means that our
# tunnel should be set up # tunnel should be set up
debug( 'Waiting for tunnel to come up...\n' ) debug( 'Waiting for tunnel to come up...\n' )
ch = tunnel.stdout.read( 1 ) ch = decode( tunnel.stdout.read( 1 ) )
if ch != '@': if ch != '@':
raise Exception( 'makeTunnel:\n', ch += decode( tunnel.stdout.read() )
'Tunnel setup failed for', cmd = ' '.join( cmd )
'%s:%s' % ( node1, node1.dest ), 'to', raise Exception( 'makeTunnel:\n'
'%s:%s\n' % ( node2, node2.dest ), 'Tunnel setup failed for '
'command was:', cmd, '\n' ) '%s:%s' % ( node1, node1.dest ) + ' to '
'%s:%s\n' % ( node2, node2.dest ) +
'command was: %s' % cmd + '\n' +
'result was: ' + ch )
# 3. Move interfaces if necessary # 3. Move interfaces if necessary
for node in node1, node2: for node in node1, node2:
if not self.moveIntf( 'tap9', node ): if not self.moveIntf( 'tap9', node ):
@@ -848,27 +851,37 @@ class MininetCluster( Mininet ):
if cfile: if cfile:
config.setdefault( 'controlPath', cfile ) config.setdefault( 'controlPath', cfile )
@staticmethod
def isLoopback( ipaddr ):
"Is ipaddr an IPv4 loopback address?"
return ipaddr.startswith( '127.' )
# pylint: disable=arguments-differ,signature-differs # pylint: disable=arguments-differ,signature-differs
def addController( self, *args, **kwargs ): def addController( self, *args, **kwargs ):
"Patch to update IP address to global IP address" "Patch to update IP address to global IP address"
controller = Mininet.addController( self, *args, **kwargs ) controller = Mininet.addController( self, *args, **kwargs )
loopback = '127.0.0.1' controllerIP = controller.IP()
if ( not isinstance( controller, Controller ) or if ( not isinstance( controller, Controller ) or
controller.IP() != loopback ): not self.isLoopback( controller.IP() ) ):
return None return controller
# Find route to a different server IP address # Find route to a different server IP address
serverIPs = [ ip for ip in self.serverIP.values() serverIPs = [ ip for ip in self.serverIP.values()
if ip is not controller.IP() ] if ip != controllerIP ]
if not serverIPs: if not serverIPs:
return None # no remote servers - loopback is fine return None # no remote servers - loopback is fine
remoteIP = serverIPs[ 0 ] for remoteIP in serverIPs:
# Route should contain 'dev <intfname>' # Route should contain 'dev <intfname>'
route = controller.cmd( 'ip route get', remoteIP, route = controller.cmd( 'ip route get', remoteIP,
r'| egrep -o "dev\s[^[:space:]]+"' ) r'| egrep -o "dev\s[^[:space:]]+"' )
if not route: if not route:
raise Exception('addController: no route from', controller, raise Exception('addController: no route from', controller,
'to', remoteIP ) 'to', remoteIP )
intf = route.split()[ 1 ].strip() intf = route.split()[ 1 ].strip()
if intf != 'lo':
break
if intf == 'lo':
raise Exception( 'addController: could not find external '
'interface/IP for %s' % controller )
debug( 'adding', intf, 'to', controller ) debug( 'adding', intf, 'to', controller )
Intf( intf, node=controller ).updateIP() Intf( intf, node=controller ).updateIP()
debug( controller, 'IP address updated to', controller.IP() ) debug( controller, 'IP address updated to', controller.IP() )
@@ -883,7 +896,10 @@ class MininetCluster( Mininet ):
Mininet.buildFromTopo( self, *args, **kwargs ) Mininet.buildFromTopo( self, *args, **kwargs )
def testNsTunnels( remote='ubuntu2', link=RemoteGRELink ): # Default remote server for tests
remoteServer = 'ubuntu2'
def testNsTunnels( remote=remoteServer, link=RemoteGRELink ):
"Test tunnels between nodes in namespaces" "Test tunnels between nodes in namespaces"
net = Mininet( host=RemoteHost, link=link, waitConnected=True ) net = Mininet( host=RemoteHost, link=link, waitConnected=True )
h1 = net.addHost( 'h1') h1 = net.addHost( 'h1')
@@ -898,14 +914,14 @@ def testNsTunnels( remote='ubuntu2', link=RemoteGRELink ):
# This shows how node options may be used to manage # This shows how node options may be used to manage
# cluster placement using the net.add*() API # cluster placement using the net.add*() API
def testRemoteNet( remote='ubuntu2', link=RemoteGRELink ): def testRemoteNet( remote=remoteServer, link=RemoteGRELink ):
"Test remote Node classes" "Test remote Node classes"
info( '*** Remote Node Test\n' ) info( '*** Remote Node Test\n' )
net = Mininet( host=RemoteHost, switch=RemoteOVSSwitch, link=link, net = Mininet( host=RemoteHost, switch=RemoteOVSSwitch,
link=link, controller=ClusterController,
waitConnected=True ) waitConnected=True )
c0 = net.addController( 'c0' ) c0 = net.addController( 'c0' )
# Make sure controller knows its non-loopback address # Make sure controller knows its non-loopback address
Intf( 'eth0', node=c0 ).updateIP()
info( "*** Creating local h1\n" ) info( "*** Creating local h1\n" )
h1 = net.addHost( 'h1' ) h1 = net.addHost( 'h1' )
info( "*** Creating remote h2\n" ) info( "*** Creating remote h2\n" )
@@ -937,7 +953,7 @@ def testRemoteNet( remote='ubuntu2', link=RemoteGRELink ):
remoteHosts = [ 'h2' ] remoteHosts = [ 'h2' ]
remoteSwitches = [ 's2' ] remoteSwitches = [ 's2' ]
remoteServer = 'ubuntu2'
def HostPlacer( name, *args, **params ): def HostPlacer( name, *args, **params ):
"Custom Host() constructor which places hosts on servers" "Custom Host() constructor which places hosts on servers"
@@ -954,10 +970,21 @@ def SwitchPlacer( name, *args, **params ):
return RemoteOVSSwitch( name, *args, **params ) return RemoteOVSSwitch( name, *args, **params )
def ClusterController( *args, **kwargs): def ClusterController( *args, **kwargs):
"Custom Controller() constructor which updates its eth0 IP address" "Custom Controller() constructor which updates its intf IP address"
intf = kwargs.pop( 'intf', '' )
controller = Controller( *args, **kwargs ) controller = Controller( *args, **kwargs )
# Find out its IP address so that cluster switches can connect # Find out its IP address so that cluster switches can connect
Intf( 'eth0', node=controller ).updateIP() if not intf:
output = controller.cmd(
r"ip a | egrep -o '\w+:\s\w+'" ).split( '\n' )
for line in output:
intf = line.split()[ -1 ]
if intf != 'lo':
break
if intf == 'lo':
raise Exception( 'Could not find non-loopback interface'
'for %s' % controller )
Intf( intf, node=controller ).updateIP()
return controller return controller
def testRemoteTopo( link=RemoteGRELink ): def testRemoteTopo( link=RemoteGRELink ):
@@ -974,7 +1001,7 @@ def testRemoteTopo( link=RemoteGRELink ):
# do random switch placement rather than completely random # do random switch placement rather than completely random
# host placement. # host placement.
def testRemoteSwitches( remote='ubuntu2', link=RemoteGRELink ): def testRemoteSwitches( remote=remoteServer, link=RemoteGRELink ):
"Test with local hosts and remote switches" "Test with local hosts and remote switches"
servers = [ 'localhost', remote] servers = [ 'localhost', remote]
topo = TreeTopo( depth=4, fanout=2 ) topo = TreeTopo( depth=4, fanout=2 )
@@ -992,7 +1019,7 @@ def testRemoteSwitches( remote='ubuntu2', link=RemoteGRELink ):
# functions, for maximum ease of use. MininetCluster() also # functions, for maximum ease of use. MininetCluster() also
# pre-flights and multiplexes server connections. # pre-flights and multiplexes server connections.
def testMininetCluster( remote='ubuntu2', link=RemoteGRELink ): def testMininetCluster( remote=remoteServer, link=RemoteGRELink ):
"Test MininetCluster()" "Test MininetCluster()"
servers = [ 'localhost', remote ] servers = [ 'localhost', remote ]
topo = TreeTopo( depth=3, fanout=3 ) topo = TreeTopo( depth=3, fanout=3 )
@@ -1002,7 +1029,7 @@ def testMininetCluster( remote='ubuntu2', link=RemoteGRELink ):
net.pingAll() net.pingAll()
net.stop() net.stop()
def signalTest( remote='ubuntu2'): def signalTest( remote=remoteServer):
"Make sure hosts are robust to signals" "Make sure hosts are robust to signals"
h = RemoteHost( 'h0', server=remote ) h = RemoteHost( 'h0', server=remote )
h.shell.send_signal( SIGINT ) h.shell.send_signal( SIGINT )
@@ -1017,7 +1044,6 @@ def signalTest( remote='ubuntu2'):
if __name__ == '__main__': if __name__ == '__main__':
setLogLevel( 'info' ) setLogLevel( 'info' )
remoteServer = 'ubuntu2'
remoteLink = RemoteSSHLink remoteLink = RemoteSSHLink
testRemoteTopo(link=remoteLink) testRemoteTopo(link=remoteLink)
testNsTunnels( remote=remoteServer, link=remoteLink ) testNsTunnels( remote=remoteServer, link=remoteLink )