From 89fb081983cf8b76cf770646d92e2850d5b4101e Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 28 Oct 2014 08:25:05 -0700 Subject: [PATCH 01/13] First crack at fixing multiple links * Makes MultiGraph more like networkx.multigraph * Adds converTo method * Synchronizes node1 with xxx1 in link options --- mininet/net.py | 43 +++++--- mininet/topo.py | 256 +++++++++++++++++++++++++++++------------------- 2 files changed, 182 insertions(+), 117 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index b74b930..f995ea4 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -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 ): + 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 @@ class Mininet( object ): 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' ) diff --git a/mininet/topo.py b/mininet/topo.py index cc458fd..6650bd2 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -'''@package topo +"""@package topo 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 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 = {} + self.edgeinfo = {} - 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 ): "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 @@ 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 @@ -86,7 +126,7 @@ class Topo(object): 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 @@ class Topo(object): 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 - ''' + 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. + # This legacy port management mechanism is clunky and will probably + # be removed at some point. - @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 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 linkInfo( self, src, dst ): - "Return link metadata" - src, dst = self.sorted([src, dst]) - return self.link_info[(src, dst)] + 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 setlinkInfo( self, src, dst, info ): - "Set link metadata" - src, dst = self.sorted([src, dst]) - self.link_info[(src, dst)] = info + 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 @@ 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." From 94f088d7e801af78475197a3711ce232f861dc46 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 28 Oct 2014 15:44:46 -0700 Subject: [PATCH 02/13] Allow natural sort to accept non-strings. --- mininet/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/util.py b/mininet/util.py index bb1935f..0c76a99 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -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" From 38ce329e7e95da02db7375b9777406836a0c867c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 28 Oct 2014 15:45:09 -0700 Subject: [PATCH 03/13] Allow Mininet() to accept multi-link topos w/correct params. --- mininet/net.py | 28 +++++++++++++--------------- mininet/topo.py | 31 +++++++++++++++++++------------ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index f995ea4..5b16ce0 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -337,18 +337,18 @@ class Mininet( object ): paramDict: dictionary of additional link params (optional) params: additional link params (optional) returns: link object""" - 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 ) ] + # other link parameters, and allow either nodes or names + paramDict = {} if paramDict is None else dict( paramDict ) + paramDict.update( params ) + node1 = paramDict.pop( 'node1', node1 ) + node2 = paramDict.pop( 'node2', node1 ) + node1 = node1 if type( node1 ) != str else self[ node1 ] + node2 = node2 if type( node2 ) != str else self[ node2 ] paramDict.setdefault( 'port1', port1 ) paramDict.setdefault( 'port2', port2 ) - paramDict.setdefault( 'addr1', mac1 ) - paramDict.setdefault( 'addr2', mac2 ) + paramDict.setdefault( 'addr1', self.randMac() ) + paramDict.setdefault( 'addr2', self.randMac() ) cls = self.link if cls is None else cls link = cls( node1, node2, **paramDict ) self.links.append( link ) @@ -408,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, paramDict=params ) - info( '(%s, %s) ' % ( src.name, dst.name ) ) + for srcName, dstName, params in topo.links( + sort=True, withInfo=True ): + self.addLink( srcName, dstName, paramDict=params ) + info( '(%s, %s) ' % ( srcName, dstName ) ) info( '\n' ) diff --git a/mininet/topo.py b/mininet/topo.py index 6650bd2..a05707e 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -53,22 +53,24 @@ class MultiGraph( object ): data: return list of ( node, attrs)""" return self.node.items() if data else self.node.keys() - def edges_iter( self, data=False ): + def edges_iter( self, data=False, key=False): "Iterator: return graph edges" 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(): + for k, attrs in keys.iteritems(): if data: yield( ( src, dst, attrs ) ) + elif key: + yield( ( src, dst, k ) ) else: yield( ( src, dst ) ) - def edges( self, data=False ): + def edges( self, data=False, key=False ): "Return list of graph edges" - return list( self.edges_iter( data ) ) + return list( self.edges_iter( data=data, key=key ) ) def __getitem__( self, node ): @@ -88,7 +90,7 @@ class MultiGraph( object ): return g -class Topo(object): +class Topo( object ): "Data center network representation for structured multi-trees." def __init__( self, *args, **params ): @@ -146,6 +148,7 @@ class Topo(object): opts = self.lopts port1, port2 = self.addPort(node1, node2, port1, port2) opts.update( node1=node1, node2=node2, port1=port1, port2=port2 ) + assert 'node1' in opts self.g.add_edge(node1, node2, key, opts ) return key @@ -172,20 +175,24 @@ class Topo(object): returns: list of hosts""" return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ] - def links( self, sort=True, withKeys=False ): + def links( self, sort=True, withInfo=False, 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( withKeys ) + return self.g.edges( data=withInfo, key=withKeys ) else: - if withKeys: - links = [ tuple( self.sorted( ( s, d ) ) ) + [ k ] - for s, d, k in self.g.edges( data=True ) ] + if withInfo: + links = [ tuple( self.sorted( ( s, d ) ) + [ info ] ) + for s, d, info in self.g.edges( data=True ) ] + elif withKeys: + links = [ tuple( self.sorted( ( s, d ) ) + [ key ] ) + for s, d, key in self.g.edges( key=True ) ] else: - links = [ tuple ( self.sorted( e ) ) for e in self.g.edges() ] - return sorted( links, key=naturalSeq ) + links = [ tuple ( self.sorted( e ) ) + for e in self.g.edges() ] + return self.sorted( links ) # This legacy port management mechanism is clunky and will probably # be removed at some point. From 06d9e4bba79956ea2698dfd432d0c52d4411a25a Mon Sep 17 00:00:00 2001 From: cody burkard Date: Wed, 3 Sep 2014 20:43:30 -0700 Subject: [PATCH 04/13] add example and test for multiple links --- examples/multiLink.py | 35 +++++++++++++++++++++ examples/test/test_multiLink.py | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100755 examples/multiLink.py create mode 100755 examples/test/test_multiLink.py diff --git a/examples/multiLink.py b/examples/multiLink.py new file mode 100755 index 0000000..28a67a8 --- /dev/null +++ b/examples/multiLink.py @@ -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() diff --git a/examples/test/test_multiLink.py b/examples/test/test_multiLink.py new file mode 100755 index 0000000..6b4bca2 --- /dev/null +++ b/examples/test/test_multiLink.py @@ -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() From eab4ea3fb99728ee4f370757a017cfe416e54e7f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 28 Oct 2014 16:58:42 -0700 Subject: [PATCH 05/13] Minor fixes --- mininet/topo.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index a05707e..88560db 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -126,7 +126,7 @@ class Topo( object ): 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 ): """Convenience method: Add switch to graph. @@ -135,7 +135,7 @@ class Topo( object ): 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, @@ -146,9 +146,8 @@ class Topo( object ): returns: link info key""" if not opts and self.lopts: opts = self.lopts - port1, port2 = self.addPort(node1, node2, port1, port2) + port1, port2 = self.addPort( node1, node2, port1, port2 ) opts.update( node1=node1, node2=node2, port1=port1, port2=port2 ) - assert 'node1' in opts self.g.add_edge(node1, node2, key, opts ) return key @@ -179,7 +178,7 @@ class Topo( object ): """Return links. sort: sort links alphabetically withKeys: return key in tuple - @return links list of ( src, dst [,key ] )""" + returns: links list of ( src, dst [,key ] )""" if not sort: return self.g.edges( data=withInfo, key=withKeys ) else: @@ -246,7 +245,7 @@ class Topo( object ): def setlinkInfo( self, src, dst, info, key=None ): "Set link metadata dict" entry, key = self._linkEntry( src, dst, key ) - entry [ key ] = info + entry[ key ] = info def nodeInfo( self, name ): "Return metadata (dict) for node" From ba8ea8f0cc776a2c5667f4c2a280a06c69b6c169 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 28 Oct 2014 20:38:31 -0700 Subject: [PATCH 06/13] Return (src, dst) in original order, and allow keys + data --- mininet/topo.py | 62 +++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index 88560db..a847c1b 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -53,24 +53,28 @@ class MultiGraph( object ): data: return list of ( node, attrs)""" return self.node.items() if data else self.node.keys() - def edges_iter( self, data=False, key=False): + def edges_iter( self, data=False, keys=False ): "Iterator: return graph edges" for src, entry in self.edge.iteritems(): for dst, keys in entry.iteritems(): - if [ src, dst ] != sorted( [ src, dst ] ): + if src > dst: # Skip duplicate edges continue for k, attrs in keys.iteritems(): if data: - yield( ( src, dst, attrs ) ) - elif key: - yield( ( src, dst, k ) ) + if keys: + yield( src, dst, k, attrs ) + else: + yield( src, dst, attrs ) else: - yield( ( src, dst ) ) + if keys: + yield( src, dst, k ) + else: + yield( src, dst ) - def edges( self, data=False, key=False ): + def edges( self, data=False, keys=False ): "Return list of graph edges" - return list( self.edges_iter( data=data, key=key ) ) + return list( self.edges_iter( data=data, keys=key ) ) def __getitem__( self, node ): @@ -174,24 +178,32 @@ class Topo( object ): returns: list of hosts""" return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ] - def links( self, sort=True, withInfo=False, withKeys=False ): - """Return links. - sort: sort links alphabetically - withKeys: return key in tuple - returns: links list of ( src, dst [,key ] )""" - if not sort: - return self.g.edges( data=withInfo, key=withKeys ) - else: - if withInfo: - links = [ tuple( self.sorted( ( s, d ) ) + [ info ] ) - for s, d, info in self.g.edges( data=True ) ] - elif withKeys: - links = [ tuple( self.sorted( ( s, d ) ) + [ key ] ) - for s, d, key in self.g.edges( key=True ) ] + 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: - links = [ tuple ( self.sorted( e ) ) - for e in self.g.edges() ] - return self.sorted( links ) + if withInfo: + yield( node1, node2, info ) + else: + yield( node1, node2 ) + + 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 ) ) + return links if not sort else self.sorted( links ) # This legacy port management mechanism is clunky and will probably # be removed at some point. From 01aac350fa92d486ad3ac58f362fcf29856a5853 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 3 Nov 2014 12:43:52 -0800 Subject: [PATCH 07/13] Remove unused edgeinfo --- mininet/topo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mininet/topo.py b/mininet/topo.py index a847c1b..65d445e 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -19,7 +19,6 @@ class MultiGraph( object ): def __init__( self ): self.node = {} self.edge = {} - self.edgeinfo = {} def add_node( self, node, attr_dict=None, **attrs): """Add node to graph From 634761b8a7ad4734dcdb64978ef6a1f412dc85ab Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 3 Nov 2014 12:59:53 -0800 Subject: [PATCH 08/13] Fix edges() and add convertTo() to Topo() (with keys option) --- mininet/topo.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mininet/topo.py b/mininet/topo.py index 65d445e..b6f1fa3 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -73,7 +73,7 @@ class MultiGraph( object ): def edges( self, data=False, keys=False ): "Return list of graph edges" - return list( self.edges_iter( data=data, keys=key ) ) + return list( self.edges_iter( data=data, keys=keys ) ) def __getitem__( self, node ): @@ -84,12 +84,13 @@ class MultiGraph( object ): "Return the number of nodes" return len( self.node ) - def convertTo( self, cls, data=False ): + 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""" + 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 ) ) + g.add_edges_from( self.edges( data=( data or keys ), keys=keys ) ) return g @@ -266,6 +267,12 @@ class Topo( object ): "Set metadata (dict) for node" 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" From 8dea57d2711d7cec6e5602a368a600c9aa912c4c Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 3 Nov 2014 14:20:01 -0800 Subject: [PATCH 09/13] Ignore link info when sorting links. --- mininet/topo.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mininet/topo.py b/mininet/topo.py index b6f1fa3..1331306 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -203,7 +203,11 @@ class Topo( object ): withInfo: return link info returns: list of ( src, dst [,key, info ] )""" links = list( self.iterLinks( withKeys, withInfo ) ) - return links if not sort else self.sorted( links ) + if not sorted: + return links + # Ignore info when sorting + tupleSize = 3 if withKeys else 2 + return sorted( links, key=( lambda l: naturalSeq( l[ 0 : tupleSize ] ) ) ) # This legacy port management mechanism is clunky and will probably # be removed at some point. From e77123cf0eae9c2c0f8940aa4be17f30d9b59c44 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 3 Nov 2014 14:25:09 -0800 Subject: [PATCH 10/13] Remove unnecessary 0 --- mininet/topo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mininet/topo.py b/mininet/topo.py index 1331306..af3eac5 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -207,7 +207,7 @@ class Topo( object ): return links # Ignore info when sorting tupleSize = 3 if withKeys else 2 - return sorted( links, key=( lambda l: naturalSeq( l[ 0 : tupleSize ] ) ) ) + return sorted( links, key=( lambda l: naturalSeq( l[ :tupleSize ] ) ) ) # This legacy port management mechanism is clunky and will probably # be removed at some point. From 2a2d605074fe78ec76542412ebbe8a350483fe09 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 7 Nov 2014 13:40:00 -0800 Subject: [PATCH 11/13] Get rid of paramDict and simplify things a bit --- mininet/net.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 5b16ce0..0102a2e 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -324,33 +324,26 @@ class Mininet( object ): @staticmethod def randMac(): "Return a random, non-multicast MAC address" - return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 ) + return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | + 0x020000000000 ) def addLink( self, node1, node2, port1=None, port2=None, - cls=None, paramDict=None, **params ): + cls=None, **params ): """"Add a link from node1 to node2 - node1: source node - node2: dest node + node1: source node (or name) + node2: dest node (or name) 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""" - # Ugly: try to ensure that node1 and node2 line up correctly with - # other link parameters, and allow either nodes or names - paramDict = {} if paramDict is None else dict( paramDict ) - paramDict.update( params ) - node1 = paramDict.pop( 'node1', node1 ) - node2 = paramDict.pop( 'node2', node1 ) node1 = node1 if type( node1 ) != str else self[ node1 ] node2 = node2 if type( node2 ) != str else self[ node2 ] - paramDict.setdefault( 'port1', port1 ) - paramDict.setdefault( 'port2', port2 ) - paramDict.setdefault( 'addr1', self.randMac() ) - paramDict.setdefault( 'addr2', self.randMac() ) + options = dict( params ) + options.setdefault( 'addr1', self.randMac() ) + options.setdefault( 'addr2', self.randMac() ) cls = self.link if cls is None else cls - link = cls( node1, node2, **paramDict ) + link = cls( node1, node2, **options ) self.links.append( link ) return link @@ -410,7 +403,7 @@ class Mininet( object ): info( '\n*** Adding links:\n' ) for srcName, dstName, params in topo.links( sort=True, withInfo=True ): - self.addLink( srcName, dstName, paramDict=params ) + self.addLink( **params ) info( '(%s, %s) ' % ( srcName, dstName ) ) info( '\n' ) From 39203484a66f5722a7d1b02290d57e83346f380a Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 7 Nov 2014 13:42:50 -0800 Subject: [PATCH 12/13] Make port1, port2 truly optional and don't pass them to Link() --- mininet/net.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mininet/net.py b/mininet/net.py index 0102a2e..fea14d4 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -342,6 +342,10 @@ class Mininet( object ): options = dict( params ) options.setdefault( 'addr1', self.randMac() ) options.setdefault( 'addr2', self.randMac() ) + if port1 is not None: + options.setdefault( 'port1', port1 ) + if port2 is not None: + options.setdefault( 'port2', port2 ) cls = self.link if cls is None else cls link = cls( node1, node2, **options ) self.links.append( link ) From c92c4efb6637a4f6585c507b358e95af63ded83d Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 7 Nov 2014 13:47:03 -0800 Subject: [PATCH 13/13] Add a few clarifying comments --- mininet/net.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index fea14d4..8f89bab 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -337,15 +337,18 @@ class Mininet( object ): cls: link class (optional) params: additional link params (optional) returns: link object""" + # 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 ) - options.setdefault( 'addr1', self.randMac() ) - options.setdefault( 'addr2', self.randMac() ) + # 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 )