diff --git a/examples/cluster.py b/examples/cluster.py index afa525f..c388f36 100755 --- a/examples/cluster.py +++ b/examples/cluster.py @@ -287,8 +287,8 @@ class RemoteMixin( object ): return super( RemoteMixin, self).popen( *args, tt=False, **kwargs ) def addIntf( self, *args, **kwargs ): - "Override: use RemoteLink.moveIntf" - kwargs.update( moveIntfFn=RemoteLink.moveIntf ) + # "Override: use RemoteLink.moveIntf" + # kwargs.update( moveIntfFn=RemoteLink.moveIntf ) return super( RemoteMixin, self).addIntf( *args, **kwargs ) @@ -401,25 +401,23 @@ class RemoteLink( Link ): printError: if true, print error""" intf = str( intf ) cmd = 'ip link set %s netns %s' % ( intf, node.pid ) - node.rcmd( cmd ) - links = node.cmd( 'ip link show' ) - if not ' %s:' % intf in links: - if printError: - error( '*** Error: RemoteLink.moveIntf: ' + intf + - ' not successfully moved to ' + node.name + '\n' ) - return False + result = node.rcmd( cmd ) + if result: + raise Exception('error executing command %s' % cmd) return True def makeTunnel( self, node1, node2, intfname1, intfname2, addr1=None, addr2=None ): "Make a tunnel across switches on different servers" # We should never try to create a tunnel to ourselves! - assert node1.server != 'localhost' or node2.server != 'localhost' + assert node1.server != node2.server # And we can't ssh into this server remotely as 'localhost', # so try again swappping node1 and node2 if node2.server == 'localhost': return self.makeTunnel( node2, node1, intfname2, intfname1, addr2, addr1 ) + print( '\n*** Make SSH tunnel ' + node1.server + ':' + intfname1 + + ' == ' + node2.server + ':' + intfname2 ) # 1. Create tap interfaces for node in node1, node2: # For now we are hard-wiring tap9, which we will rename @@ -475,6 +473,87 @@ class RemoteLink( Link ): return result +class RemoteSSHLink( RemoteLink ): + def __init__(self, node1, node2, **kwargs): + RemoteLink.__init__( self, node1, node2, **kwargs ) + + +GRE_KEY = 0 +class RemoteGRELink( RemoteLink ): + def __init__(self, node1, node2, **kwargs): + RemoteLink.__init__( self, node1, node2, **kwargs ) + + def stop( self ): + "Stop this link" + if self.tunnel: + self.intf1.delete() + self.intf2.delete() + else: + Link.stop( self ) + self.tunnel = None + + def makeIntfPair( self, intfname1, intfname2, addr1=None, addr2=None, + node1=None, node2=None, deleteIntfs=True ): + """Create pair of interfaces + intfname1: name of interface 1 + intfname2: name of interface 2 + (override this method [and possibly delete()] + to change link type)""" + node1 = self.node1 if node1 is None else node1 + node2 = self.node2 if node2 is None else node2 + server1 = getattr( node1, 'server', 'localhost' ) + server2 = getattr( node2, 'server', 'localhost' ) + if server1 == server2: + # Link within same server + Link.makeIntfPair( intfname1, intfname2, addr1, addr2, + node1, node2, deleteIntfs=deleteIntfs ) + # Need to reduce the MTU of all emulated hosts to 1450 for GRE + # tunneling, otherwise packets larger than 1400 bytes cannot be + # successfully transmitted through the tunnel. + node1.cmd('ip link set dev %s mtu 1450' % intfname1) + node2.cmd('ip link set dev %s mtu 1450' % intfname2) + else: + # Otherwise, make a tunnel + self.makeTunnel( node1, node2, intfname1, intfname2, addr1, addr2 ) + self.tunnel = 1 + + def makeTunnel(self, node1, node2, intfname1, intfname2, + addr1=None, addr2=None): + "Make a tunnel across switches on different servers" + # We should never try to create a tunnel to ourselves! + assert node1.server != node2.server + if node2.server == 'localhost': + return self.makeTunnel( node2, node1, intfname2, intfname1, + addr2, addr1 ) + IP1, IP2 = node1.serverIP, node2.serverIP + # GRE tunnel needs to be set up with the IP of the local interface + # that connects the remote node, NOT '127.0.0.1' of localhost + if node1.server == 'localhost': + output = quietRun('ip route get %s' % node2.serverIP) + IP1 = output.split(' src ')[1].split()[0] + print( '\n*** Make GRE tunnel ' + node1.server + ':' + intfname1 + + ' == ' + node2.server + ':' + intfname2 ) + tun1 = 'local ' + IP1 + ' remote ' + IP2 + tun2 = 'local ' + IP2 + ' remote ' + IP1 + global GRE_KEY + GRE_KEY += 1 + for (node, intfname, addr, tun) in [(node1, intfname1, addr1, tun1), + (node2, intfname2, addr2, tun2)]: + node.rcmd('ip link delete ' + intfname) + result = node.rcmd('ip link add name ' + intfname + ' type gretap ' + + tun + ' ttl 64 key ' + str(GRE_KEY)) + if result: + raise Exception('error creating gretap on %s: %s' + % (node, result)) + if addr: + node.rcmd('ip link set %s address %s' % (intfname, addr)) + + node.rcmd('ip link set dev %s up' % intfname) + node.rcmd('ip link set dev %s mtu 1450' % intfname) + if not self.moveIntf(intfname, node): + raise Exception('interface move failed on node %s' % node) + + # Some simple placement algorithms for MininetCluster class Placer( object ): @@ -776,11 +855,11 @@ class MininetCluster( Mininet ): Mininet.buildFromTopo( self, *args, **kwargs ) -def testNsTunnels(): +def testNsTunnels( remote='ubuntu2', link=RemoteGRELink ): "Test tunnels between nodes in namespaces" - net = Mininet( host=RemoteHost, link=RemoteLink ) - h1 = net.addHost( 'h1' ) - h2 = net.addHost( 'h2', server='ubuntu2' ) + net = Mininet( host=RemoteHost, link=link ) + h1 = net.addHost( 'h1') + h2 = net.addHost( 'h2', server=remote ) net.addLink( h1, h2 ) net.start() net.pingAll() @@ -791,11 +870,10 @@ def testNsTunnels(): # This shows how node options may be used to manage # cluster placement using the net.add*() API -def testRemoteNet( remote='ubuntu2' ): +def testRemoteNet( remote='ubuntu2', link=RemoteGRELink ): "Test remote Node classes" print( '*** Remote Node Test' ) - net = Mininet( host=RemoteHost, switch=RemoteOVSSwitch, - link=RemoteLink ) + net = Mininet( host=RemoteHost, switch=RemoteOVSSwitch, link=link ) c0 = net.addController( 'c0' ) # Make sure controller knows its non-loopback address Intf( 'eth0', node=c0 ).updateIP() @@ -852,11 +930,11 @@ def ClusterController( *args, **kwargs): Intf( 'eth0', node=controller ).updateIP() return controller -def testRemoteTopo(): +def testRemoteTopo( link=RemoteGRELink ): "Test remote Node classes using Mininet()/Topo() API" topo = LinearTopo( 2 ) net = Mininet( topo=topo, host=HostPlacer, switch=SwitchPlacer, - link=RemoteLink, controller=ClusterController ) + link=link, controller=ClusterController ) net.start() net.pingAll() net.stop() @@ -866,18 +944,17 @@ def testRemoteTopo(): # do random switch placement rather than completely random # host placement. -def testRemoteSwitches(): +def testRemoteSwitches( remote='ubuntu2', link=RemoteGRELink ): "Test with local hosts and remote switches" - servers = [ 'localhost', 'ubuntu2'] + servers = [ 'localhost', remote] topo = TreeTopo( depth=4, fanout=2 ) - net = MininetCluster( topo=topo, servers=servers, + net = MininetCluster( topo=topo, servers=servers, link=link, placement=RoundRobinPlacer ) net.start() net.pingAll() net.stop() -# # For testing and demo purposes it would be nice to draw the # network graph and color it based on server. @@ -885,19 +962,19 @@ def testRemoteSwitches(): # functions, for maximum ease of use. MininetCluster() also # pre-flights and multiplexes server connections. -def testMininetCluster(): +def testMininetCluster( remote='ubuntu2', link=RemoteGRELink ): "Test MininetCluster()" - servers = [ 'localhost', 'ubuntu2' ] + servers = [ 'localhost', remote ] topo = TreeTopo( depth=3, fanout=3 ) - net = MininetCluster( topo=topo, servers=servers, + net = MininetCluster( topo=topo, servers=servers, link=link, placement=SwitchBinPlacer ) net.start() net.pingAll() net.stop() -def signalTest(): +def signalTest( remote='ubuntu2'): "Make sure hosts are robust to signals" - h = RemoteHost( 'h0', server='ubuntu1' ) + h = RemoteHost( 'h0', server=remote ) h.shell.send_signal( SIGINT ) h.shell.poll() if h.shell.returncode is None: @@ -906,10 +983,14 @@ def signalTest(): print( 'FAILURE:', h, 'exited with code', h.shell.returncode ) h.stop() + if __name__ == '__main__': setLogLevel( 'info' ) - # testRemoteTopo() - # testRemoteNet() - # testMininetCluster() - # testRemoteSwitches() - signalTest() + remoteServer = 'ubuntu2' + remoteLink = RemoteSSHLink + testRemoteTopo(link=remoteLink) + testNsTunnels( remote=remoteServer, link=remoteLink ) + testRemoteNet( remote=remoteServer, link=remoteLink) + testMininetCluster( remote=remoteServer, link=remoteLink) + testRemoteSwitches( remote=remoteServer, link=remoteLink) + signalTest( remote=remoteServer ) diff --git a/examples/clusterdemo.py b/examples/clusterdemo.py index 6e050c5..66d510e 100755 --- a/examples/clusterdemo.py +++ b/examples/clusterdemo.py @@ -3,6 +3,7 @@ "clusterdemo.py: demo of Mininet Cluster Edition prototype" from mininet.examples.cluster import MininetCluster, SwitchBinPlacer +from cluster import RemoteLink, RemoteSSHLink, RemoteGRELink from mininet.topolib import TreeTopo from mininet.log import setLogLevel from mininet.examples.clustercli import ClusterCLI as CLI @@ -11,7 +12,7 @@ def demo(): "Simple Demo of Cluster Mode" servers = [ 'localhost', 'ubuntu2', 'ubuntu3' ] topo = TreeTopo( depth=3, fanout=3 ) - net = MininetCluster( topo=topo, servers=servers, + net = MininetCluster( topo=topo, servers=servers, Link=RemoteLink, placement=SwitchBinPlacer ) net.start() CLI( net ) diff --git a/examples/clusterperf.py b/examples/clusterperf.py new file mode 100755 index 0000000..7531aec --- /dev/null +++ b/examples/clusterperf.py @@ -0,0 +1,22 @@ +#!/usr/bin/python + +"clusterperf.py compare the maximum throughput between SSH and GRE tunnels" + +from mininet.examples.cluster import RemoteSSHLink, RemoteGRELink, RemoteHost +from mininet.net import Mininet +from mininet.log import setLogLevel + +def perf(Link): + net = Mininet( host=RemoteHost, link=Link ) + h1 = net.addHost( 'h1') + h2 = net.addHost( 'h2', server='ubuntu2' ) + net.addLink( h1, h2 ) + net.start() + net.pingAll() + net.iperf() + net.stop() + +if __name__ == '__main__': + setLogLevel('info') + perf(RemoteSSHLink) + perf(RemoteGRELink)