Statistics
| Branch: | Tag: | Revision:

mininet / mininet / topo.py @ b1ec912d

History | View | Annotate | Download (12.1 KB)

1
#!/usr/bin/env python
2
"""@package topo
3

4
Network topology creation.
5

6
@author Brandon Heller (brandonh@stanford.edu)
7

8
This package includes code to represent network topologies.
9

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.
12
"""
13

    
14
from mininet.util import irange, natural, naturalSeq
15

    
16
class MultiGraph( object ):
17
    "Utility class to track nodes and edges - replaces networkx.MultiGraph"
18

    
19
    def __init__( self ):
20
        self.node = {}
21
        self.edge = {}
22

    
23
    def add_node( self, node, attr_dict=None, **attrs):
24
        """Add node to graph
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
31

    
32
    def add_edge( self, src, dst, key=None, attr_dict=None, **attrs ):
33
        """Add edge to graph
34
           key: optional key
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
47
        if key is None:
48
            keys = [ k for k in entry.keys() if isinstance( k, int ) ]
49
            key = max( [ 0 ] + keys ) + 1
50
        entry[ key ] = attr_dict
51
        return key
52

    
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()
57

    
58
    def edges_iter( self, data=False, keys=False ):
59
        "Iterator: return graph edges"
60
        for src, entry in self.edge.iteritems():
61
            for dst, keys in entry.iteritems():
62
                if src > dst:
63
                    # Skip duplicate edges
64
                    continue
65
                for k, attrs in keys.iteritems():
66
                    if data:
67
                        if keys:
68
                            yield( src, dst, k, attrs )
69
                        else:
70
                            yield( src, dst, attrs )
71
                    else:
72
                        if keys:
73
                            yield( src, dst, k )
74
                        else:
75
                            yield( src, dst )
76

    
77
    def edges( self, data=False, keys=False ):
78
        "Return list of graph edges"
79
        return list( self.edges_iter( data=data, keys=keys ) )
80

    
81

    
82
    def __getitem__( self, node ):
83
        "Return link dict for given src node"
84
        return self.edge[ node ]
85

    
86
    def __len__( self ):
87
        "Return the number of nodes"
88
        return len( self.node )
89

    
90
    def convertTo( self, cls, data=False, keys=False ):
91
        """Convert to a new object of networkx.MultiGraph-like class cls
92
           data: include node and edge data
93
           keys: include edge keys as well as edge data"""
94
        g = cls()
95
        g.add_nodes_from( self.nodes( data=data ) )
96
        g.add_edges_from( self.edges( data=( data or keys ), keys=keys ) )
97
        return g
98

    
99

    
100
class Topo( object ):
101
    "Data center network representation for structured multi-trees."
102

    
103
    def __init__( self, *args, **params ):
104
        """Topo object.
105
           Optional named parameters:
106
           hinfo: default host options
107
           sopts: default switch options
108
           lopts: default link options
109
           calls build()"""
110
        self.g = MultiGraph()
111
        self.hopts = params.pop( 'hopts', {} )
112
        self.sopts = params.pop( 'sopts', {} )
113
        self.lopts = params.pop( 'lopts', {} )
114
        # ports[src][dst][sport] is port on dst that connects to src
115
        self.ports = {}
116
        self.build( *args, **params )
117

    
118
    def build( self, *args, **params ):
119
        "Override this method to build your topology."
120
        pass
121

    
122
    def addNode( self, name, **opts ):
123
        """Add Node to graph.
124
           name: name
125
           opts: node options
126
           returns: node name"""
127
        self.g.add_node( name, **opts )
128
        return name
129

    
130
    def addHost( self, name, **opts ):
131
        """Convenience method: Add host to graph.
132
           name: host name
133
           opts: host options
134
           returns: host name"""
135
        if not opts and self.hopts:
136
            opts = self.hopts
137
        return self.addNode( name, **opts )
138

    
139
    def addSwitch( self, name, **opts ):
140
        """Convenience method: Add switch to graph.
141
           name: switch name
142
           opts: switch options
143
           returns: switch name"""
144
        if not opts and self.sopts:
145
            opts = self.sopts
146
        result = self.addNode( name, isSwitch=True, **opts )
147
        return result
148

    
149
    def addLink( self, node1, node2, port1=None, port2=None,
150
                key=None, **opts ):
151
        """node1, node2: nodes to link together
152
           port1, port2: ports (optional)
153
           opts: link options (optional)
154
           returns: link info key"""
155
        if not opts and self.lopts:
156
            opts = self.lopts
157
        port1, port2 = self.addPort( node1, node2, port1, port2 )
158
        opts = dict( opts )
159
        opts.update( node1=node1, node2=node2, port1=port1, port2=port2 )
160
        self.g.add_edge(node1, node2, key, opts )
161
        return key
162

    
163
    def nodes( self, sort=True ):
164
        "Return nodes in graph"
165
        if sort:
166
            return self.sorted( self.g.nodes() )
167
        else:
168
            return self.g.nodes()
169

    
170
    def isSwitch( self, n ):
171
        "Returns true if node is a switch."
172
        return self.g.node[ n ].get( 'isSwitch', False )
173

    
174
    def switches( self, sort=True ):
175
        """Return switches.
176
           sort: sort switches alphabetically
177
           returns: dpids list of dpids"""
178
        return [ n for n in self.nodes( sort ) if self.isSwitch( n ) ]
179

    
180
    def hosts( self, sort=True ):
181
        """Return hosts.
182
           sort: sort hosts alphabetically
183
           returns: list of hosts"""
184
        return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ]
185

    
186
    def iterLinks( self, withKeys=False, withInfo=False ):
187
        """Return links (iterator)
188
           withKeys: return link keys
189
           withInfo: return link info
190
           returns: list of ( src, dst [,key, info ] )"""
191
        for _src, _dst, key, info in self.g.edges_iter( data=True, keys=True ):
192
            node1, node2 = info[ 'node1' ], info[ 'node2' ]
193
            if withKeys:
194
                if withInfo:
195
                    yield( node1, node2, key, info )
196
                else:
197
                    yield( node1, node2, key )
198
            else:
199
                if withInfo:
200
                    yield( node1, node2, info )
201
                else:
202
                    yield( node1, node2 )
203

    
204
    def links( self, sort=False, withKeys=False, withInfo=False ):
205
        """Return links
206
           sort: sort links alphabetically, preserving (src, dst) order
207
           withKeys: return link keys
208
           withInfo: return link info
209
           returns: list of ( src, dst [,key, info ] )"""
210
        links = list( self.iterLinks( withKeys, withInfo ) )
211
        if not sort:
212
            return links
213
        # Ignore info when sorting
214
        tupleSize = 3 if withKeys else 2
215
        return sorted( links, key=( lambda l: naturalSeq( l[ :tupleSize ] ) ) )
216

    
217
    # This legacy port management mechanism is clunky and will probably
218
    # be removed at some point.
219

    
220
    def addPort( self, src, dst, sport=None, dport=None ):
221
        """Generate port mapping for new edge.
222
            src: source switch name
223
            dst: destination switch name"""
224
        # Initialize if necessary
225
        ports = self.ports
226
        ports.setdefault( src, {} )
227
        ports.setdefault( dst, {} )
228
        # New port: number of outlinks + base
229
        if sport is None:
230
            src_base = 1 if self.isSwitch( src ) else 0
231
            sport = len( ports[ src ] ) + src_base
232
        if dport is None:
233
            dst_base = 1 if self.isSwitch( dst ) else 0
234
            dport = len( ports[ dst ] ) + dst_base
235
        ports[ src ][ sport ] = ( dst, dport )
236
        ports[ dst ][ dport ] = ( src, sport )
237
        return sport, dport
238

    
239
    def port( self, src, dst ):
240
        """Get port numbers.
241
            src: source switch name
242
            dst: destination switch name
243
            sport: optional source port (otherwise use lowest src port)
244
            returns: tuple (sport, dport), where
245
                sport = port on source switch leading to the destination switch
246
                dport = port on destination switch leading to the source switch
247
            Note that you can also look up ports using linkInfo()"""
248
        # A bit ugly and slow vs. single-link implementation ;-(
249
        ports = [ ( sport, entry[ 1 ] )
250
                  for sport, entry in self.ports[ src ].items()
251
                  if entry[ 0 ] == dst ]
252
        return ports if len( ports ) != 1 else ports[ 0 ]
253

    
254
    def _linkEntry( self, src, dst, key=None ):
255
        "Helper function: return link entry and key"
256
        entry = self.g[ src ][ dst ]
257
        if key is None:
258
            key = min( entry )
259
        return entry, key
260

    
261
    def linkInfo( self, src, dst, key=None ):
262
        "Return link metadata dict"
263
        entry, key = self._linkEntry( src, dst, key )
264
        return entry[ key ]
265

    
266
    def setlinkInfo( self, src, dst, info, key=None ):
267
        "Set link metadata dict"
268
        entry, key = self._linkEntry( src, dst, key )
269
        entry[ key ] = info
270

    
271
    def nodeInfo( self, name ):
272
        "Return metadata (dict) for node"
273
        return self.g.node[ name ]
274

    
275
    def setNodeInfo( self, name, info ):
276
        "Set metadata (dict) for node"
277
        self.g.node[ name ] = info
278

    
279
    def convertTo( self, cls, data=True, keys=True ):
280
        """Convert to a new object of networkx.MultiGraph-like class cls
281
           data: include node and edge data (default True)
282
           keys: include edge keys as well as edge data (default True)"""
283
        return self.g.convertTo( cls, data=data, keys=keys )
284

    
285
    @staticmethod
286
    def sorted( items ):
287
        "Items sorted in natural (i.e. alphabetical) order"
288
        return sorted( items, key=natural )
289

    
290

    
291
# Our idiom defines additional parameters in build(param...)
292
# pylint: disable=arguments-differ, attribute-defined-outside-init
293

    
294
class SingleSwitchTopo( Topo ):
295
    "Single switch connected to k hosts."
296

    
297
    def build( self, k=2, **_opts ):
298
        "k: number of hosts"
299
        self.k = k
300
        switch = self.addSwitch( 's1' )
301
        for h in irange( 1, k ):
302
            host = self.addHost( 'h%s' % h )
303
            self.addLink( host, switch )
304

    
305

    
306
class SingleSwitchReversedTopo( Topo ):
307
    """Single switch connected to k hosts, with reversed ports.
308
       The lowest-numbered host is connected to the highest-numbered port.
309
       Useful to verify that Mininet properly handles custom port numberings."""
310

    
311
    def build( self, k=2 ):
312
        "k: number of hosts"
313
        self.k = k
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 ) )
319

    
320

    
321
class LinearTopo( Topo ):
322
    "Linear topology of k switches, with n hosts per switch."
323

    
324
    def build( self, k=2, n=1, **_opts):
325
        """k: number of switches
326
           n: number of hosts per switch"""
327
        self.k = k
328
        self.n = n
329

    
330
        if n == 1:
331
            genHostName = lambda i, j: 'h%s' % i
332
        else:
333
            genHostName = lambda i, j: 'h%ss%d' % ( j, i )
334

    
335
        lastSwitch = None
336
        for i in irange( 1, k ):
337
            # Add switch
338
            switch = self.addSwitch( 's%s' % i )
339
            # Add hosts to switch
340
            for j in irange( 1, n ):
341
                host = self.addHost( genHostName( i, j ) )
342
                self.addLink( host, switch )
343
            # Connect switch to previous
344
            if lastSwitch:
345
                self.addLink( switch, lastSwitch )
346
            lastSwitch = switch
347

    
348
# pylint: enable=arguments-differ, attribute-defined-outside-init