First crack at fixing multiple links
* Makes MultiGraph more like networkx.multigraph * Adds converTo method * Synchronizes node1 with xxx1 in link options
This commit is contained in:
+27
-16
@@ -321,25 +321,36 @@ class Mininet( object ):
|
|||||||
"return (key,value) tuple list for every node in net"
|
"return (key,value) tuple list for every node in net"
|
||||||
return zip( self.keys(), self.values() )
|
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,
|
def addLink( self, node1, node2, port1=None, port2=None,
|
||||||
cls=None, **params ):
|
cls=None, paramDict=None, **params ):
|
||||||
""""Add a link from node1 to node2
|
""""Add a link from node1 to node2
|
||||||
node1: source node
|
node1: source node
|
||||||
node2: dest node
|
node2: dest node
|
||||||
port1: source port
|
port1: source port (optional)
|
||||||
port2: dest port
|
port2: dest port (optional)
|
||||||
|
cls: link class (optional)
|
||||||
|
paramDict: dictionary of additional link params (optional)
|
||||||
|
params: additional link params (optional)
|
||||||
returns: link object"""
|
returns: link object"""
|
||||||
mac1 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 )
|
mac1 = self.randMac()
|
||||||
mac2 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 )
|
mac2 = self.randMac()
|
||||||
defaults = { 'port1': port1,
|
paramDict = {} if paramDict is None else paramDict
|
||||||
'port2': port2,
|
paramDict.update( params )
|
||||||
'addr1': mac1,
|
# Ugly: try to ensure that node1 and node2 line up correctly with
|
||||||
'addr2': mac2,
|
# other link parameters
|
||||||
'intf': self.intf }
|
node1 = self[ paramDict.pop( 'node1', node1.name ) ]
|
||||||
defaults.update( params )
|
node2 = self[ paramDict.pop( 'node2', node2.name ) ]
|
||||||
if not cls:
|
paramDict.setdefault( 'port1', port1 )
|
||||||
cls = self.link
|
paramDict.setdefault( 'port2', port2 )
|
||||||
link = cls( node1, node2, **defaults )
|
paramDict.setdefault( 'addr1', mac1 )
|
||||||
|
paramDict.setdefault( 'addr2', mac2 )
|
||||||
|
cls = self.link if cls is None else cls
|
||||||
|
link = cls( node1, node2, **paramDict )
|
||||||
self.links.append( link )
|
self.links.append( link )
|
||||||
return link
|
return link
|
||||||
|
|
||||||
@@ -397,11 +408,11 @@ class Mininet( object ):
|
|||||||
info( switchName + ' ' )
|
info( switchName + ' ' )
|
||||||
|
|
||||||
info( '\n*** Adding links:\n' )
|
info( '\n*** Adding links:\n' )
|
||||||
for srcName, dstName in topo.links(sort=True):
|
for srcName, dstName in topo.links( sort=True ):
|
||||||
src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
|
src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
|
||||||
params = topo.linkInfo( srcName, dstName )
|
params = topo.linkInfo( srcName, dstName )
|
||||||
srcPort, dstPort = topo.port( srcName, dstName )
|
srcPort, dstPort = topo.port( srcName, dstName )
|
||||||
self.addLink( src, dst, srcPort, dstPort, **params )
|
self.addLink( src, dst, srcPort, dstPort, paramDict=params )
|
||||||
info( '(%s, %s) ' % ( src.name, dst.name ) )
|
info( '(%s, %s) ' % ( src.name, dst.name ) )
|
||||||
|
|
||||||
info( '\n' )
|
info( '\n' )
|
||||||
|
|||||||
+155
-101
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
'''@package topo
|
"""@package topo
|
||||||
|
|
||||||
Network topology creation.
|
Network topology creation.
|
||||||
|
|
||||||
@@ -9,46 +9,89 @@ This package includes code to represent network topologies.
|
|||||||
|
|
||||||
A Topo object can be a topology database for NOX, can represent a physical
|
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.
|
setup for testing, and can even be emulated with the Mininet package.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
from mininet.util import irange, natural, naturalSeq
|
from mininet.util import irange, natural, naturalSeq
|
||||||
|
|
||||||
class MultiGraph( object ):
|
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 ):
|
def __init__( self ):
|
||||||
self.data = {}
|
self.node = {}
|
||||||
|
self.edge = {}
|
||||||
|
self.edgeinfo = {}
|
||||||
|
|
||||||
def add_node( self, node ):
|
def add_node( self, node, attr_dict=None, **attrs):
|
||||||
"Add node to graph"
|
"""Add node to graph
|
||||||
self.data.setdefault( node, [] )
|
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 ):
|
def add_edge( self, src, dst, key=None, attr_dict=None, **attrs ):
|
||||||
"Add edge to graph"
|
"""Add edge to graph
|
||||||
src, dest = sorted( ( src, dest ) )
|
key: optional key
|
||||||
self.add_node( src )
|
attr_dict: optional attribute dict"""
|
||||||
self.add_node( dest )
|
attr_dict = {} if attr_dict is None else attr_dict
|
||||||
self.data[ src ].append( dest )
|
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 ):
|
def nodes( self, data=False):
|
||||||
"Return list of graph nodes"
|
"""Return list of graph nodes
|
||||||
return self.data.keys()
|
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 ):
|
||||||
"Iterator: return graph edges"
|
"Iterator: return graph edges"
|
||||||
for src in self.data.keys():
|
for src, entry in self.edge.iteritems():
|
||||||
for dest in self.data[ src ]:
|
for dst, keys in entry.iteritems():
|
||||||
yield ( src, dest )
|
if [ src, dst ] != sorted( [ src, dst ] ):
|
||||||
|
# Skip duplicate edges
|
||||||
|
continue
|
||||||
|
for key, attrs in keys.iteritems():
|
||||||
|
if data:
|
||||||
|
yield( ( src, dst, attrs ) )
|
||||||
|
else:
|
||||||
|
yield( ( src, dst ) )
|
||||||
|
|
||||||
|
def edges( self, data=False ):
|
||||||
|
"Return list of graph edges"
|
||||||
|
return list( self.edges_iter( data ) )
|
||||||
|
|
||||||
|
|
||||||
def __getitem__( self, node ):
|
def __getitem__( self, node ):
|
||||||
"Return link dict for the given node"
|
"Return link dict for given src node"
|
||||||
return self.data[node]
|
return self.edge[ node ]
|
||||||
|
|
||||||
|
def __len__( self ):
|
||||||
|
"Return the number of nodes"
|
||||||
|
return len( self.node )
|
||||||
|
|
||||||
|
def convertTo( self, cls, data=False ):
|
||||||
|
"""Convert to a new object of networkx.MultiGraph-like class cls
|
||||||
|
data: include node and edge data"""
|
||||||
|
g = cls()
|
||||||
|
g.add_nodes_from( self.nodes( data=data ) )
|
||||||
|
g.add_edges_from( self.edges( data=data ) )
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
class Topo(object):
|
class Topo(object):
|
||||||
"Data center network representation for structured multi-trees."
|
"Data center network representation for structured multi-trees."
|
||||||
|
|
||||||
def __init__(self, *args, **params):
|
def __init__( self, *args, **params ):
|
||||||
"""Topo object.
|
"""Topo object.
|
||||||
Optional named parameters:
|
Optional named parameters:
|
||||||
hinfo: default host options
|
hinfo: default host options
|
||||||
@@ -56,28 +99,25 @@ class Topo(object):
|
|||||||
lopts: default link options
|
lopts: default link options
|
||||||
calls build()"""
|
calls build()"""
|
||||||
self.g = MultiGraph()
|
self.g = MultiGraph()
|
||||||
self.node_info = {}
|
|
||||||
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
|
|
||||||
self.hopts = params.pop( 'hopts', {} )
|
self.hopts = params.pop( 'hopts', {} )
|
||||||
self.sopts = params.pop( 'sopts', {} )
|
self.sopts = params.pop( 'sopts', {} )
|
||||||
self.lopts = params.pop( 'lopts', {} )
|
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 )
|
self.build( *args, **params )
|
||||||
|
|
||||||
def build( self, *args, **params ):
|
def build( self, *args, **params ):
|
||||||
"Override this method to build your topology."
|
"Override this method to build your topology."
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def addNode(self, name, **opts):
|
def addNode( self, name, **opts ):
|
||||||
"""Add Node to graph.
|
"""Add Node to graph.
|
||||||
name: name
|
name: name
|
||||||
opts: node options
|
opts: node options
|
||||||
returns: node name"""
|
returns: node name"""
|
||||||
self.g.add_node(name)
|
self.g.add_node( name, **opts )
|
||||||
self.node_info[name] = opts
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def addHost(self, name, **opts):
|
def addHost( self, name, **opts ):
|
||||||
"""Convenience method: Add host to graph.
|
"""Convenience method: Add host to graph.
|
||||||
name: host name
|
name: host name
|
||||||
opts: host options
|
opts: host options
|
||||||
@@ -86,7 +126,7 @@ class Topo(object):
|
|||||||
opts = 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.
|
"""Convenience method: Add switch to graph.
|
||||||
name: switch name
|
name: switch name
|
||||||
opts: switch options
|
opts: switch options
|
||||||
@@ -96,110 +136,123 @@ class Topo(object):
|
|||||||
result = self.addNode(name, isSwitch=True, **opts)
|
result = self.addNode(name, isSwitch=True, **opts)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def addLink(self, node1, node2, port1=None, port2=None,
|
def addLink( self, node1, node2, port1=None, port2=None,
|
||||||
**opts):
|
key=None, **opts ):
|
||||||
"""node1, node2: nodes to link together
|
"""node1, node2: nodes to link together
|
||||||
port1, port2: ports (optional)
|
port1, port2: ports (optional)
|
||||||
opts: link options (optional)
|
opts: link options (optional)
|
||||||
returns: link info key"""
|
returns: link info key"""
|
||||||
if not opts and self.lopts:
|
if not opts and self.lopts:
|
||||||
opts = self.lopts
|
opts = self.lopts
|
||||||
self.addPort(node1, node2, port1, port2)
|
port1, port2 = self.addPort(node1, node2, port1, port2)
|
||||||
key = tuple(self.sorted([node1, node2]))
|
opts.update( node1=node1, node2=node2, port1=port1, port2=port2 )
|
||||||
self.link_info[key] = opts
|
self.g.add_edge(node1, node2, key, opts )
|
||||||
self.g.add_edge(*key)
|
|
||||||
return key
|
return key
|
||||||
|
|
||||||
def addPort(self, src, dst, sport=None, dport=None):
|
def nodes( self, sort=True ):
|
||||||
'''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):
|
|
||||||
"Return nodes in graph"
|
"Return nodes in graph"
|
||||||
if sort:
|
if sort:
|
||||||
return self.sorted( self.g.nodes() )
|
return self.sorted( self.g.nodes() )
|
||||||
else:
|
else:
|
||||||
return self.g.nodes()
|
return self.g.nodes()
|
||||||
|
|
||||||
def isSwitch(self, n):
|
def isSwitch( self, n ):
|
||||||
'''Returns true if node is a switch.'''
|
"Returns true if node is a switch."
|
||||||
info = self.node_info[n]
|
return self.g.node[ n ].get( 'isSwitch', False )
|
||||||
return info and info.get('isSwitch', False)
|
|
||||||
|
|
||||||
def switches(self, sort=True):
|
def switches( self, sort=True ):
|
||||||
'''Return switches.
|
"""Return switches.
|
||||||
sort: sort switches alphabetically
|
sort: sort switches alphabetically
|
||||||
@return dpids list of dpids
|
returns: dpids list of dpids"""
|
||||||
'''
|
return [ n for n in self.nodes( sort ) if self.isSwitch( n ) ]
|
||||||
return [n for n in self.nodes(sort) if self.isSwitch(n)]
|
|
||||||
|
|
||||||
def hosts(self, sort=True):
|
def hosts( self, sort=True ):
|
||||||
'''Return hosts.
|
"""Return hosts.
|
||||||
sort: sort hosts alphabetically
|
sort: sort hosts alphabetically
|
||||||
@return dpids list of dpids
|
returns: list of hosts"""
|
||||||
'''
|
return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ]
|
||||||
return [n for n in self.nodes(sort) if not self.isSwitch(n)]
|
|
||||||
|
|
||||||
def links(self, sort=True):
|
def links( self, sort=True, withKeys=False ):
|
||||||
'''Return links.
|
"""Return links.
|
||||||
sort: sort links alphabetically
|
sort: sort links alphabetically
|
||||||
@return links list of name pairs
|
withKeys: return key in tuple
|
||||||
'''
|
@return links list of ( src, dst [,key ] )"""
|
||||||
if not sort:
|
if not sort:
|
||||||
return self.g.edges()
|
return self.g.edges( withKeys )
|
||||||
else:
|
else:
|
||||||
links = [tuple(self.sorted(e)) for e in self.g.edges()]
|
if withKeys:
|
||||||
|
links = [ tuple( self.sorted( ( s, d ) ) ) + [ k ]
|
||||||
|
for s, d, k in self.g.edges( data=True ) ]
|
||||||
|
else:
|
||||||
|
links = [ tuple ( self.sorted( e ) ) for e in self.g.edges() ]
|
||||||
return sorted( links, key=naturalSeq )
|
return sorted( links, key=naturalSeq )
|
||||||
|
|
||||||
def port(self, src, dst):
|
# This legacy port management mechanism is clunky and will probably
|
||||||
'''Get port number.
|
# be removed at some point.
|
||||||
|
|
||||||
@param src source switch name
|
def addPort( self, src, dst, sport=None, dport=None ):
|
||||||
@param dst destination switch name
|
"""Generate port mapping for new edge.
|
||||||
@return tuple (src_port, dst_port):
|
src: source switch name
|
||||||
src_port: port on source switch leading to the destination switch
|
dst: destination switch name"""
|
||||||
dst_port: port on destination switch leading to the source switch
|
# Initialize if necessary
|
||||||
'''
|
ports = self.ports
|
||||||
if src in self.ports and dst in self.ports[src]:
|
ports.setdefault( src, {} )
|
||||||
assert dst in self.ports and src in self.ports[dst]
|
ports.setdefault( dst, {} )
|
||||||
return self.ports[src][dst], self.ports[dst][src]
|
# 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 linkInfo( self, src, dst ):
|
def port( self, src, dst ):
|
||||||
"Return link metadata"
|
"""Get port numbers.
|
||||||
src, dst = self.sorted([src, dst])
|
src: source switch name
|
||||||
return self.link_info[(src, dst)]
|
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 setlinkInfo( self, src, dst, info ):
|
def _linkEntry( self, src, dst, key=None ):
|
||||||
"Set link metadata"
|
"Helper function: return link entry and key"
|
||||||
src, dst = self.sorted([src, dst])
|
entry = self.g[ src ][ dst ]
|
||||||
self.link_info[(src, dst)] = info
|
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 ):
|
def nodeInfo( self, name ):
|
||||||
"Return metadata (dict) for node"
|
"Return metadata (dict) for node"
|
||||||
info = self.node_info[ name ]
|
return self.g.node[ name ]
|
||||||
return info if info is not None else {}
|
|
||||||
|
|
||||||
def setNodeInfo( self, name, info ):
|
def setNodeInfo( self, name, info ):
|
||||||
"Set metadata (dict) for node"
|
"Set metadata (dict) for node"
|
||||||
self.node_info[ name ] = info
|
self.g.node[ name ] = info
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sorted( items ):
|
def sorted( items ):
|
||||||
"Items sorted in natural (i.e. alphabetical) order"
|
"Items sorted in natural (i.e. alphabetical) order"
|
||||||
return sorted(items, key=natural)
|
return sorted( items, key=natural )
|
||||||
|
|
||||||
|
|
||||||
class SingleSwitchTopo( Topo ):
|
class SingleSwitchTopo( Topo ):
|
||||||
@@ -228,6 +281,7 @@ class SingleSwitchReversedTopo( Topo ):
|
|||||||
self.addLink( host, switch,
|
self.addLink( host, switch,
|
||||||
port1=0, port2=( k - h + 1 ) )
|
port1=0, port2=( k - h + 1 ) )
|
||||||
|
|
||||||
|
|
||||||
class LinearTopo( Topo ):
|
class LinearTopo( Topo ):
|
||||||
"Linear topology of k switches, with n hosts per switch."
|
"Linear topology of k switches, with n hosts per switch."
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user