4 Network topology creation.
6 @author Brandon Heller (brandonh@stanford.edu)
8 This package includes code to represent network topologies.
10 A Topo object can be a topology database for NOX, can represent a physical
11 setup for testing, and can even be emulated with the Mininet package.
14 from mininet.util import irange, natural, naturalSeq
16 class MultiGraph( object ):
17 "Utility class to track nodes and edges - replaces networkx.MultiGraph"
23 def add_node( self, node, attr_dict=None, **attrs):
25 attr_dict: attribute dict (optional)
26 attrs: more attributes (optional)
27 warning: updates attr_dict with attrs"""
28 attr_dict = {} if attr_dict is None else attr_dict
29 attr_dict.update( attrs )
30 self.node[ node ] = attr_dict
32 def add_edge( self, src, dst, key=None, attr_dict=None, **attrs ):
35 attr_dict: optional attribute dict
36 attrs: more attributes
37 warning: udpates attr_dict with attrs"""
38 attr_dict = {} if attr_dict is None else attr_dict
39 attr_dict.update( attrs )
40 self.node.setdefault( src, {} )
41 self.node.setdefault( dst, {} )
42 self.edge.setdefault( src, {} )
43 self.edge.setdefault( dst, {} )
44 self.edge[ src ].setdefault( dst, {} )
45 entry = self.edge[ dst ][ src ] = self.edge[ src ][ dst ]
46 # If no key, pick next ordinal number
48 keys = [ k for k in entry.keys() if isinstance( k, int ) ]
49 key = max( [ 0 ] + keys ) + 1
50 entry[ key ] = attr_dict
53 def nodes( self, data=False):
54 """Return list of graph nodes
55 data: return list of ( node, attrs)"""
56 return self.node.items() if data else self.node.keys()
58 def edges_iter( self, data=False, keys=False ):
59 "Iterator: return graph edges, optionally with data and keys"
60 for src, entry in self.edge.items():
61 for dst, entrykeys in entry.items():
63 # Skip duplicate edges
65 for k, attrs in entrykeys.items():
68 yield( src, dst, k, attrs )
70 yield( src, dst, attrs )
77 def edges( self, data=False, keys=False ):
78 "Return list of graph edges"
79 return list( self.edges_iter( data=data, keys=keys ) )
81 def __getitem__( self, node ):
82 "Return link dict for given src node"
83 return self.edge[ node ]
86 "Return the number of nodes"
87 return len( self.node )
89 def convertTo( self, cls, data=False, keys=False ):
90 """Convert to a new object of networkx.MultiGraph-like class cls
91 data: include node and edge data
92 keys: include edge keys as well as edge data"""
94 g.add_nodes_from( self.nodes( data=data ) )
95 g.add_edges_from( self.edges( data=( data or keys ), keys=keys ) )
100 "Data center network representation for structured multi-trees."
102 def __init__( self, *args, **params ):
104 Optional named parameters:
105 hinfo: default host options
106 sopts: default switch options
107 lopts: default link options
109 self.g = MultiGraph()
110 self.hopts = params.pop( 'hopts', {} )
111 self.sopts = params.pop( 'sopts', {} )
112 self.lopts = params.pop( 'lopts', {} )
113 # ports[src][dst][sport] is port on dst that connects to src
115 self.build( *args, **params )
117 def build( self, *args, **params ):
118 "Override this method to build your topology."
121 def addNode( self, name, **opts ):
122 """Add Node to graph.
125 returns: node name"""
126 self.g.add_node( name, **opts )
129 def addHost( self, name, **opts ):
130 """Convenience method: Add host to graph.
133 returns: host name"""
134 if not opts and self.hopts:
136 return self.addNode( name, **opts )
138 def addSwitch( self, name, **opts ):
139 """Convenience method: Add switch to graph.
142 returns: switch name"""
143 if not opts and self.sopts:
145 result = self.addNode( name, isSwitch=True, **opts )
148 def addLink( self, node1, node2, port1=None, port2=None,
150 """node1, node2: nodes to link together
151 port1, port2: ports (optional)
152 opts: link options (optional)
153 returns: link info key"""
154 if not opts and self.lopts:
156 port1, port2 = self.addPort( node1, node2, port1, port2 )
158 opts.update( node1=node1, node2=node2, port1=port1, port2=port2 )
159 self.g.add_edge(node1, node2, key, opts )
162 def nodes( self, sort=True ):
163 "Return nodes in graph"
165 return self.sorted( self.g.nodes() )
167 return self.g.nodes()
169 def isSwitch( self, n ):
170 "Returns true if node is a switch."
171 return self.g.node[ n ].get( 'isSwitch', False )
173 def switches( self, sort=True ):
175 sort: sort switches alphabetically
176 returns: dpids list of dpids"""
177 return [ n for n in self.nodes( sort ) if self.isSwitch( n ) ]
179 def hosts( self, sort=True ):
181 sort: sort hosts alphabetically
182 returns: list of hosts"""
183 return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ]
185 def iterLinks( self, withKeys=False, withInfo=False ):
186 """Return links (iterator)
187 withKeys: return link keys
188 withInfo: return link info
189 returns: list of ( src, dst [,key, info ] )"""
190 for _src, _dst, key, info in self.g.edges_iter( data=True, keys=True ):
191 node1, node2 = info[ 'node1' ], info[ 'node2' ]
194 yield( node1, node2, key, info )
196 yield( node1, node2, key )
199 yield( node1, node2, info )
201 yield( node1, node2 )
203 def links( self, sort=False, withKeys=False, withInfo=False ):
205 sort: sort links alphabetically, preserving (src, dst) order
206 withKeys: return link keys
207 withInfo: return link info
208 returns: list of ( src, dst [,key, info ] )"""
209 links = list( self.iterLinks( withKeys, withInfo ) )
212 # Ignore info when sorting
213 tupleSize = 3 if withKeys else 2
214 return sorted( links, key=( lambda l: naturalSeq( l[ :tupleSize ] ) ) )
216 # This legacy port management mechanism is clunky and will probably
217 # be removed at some point.
219 def addPort( self, src, dst, sport=None, dport=None ):
220 """Generate port mapping for new edge.
221 src: source switch name
222 dst: destination switch name"""
223 # Initialize if necessary
225 ports.setdefault( src, {} )
226 ports.setdefault( dst, {} )
227 # New port: number of outlinks + base
229 src_base = 1 if self.isSwitch( src ) else 0
230 sport = len( ports[ src ] ) + src_base
232 dst_base = 1 if self.isSwitch( dst ) else 0
233 dport = len( ports[ dst ] ) + dst_base
234 ports[ src ][ sport ] = ( dst, dport )
235 ports[ dst ][ dport ] = ( src, sport )
238 def port( self, src, dst ):
240 src: source switch name
241 dst: destination switch name
242 sport: optional source port (otherwise use lowest src port)
243 returns: tuple (sport, dport), where
244 sport = port on source switch leading to the destination switch
245 dport = port on destination switch leading to the source switch
246 Note that you can also look up ports using linkInfo()"""
247 # A bit ugly and slow vs. single-link implementation ;-(
248 ports = [ ( sport, entry[ 1 ] )
249 for sport, entry in self.ports[ src ].items()
250 if entry[ 0 ] == dst ]
251 return ports if len( ports ) != 1 else ports[ 0 ]
253 def _linkEntry( self, src, dst, key=None ):
254 "Helper function: return link entry and key"
255 entry = self.g[ src ][ dst ]
260 def linkInfo( self, src, dst, key=None ):
261 "Return link metadata dict"
262 entry, key = self._linkEntry( src, dst, key )
265 def setlinkInfo( self, src, dst, info, key=None ):
266 "Set link metadata dict"
267 entry, key = self._linkEntry( src, dst, key )
270 def nodeInfo( self, name ):
271 "Return metadata (dict) for node"
272 return self.g.node[ name ]
274 def setNodeInfo( self, name, info ):
275 "Set metadata (dict) for node"
276 self.g.node[ name ] = info
278 def convertTo( self, cls, data=True, keys=True ):
279 """Convert to a new object of networkx.MultiGraph-like class cls
280 data: include node and edge data (default True)
281 keys: include edge keys as well as edge data (default True)"""
282 return self.g.convertTo( cls, data=data, keys=keys )
286 "Items sorted in natural (i.e. alphabetical) order"
287 return sorted( items, key=natural )
290 # Our idiom defines additional parameters in build(param...)
291 # pylint: disable=arguments-differ
293 class SingleSwitchTopo( Topo ):
294 "Single switch connected to k hosts."
296 def build( self, k=2, **_opts ):
299 switch = self.addSwitch( 's1' )
300 for h in irange( 1, k ):
301 host = self.addHost( 'h%s' % h )
302 self.addLink( host, switch )
305 class SingleSwitchReversedTopo( Topo ):
306 """Single switch connected to k hosts, with reversed ports.
307 The lowest-numbered host is connected to the highest-numbered port.
308 Useful to verify that Mininet properly handles custom port
311 def build( self, k=2 ):
314 switch = self.addSwitch( 's1' )
315 for h in irange( 1, k ):
316 host = self.addHost( 'h%s' % h )
317 self.addLink( host, switch,
318 port1=0, port2=( k - h + 1 ) )
321 class MinimalTopo( SingleSwitchTopo ):
322 "Minimal topology with two hosts and one switch"
324 return SingleSwitchTopo.build( self, k=2 )
327 class LinearTopo( Topo ):
328 "Linear topology of k switches, with n hosts per switch."
330 def build( self, k=2, n=1, **_opts):
331 """k: number of switches
332 n: number of hosts per switch"""
337 genHostName = lambda i, j: 'h%s' % i
339 genHostName = lambda i, j: 'h%ss%d' % ( j, i )
342 for i in irange( 1, k ):
344 switch = self.addSwitch( 's%s' % i )
345 # Add hosts to switch
346 for j in irange( 1, n ):
347 host = self.addHost( genHostName( i, j ) )
348 self.addLink( host, switch )
349 # Connect switch to previous
351 self.addLink( switch, lastSwitch )
354 # pylint: enable=arguments-differ