Skip to content
Snippets Groups Projects
Commit 89fb0819 authored by Bob Lantz's avatar Bob Lantz
Browse files

First crack at fixing multiple links

* Makes MultiGraph more like networkx.multigraph
* Adds converTo method
* Synchronizes node1 with xxx1 in link options
parent aae0affa
No related branches found
No related tags found
No related merge requests found
......@@ -321,25 +321,36 @@ def items( self ):
"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 ):
cls=None, paramDict=None, **params ):
""""Add a link from node1 to node2
node1: source node
node2: dest node
port1: source port
port2: dest port
port1: source port (optional)
port2: dest port (optional)
cls: link class (optional)
paramDict: dictionary of additional link params (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 )
mac1 = self.randMac()
mac2 = self.randMac()
paramDict = {} if paramDict is None else paramDict
paramDict.update( params )
# Ugly: try to ensure that node1 and node2 line up correctly with
# other link parameters
node1 = self[ paramDict.pop( 'node1', node1.name ) ]
node2 = self[ paramDict.pop( 'node2', node2.name ) ]
paramDict.setdefault( 'port1', port1 )
paramDict.setdefault( 'port2', port2 )
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 )
return link
......@@ -397,11 +408,11 @@ def buildFromTopo( self, topo=None ):
info( switchName + ' ' )
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 ]
params = topo.linkInfo( 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( '\n' )
......
#!/usr/bin/env python
'''@package topo
"""@package topo
Network topology creation.
......@@ -9,46 +9,89 @@
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 = {}
def add_node( self, node ):
"Add node to graph"
self.data.setdefault( node, [] )
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 )
self.node = {}
self.edge = {}
self.edgeinfo = {}
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, 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 ):
"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 ] != 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 ):
"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 ):
"""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):
"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,28 +99,25 @@ def __init__(self, *args, **params):
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
......@@ -86,7 +126,7 @@ def addHost(self, name, **opts):
opts = self.hopts
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
......@@ -96,110 +136,123 @@ def addSwitch(self, name, **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 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 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 links(self, sort=True):
'''Return links.
sort: sort links alphabetically
@return links list of name pairs
'''
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
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
returns: list of hosts"""
return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ]
def links( self, sort=True, withKeys=False ):
"""Return links.
sort: sort links alphabetically
withKeys: return key in tuple
@return links list of ( src, dst [,key ] )"""
if not sort:
return self.g.edges()
return self.g.edges( withKeys )
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 )
def port(self, src, dst):
'''Get port number.
@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]
def linkInfo( self, src, dst ):
"Return link metadata"
src, dst = self.sorted([src, dst])
return self.link_info[(src, dst)]
def setlinkInfo( self, src, dst, info ):
"Set link metadata"
src, dst = self.sorted([src, dst])
self.link_info[(src, dst)] = info
# This legacy port management mechanism is clunky and will probably
# be removed at some point.
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 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
@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 +281,7 @@ def build( self, k=2 ):
self.addLink( host, switch,
port1=0, port2=( k - h + 1 ) )
class LinearTopo( Topo ):
"Linear topology of k switches, with n hosts per switch."
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment