Merge pull request #416 from mininet/devel/multitopo

Multi-link topology support
This commit is contained in:
lantz
2014-11-07 16:16:25 -08:00
5 changed files with 310 additions and 129 deletions
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/python
"""
This is a simple example that demonstrates multiple links
between nodes.
"""
from mininet.cli import CLI
from mininet.log import lg, info
from mininet.net import Mininet
from mininet.topo import Topo
def runMultiLink():
topo = simpleMultiLinkTopo( n=2 )
net = Mininet( topo=topo )
net.start()
CLI( net )
net.stop()
class simpleMultiLinkTopo( Topo ):
def __init__( self, n, **kwargs ):
Topo.__init__( self, **kwargs )
h1, h2 = self.addHost( 'h1' ), self.addHost( 'h2' )
s1 = self.addSwitch( 's1' )
for _ in range( n ):
self.addLink( s1, h1 )
self.addLink( s1, h2 )
if __name__ == '__main__':
lg.setLogLevel( 'info' )
runMultiLink()
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python
'''
Test for multiple links between nodes
validates mininet interfaces against systems interfaces
'''
import unittest
import pexpect
class testMultiLink( unittest.TestCase ):
prompt = 'mininet>'
def testMultiLink(self):
p = pexpect.spawn( 'python -m mininet.examples.multiLink' )
p.expect( self.prompt )
p.sendline( 'intfs' )
p.expect( 's(\d): lo' )
intfsOutput = p.before
# parse interfaces from mininet intfs, and store them in a list
hostToIntfs = intfsOutput.split( '\r\n' )[ 1:3 ]
intfList = []
for hostToIntf in hostToIntfs:
intfList += [ intf for intf in
hostToIntf.split()[1].split(',') ]
# get interfaces from system by running ifconfig on every host
sysIntfList = []
opts = [ 'h(\d)-eth(\d)', self.prompt ]
p.expect( self.prompt )
p.sendline( 'h1 ifconfig' )
while True:
p.expect( opts )
if p.after == self.prompt:
break
sysIntfList.append( p.after )
p.sendline( 'h2 ifconfig' )
while True:
p.expect( opts )
if p.after == self.prompt:
break
sysIntfList.append( p.after )
failMsg = ( 'The systems interfaces and mininet interfaces\n'
'are not the same' )
self.assertEqual( sysIntfList, intfList, msg=failMsg )
p.sendline( 'exit' )
p.wait()
if __name__ == '__main__':
unittest.main()
+30 -21
View File
@@ -321,25 +321,36 @@ class Mininet( object ):
"return (key,value) tuple list for every node in net"
return zip( self.keys(), self.values() )
@staticmethod
def randMac():
"Return a random, non-multicast MAC address"
return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
0x020000000000 )
def addLink( self, node1, node2, port1=None, port2=None,
cls=None, **params ):
""""Add a link from node1 to node2
node1: source node
node2: dest node
port1: source port
port2: dest port
node1: source node (or name)
node2: dest node (or name)
port1: source port (optional)
port2: dest port (optional)
cls: link class (optional)
params: additional link params (optional)
returns: link object"""
mac1 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 )
mac2 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 )
defaults = { 'port1': port1,
'port2': port2,
'addr1': mac1,
'addr2': mac2,
'intf': self.intf }
defaults.update( params )
if not cls:
cls = self.link
link = cls( node1, node2, **defaults )
# Accept node objects or names
node1 = node1 if type( node1 ) != str else self[ node1 ]
node2 = node2 if type( node2 ) != str else self[ node2 ]
options = dict( params )
# Port is optional
if port1 is not None:
options.setdefault( 'port1', port1 )
if port2 is not None:
options.setdefault( 'port2', port2 )
# Set default MAC - this should probably be in Link
options.setdefault( 'addr1', self.randMac() )
options.setdefault( 'addr2', self.randMac() )
cls = self.link if cls is None else cls
link = cls( node1, node2, **options )
self.links.append( link )
return link
@@ -397,12 +408,10 @@ class Mininet( object ):
info( switchName + ' ' )
info( '\n*** Adding links:\n' )
for srcName, dstName in topo.links(sort=True):
src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
params = topo.linkInfo( srcName, dstName )
srcPort, dstPort = topo.port( srcName, dstName )
self.addLink( src, dst, srcPort, dstPort, **params )
info( '(%s, %s) ' % ( src.name, dst.name ) )
for srcName, dstName, params in topo.links(
sort=True, withInfo=True ):
self.addLink( **params )
info( '(%s, %s) ' % ( srcName, dstName ) )
info( '\n' )
+189 -107
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
'''@package topo
"""@package topo
Network topology creation.
@@ -9,46 +9,95 @@ This package includes code to represent network topologies.
A Topo object can be a topology database for NOX, can represent a physical
setup for testing, and can even be emulated with the Mininet package.
'''
"""
from mininet.util import irange, natural, naturalSeq
class MultiGraph( object ):
"Utility class to track nodes and edges - replaces networkx.Graph"
"Utility class to track nodes and edges - replaces networkx.MultiGraph"
def __init__( self ):
self.data = {}
self.node = {}
self.edge = {}
def add_node( self, node ):
"Add node to graph"
self.data.setdefault( node, [] )
def add_node( self, node, attr_dict=None, **attrs):
"""Add node to graph
attr_dict: attribute dict (optional)
attrs: more attributes (optional)"""
attr_dict = {} if attr_dict is None else attr_dict
attr_dict.update( attrs )
self.node[ node ] = attr_dict
def add_edge( self, src, dest ):
"Add edge to graph"
src, dest = sorted( ( src, dest ) )
self.add_node( src )
self.add_node( dest )
self.data[ src ].append( dest )
def add_edge( self, src, dst, key=None, attr_dict=None, **attrs ):
"""Add edge to graph
key: optional key
attr_dict: optional attribute dict"""
attr_dict = {} if attr_dict is None else attr_dict
attr_dict.update( attrs )
self.node.setdefault( src, {} )
self.node.setdefault( dst, {} )
self.edge.setdefault( src, {} )
self.edge.setdefault( dst, {} )
self.edge[ src ].setdefault( dst, {} )
entry = self.edge[ dst ][ src ] = self.edge[ src ][ dst ]
# If no key, pick next ordinal number
if key is None:
keys = [ k for k in entry.keys() if type( k ) is int ]
key = max( [ 0 ] + keys ) + 1
entry[ key ] = attr_dict
return key
def nodes( self ):
"Return list of graph nodes"
return self.data.keys()
def nodes( self, data=False):
"""Return list of graph nodes
data: return list of ( node, attrs)"""
return self.node.items() if data else self.node.keys()
def edges( self ):
def edges_iter( self, data=False, keys=False ):
"Iterator: return graph edges"
for src in self.data.keys():
for dest in self.data[ src ]:
yield ( src, dest )
for src, entry in self.edge.iteritems():
for dst, keys in entry.iteritems():
if src > dst:
# Skip duplicate edges
continue
for k, attrs in keys.iteritems():
if data:
if keys:
yield( src, dst, k, attrs )
else:
yield( src, dst, attrs )
else:
if keys:
yield( src, dst, k )
else:
yield( src, dst )
def edges( self, data=False, keys=False ):
"Return list of graph edges"
return list( self.edges_iter( data=data, keys=keys ) )
def __getitem__( self, node ):
"Return link dict for the given node"
return self.data[node]
"Return link dict for given src node"
return self.edge[ node ]
def __len__( self ):
"Return the number of nodes"
return len( self.node )
def convertTo( self, cls, data=False, keys=False ):
"""Convert to a new object of networkx.MultiGraph-like class cls
data: include node and edge data
keys: include edge keys as well as edge data"""
g = cls()
g.add_nodes_from( self.nodes( data=data ) )
g.add_edges_from( self.edges( data=( data or keys ), keys=keys ) )
return g
class Topo(object):
class Topo( object ):
"Data center network representation for structured multi-trees."
def __init__(self, *args, **params):
def __init__( self, *args, **params ):
"""Topo object.
Optional named parameters:
hinfo: default host options
@@ -56,150 +105,182 @@ class Topo(object):
lopts: default link options
calls build()"""
self.g = MultiGraph()
self.node_info = {}
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
self.hopts = params.pop( 'hopts', {} )
self.sopts = params.pop( 'sopts', {} )
self.lopts = params.pop( 'lopts', {} )
self.ports = {} # ports[src][dst] is port on src that connects to dst
self.ports = {} # ports[src][dst][sport] is port on dst that connects to src
self.build( *args, **params )
def build( self, *args, **params ):
"Override this method to build your topology."
pass
def addNode(self, name, **opts):
def addNode( self, name, **opts ):
"""Add Node to graph.
name: name
opts: node options
returns: node name"""
self.g.add_node(name)
self.node_info[name] = opts
self.g.add_node( name, **opts )
return name
def addHost(self, name, **opts):
def addHost( self, name, **opts ):
"""Convenience method: Add host to graph.
name: host name
opts: host options
returns: host name"""
if not opts and self.hopts:
opts = self.hopts
return self.addNode(name, **opts)
return self.addNode( name, **opts )
def addSwitch(self, name, **opts):
def addSwitch( self, name, **opts ):
"""Convenience method: Add switch to graph.
name: switch name
opts: switch options
returns: switch name"""
if not opts and self.sopts:
opts = self.sopts
result = self.addNode(name, isSwitch=True, **opts)
result = self.addNode( name, isSwitch=True, **opts )
return result
def addLink(self, node1, node2, port1=None, port2=None,
**opts):
def addLink( self, node1, node2, port1=None, port2=None,
key=None, **opts ):
"""node1, node2: nodes to link together
port1, port2: ports (optional)
opts: link options (optional)
returns: link info key"""
if not opts and self.lopts:
opts = self.lopts
self.addPort(node1, node2, port1, port2)
key = tuple(self.sorted([node1, node2]))
self.link_info[key] = opts
self.g.add_edge(*key)
port1, port2 = self.addPort( node1, node2, port1, port2 )
opts.update( node1=node1, node2=node2, port1=port1, port2=port2 )
self.g.add_edge(node1, node2, key, opts )
return key
def addPort(self, src, dst, sport=None, dport=None):
'''Generate port mapping for new edge.
@param src source switch name
@param dst destination switch name
'''
self.ports.setdefault(src, {})
self.ports.setdefault(dst, {})
# New port: number of outlinks + base
src_base = 1 if self.isSwitch(src) else 0
dst_base = 1 if self.isSwitch(dst) else 0
if sport is None:
sport = len(self.ports[src]) + src_base
if dport is None:
dport = len(self.ports[dst]) + dst_base
self.ports[src][dst] = sport
self.ports[dst][src] = dport
def nodes(self, sort=True):
def nodes( self, sort=True ):
"Return nodes in graph"
if sort:
return self.sorted( self.g.nodes() )
else:
return self.g.nodes()
def isSwitch(self, n):
'''Returns true if node is a switch.'''
info = self.node_info[n]
return info and info.get('isSwitch', False)
def isSwitch( self, n ):
"Returns true if node is a switch."
return self.g.node[ n ].get( 'isSwitch', False )
def switches(self, sort=True):
'''Return switches.
sort: sort switches alphabetically
@return dpids list of dpids
'''
return [n for n in self.nodes(sort) if self.isSwitch(n)]
def switches( self, sort=True ):
"""Return switches.
sort: sort switches alphabetically
returns: dpids list of dpids"""
return [ n for n in self.nodes( sort ) if self.isSwitch( n ) ]
def hosts(self, sort=True):
'''Return hosts.
sort: sort hosts alphabetically
@return dpids list of dpids
'''
return [n for n in self.nodes(sort) if not self.isSwitch(n)]
def hosts( self, sort=True ):
"""Return hosts.
sort: sort hosts alphabetically
returns: list of hosts"""
return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ]
def links(self, sort=True):
'''Return links.
sort: sort links alphabetically
@return links list of name pairs
'''
if not sort:
return self.g.edges()
else:
links = [tuple(self.sorted(e)) for e in self.g.edges()]
return sorted( links, key=naturalSeq )
def iterLinks( self, withKeys=False, withInfo=False ):
"""Return links (iterator)
withKeys: return link keys
withInfo: return link info
returns: list of ( src, dst [,key, info ] )"""
for src, dst, key, info in self.g.edges_iter( data=True, keys=True ):
node1, node2 = info[ 'node1' ], info[ 'node2' ]
if withKeys:
if withInfo:
yield( node1, node2, key, info )
else:
yield( node1, node2, key )
else:
if withInfo:
yield( node1, node2, info )
else:
yield( node1, node2 )
def port(self, src, dst):
'''Get port number.
def links( self, sort=False, withKeys=False, withInfo=False ):
"""Return links
sort: sort links alphabetically, preserving (src, dst) order
withKeys: return link keys
withInfo: return link info
returns: list of ( src, dst [,key, info ] )"""
links = list( self.iterLinks( withKeys, withInfo ) )
if not sorted:
return links
# Ignore info when sorting
tupleSize = 3 if withKeys else 2
return sorted( links, key=( lambda l: naturalSeq( l[ :tupleSize ] ) ) )
@param src source switch name
@param dst destination switch name
@return tuple (src_port, dst_port):
src_port: port on source switch leading to the destination switch
dst_port: port on destination switch leading to the source switch
'''
if src in self.ports and dst in self.ports[src]:
assert dst in self.ports and src in self.ports[dst]
return self.ports[src][dst], self.ports[dst][src]
# This legacy port management mechanism is clunky and will probably
# be removed at some point.
def linkInfo( self, src, dst ):
"Return link metadata"
src, dst = self.sorted([src, dst])
return self.link_info[(src, dst)]
def addPort( self, src, dst, sport=None, dport=None ):
"""Generate port mapping for new edge.
src: source switch name
dst: destination switch name"""
# Initialize if necessary
ports = self.ports
ports.setdefault( src, {} )
ports.setdefault( dst, {} )
# New port: number of outlinks + base
if sport is None:
src_base = 1 if self.isSwitch( src ) else 0
sport = len( ports[ src ] ) + src_base
if dport is None:
dst_base = 1 if self.isSwitch( dst ) else 0
dport = len( ports[ dst ] ) + dst_base
ports[ src ][ sport ] = ( dst, dport )
ports[ dst ][ dport ] = ( src, sport )
return sport, dport
def setlinkInfo( self, src, dst, info ):
"Set link metadata"
src, dst = self.sorted([src, dst])
self.link_info[(src, dst)] = info
def port( self, src, dst ):
"""Get port numbers.
src: source switch name
dst: destination switch name
sport: optional source port (otherwise use lowest src port)
returns: tuple (sport, dport), where
sport = port on source switch leading to the destination switch
dport = port on destination switch leading to the source switch
Note that you can also look up ports using linkInfo()"""
# A bit ugly and slow vs. single-link implementation ;-(
ports = [ ( sport, entry[ 1 ] )
for sport, entry in self.ports[ src ].items()
if entry[ 0 ] == dst ]
return ports if len( ports ) != 1 else ports[ 0 ]
def _linkEntry( self, src, dst, key=None ):
"Helper function: return link entry and key"
entry = self.g[ src ][ dst ]
if key is None:
key = min( entry )
return entry, key
def linkInfo( self, src, dst, key=None ):
"Return link metadata dict"
entry, key = self._linkEntry( src, dst, key )
return entry[ key ]
def setlinkInfo( self, src, dst, info, key=None ):
"Set link metadata dict"
entry, key = self._linkEntry( src, dst, key )
entry[ key ] = info
def nodeInfo( self, name ):
"Return metadata (dict) for node"
info = self.node_info[ name ]
return info if info is not None else {}
return self.g.node[ name ]
def setNodeInfo( self, name, info ):
"Set metadata (dict) for node"
self.node_info[ name ] = info
self.g.node[ name ] = info
def convertTo( self, cls, data=True, keys=True ):
"""Convert to a new object of networkx.MultiGraph-like class cls
data: include node and edge data (default True)
keys: include edge keys as well as edge data (default True)"""
return self.g.convertTo( cls, data=data, keys=keys )
@staticmethod
def sorted( items ):
"Items sorted in natural (i.e. alphabetical) order"
return sorted(items, key=natural)
return sorted( items, key=natural )
class SingleSwitchTopo( Topo ):
@@ -228,6 +309,7 @@ class SingleSwitchReversedTopo( Topo ):
self.addLink( host, switch,
port1=0, port2=( k - h + 1 ) )
class LinearTopo( Topo ):
"Linear topology of k switches, with n hosts per switch."
+1 -1
View File
@@ -448,7 +448,7 @@ def natural( text ):
def num( s ):
"Convert text segment to int if necessary"
return int( s ) if s.isdigit() else s
return [ num( s ) for s in re.split( r'(\d+)', text ) ]
return [ num( s ) for s in re.split( r'(\d+)', str( text ) ) ]
def naturalSeq( t ):
"Natural sort key function for sequences"