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:
Bob Lantz
2014-10-28 15:50:06 -07:00
parent aae0affae4
commit 89fb081983
2 changed files with 182 additions and 117 deletions
+27 -16
View File
@@ -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' )
+152 -98
View File
@@ -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."