Statistics
| Branch: | Tag: | Revision:

mininet / mininet / topo.py @ 38ce329e

History | View | Annotate | Download (10.9 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
        self.edgeinfo = {}
23

    
24
    def add_node( self, node, attr_dict=None, **attrs):
25
        """Add node to graph
26
           attr_dict: attribute dict (optional)
27
           attrs: more attributes (optional)"""
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
        attr_dict = {} if attr_dict is None else attr_dict
37
        attr_dict.update( attrs )
38
        self.node.setdefault( src, {} )
39
        self.node.setdefault( dst, {} )
40
        self.edge.setdefault( src, {} )
41
        self.edge.setdefault( dst, {} )
42
        self.edge[ src ].setdefault( dst, {} )
43
        entry = self.edge[ dst ][ src ] = self.edge[ src ][ dst ]
44
        # If no key, pick next ordinal number
45
        if key is None:
46
            keys = [ k for k in entry.keys() if type( k ) is int ]
47
            key = max( [ 0 ] + keys ) + 1
48
        entry[ key ] = attr_dict
49
        return key
50

    
51
    def nodes( self, data=False):
52
        """Return list of graph nodes
53
           data: return list of ( node, attrs)"""
54
        return self.node.items() if data else self.node.keys()
55

    
56
    def edges_iter( self, data=False, key=False):
57
        "Iterator: return graph edges"
58
        for src, entry in self.edge.iteritems():
59
            for dst, keys in entry.iteritems():
60
                if [ src, dst ] != sorted( [ src, dst ] ):
61
                    # Skip duplicate edges
62
                    continue
63
                for k, attrs in keys.iteritems():
64
                    if data:
65
                        yield( ( src, dst, attrs ) )
66
                    elif key:
67
                        yield( ( src, dst, k ) )
68
                    else:
69
                        yield( ( src, dst ) )
70

    
71
    def edges( self, data=False, key=False ):
72
        "Return list of graph edges"
73
        return list( self.edges_iter( data=data, key=key ) )
74

    
75

    
76
    def __getitem__( self, node ):
77
        "Return link dict for given src node"
78
        return self.edge[ node ]
79

    
80
    def __len__( self ):
81
        "Return the number of nodes"
82
        return len( self.node )
83

    
84
    def convertTo( self, cls, data=False ):
85
        """Convert to a new object of networkx.MultiGraph-like class cls
86
           data: include node and edge data"""
87
        g = cls()
88
        g.add_nodes_from( self.nodes( data=data ) )
89
        g.add_edges_from( self.edges( data=data ) )
90
        return g
91

    
92

    
93
class Topo( object ):
94
    "Data center network representation for structured multi-trees."
95

    
96
    def __init__( self, *args, **params ):
97
        """Topo object. 
98
           Optional named parameters:
99
           hinfo: default host options
100
           sopts: default switch options
101
           lopts: default link options
102
           calls build()"""
103
        self.g = MultiGraph()
104
        self.hopts = params.pop( 'hopts', {} )
105
        self.sopts = params.pop( 'sopts', {} )
106
        self.lopts = params.pop( 'lopts', {} )
107
        self.ports = {}  # ports[src][dst][sport] is port on dst that connects to src
108
        self.build( *args, **params )
109

    
110
    def build( self, *args, **params ):
111
        "Override this method to build your topology."
112
        pass
113

    
114
    def addNode( self, name, **opts ):
115
        """Add Node to graph.
116
           name: name
117
           opts: node options
118
           returns: node name"""
119
        self.g.add_node( name, **opts )
120
        return name
121

    
122
    def addHost( self, name, **opts ):
123
        """Convenience method: Add host to graph.
124
           name: host name
125
           opts: host options
126
           returns: host name"""
127
        if not opts and self.hopts:
128
            opts = self.hopts
129
        return self.addNode(name, **opts)
130

    
131
    def addSwitch( self, name, **opts ):
132
        """Convenience method: Add switch to graph.
133
           name: switch name
134
           opts: switch options
135
           returns: switch name"""
136
        if not opts and self.sopts:
137
            opts = self.sopts
138
        result = self.addNode(name, isSwitch=True, **opts)
139
        return result
140

    
141
    def addLink( self, node1, node2, port1=None, port2=None,
142
                key=None, **opts ):
143
        """node1, node2: nodes to link together
144
           port1, port2: ports (optional)
145
           opts: link options (optional)
146
           returns: link info key"""
147
        if not opts and self.lopts:
148
            opts = self.lopts
149
        port1, port2 = self.addPort(node1, node2, port1, port2)
150
        opts.update( node1=node1, node2=node2, port1=port1, port2=port2 )
151
        assert 'node1' in opts
152
        self.g.add_edge(node1, node2, key, opts )
153
        return key
154

    
155
    def nodes( self, sort=True ):
156
        "Return nodes in graph"
157
        if sort:
158
            return self.sorted( self.g.nodes() )
159
        else:
160
            return self.g.nodes()
161

    
162
    def isSwitch( self, n ):
163
        "Returns true if node is a switch."
164
        return self.g.node[ n ].get( 'isSwitch', False )
165

    
166
    def switches( self, sort=True ):
167
        """Return switches.
168
           sort: sort switches alphabetically
169
           returns: dpids list of dpids"""
170
        return [ n for n in self.nodes( sort ) if self.isSwitch( n ) ]
171

    
172
    def hosts( self, sort=True ):
173
        """Return hosts.
174
           sort: sort hosts alphabetically
175
           returns: list of hosts"""
176
        return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ]
177

    
178
    def links( self, sort=True, withInfo=False, withKeys=False ):
179
        """Return links.
180
           sort: sort links alphabetically
181
           withKeys: return key in tuple
182
           @return links list of ( src, dst [,key ] )"""
183
        if not sort:
184
            return self.g.edges( data=withInfo, key=withKeys )
185
        else:
186
            if withInfo:
187
                links = [ tuple( self.sorted( ( s, d ) ) + [ info ] )
188
                          for s, d, info in self.g.edges( data=True ) ]
189
            elif withKeys:
190
                links = [ tuple( self.sorted( ( s, d ) ) + [ key ] )
191
                          for s, d, key in self.g.edges( key=True ) ]
192
            else:
193
                links = [ tuple ( self.sorted( e ) )
194
                          for e in self.g.edges() ]
195
            return self.sorted( links )
196

    
197
    # This legacy port management mechanism is clunky and will probably
198
    # be removed at some point.
199

    
200
    def addPort( self, src, dst, sport=None, dport=None ):
201
        """Generate port mapping for new edge.
202
            src: source switch name
203
            dst: destination switch name"""
204
        # Initialize if necessary
205
        ports = self.ports
206
        ports.setdefault( src, {} )
207
        ports.setdefault( dst, {} )
208
        # New port: number of outlinks + base
209
        if sport is None:
210
            src_base = 1 if self.isSwitch( src ) else 0
211
            sport = len( ports[ src ] ) + src_base
212
        if dport is None:
213
            dst_base = 1 if self.isSwitch( dst ) else 0
214
            dport = len( ports[ dst ] ) + dst_base
215
        ports[ src ][ sport ] = ( dst, dport )
216
        ports[ dst ][ dport ] = ( src, sport )
217
        return sport, dport
218

    
219
    def port( self, src, dst ):
220
        """Get port numbers.
221
            src: source switch name
222
            dst: destination switch name
223
            sport: optional source port (otherwise use lowest src port)
224
            returns: tuple (sport, dport), where
225
                sport = port on source switch leading to the destination switch
226
                dport = port on destination switch  leading to the source switch
227
            Note that you can also look up ports using linkInfo()"""
228
        # A bit ugly and slow vs. single-link implementation ;-(
229
        ports = [ ( sport, entry[ 1 ] )
230
                  for sport, entry in self.ports[ src ].items()
231
                  if entry[ 0 ] == dst ]
232
        return ports if len( ports ) != 1 else ports[ 0 ]
233

    
234
    def _linkEntry( self, src, dst, key=None ):
235
        "Helper function: return link entry and key"
236
        entry = self.g[ src ][ dst ]
237
        if key is None:
238
            key = min( entry )
239
        return entry, key
240
    
241
    def linkInfo( self, src, dst, key=None ):
242
        "Return link metadata dict"
243
        entry, key = self._linkEntry( src, dst, key )
244
        return entry[ key ]
245

    
246
    def setlinkInfo( self, src, dst, info, key=None ):
247
        "Set link metadata dict"
248
        entry, key = self._linkEntry( src, dst, key )
249
        entry [ key ] = info
250

    
251
    def nodeInfo( self, name ):
252
        "Return metadata (dict) for node"
253
        return self.g.node[ name ]
254

    
255
    def setNodeInfo( self, name, info ):
256
        "Set metadata (dict) for node"
257
        self.g.node[ name ] = info
258

    
259
    @staticmethod
260
    def sorted( items ):
261
        "Items sorted in natural (i.e. alphabetical) order"
262
        return sorted( items, key=natural )
263

    
264

    
265
class SingleSwitchTopo( Topo ):
266
    "Single switch connected to k hosts."
267

    
268
    def build( self, k=2, **opts ):
269
        "k: number of hosts"
270
        self.k = k
271
        switch = self.addSwitch( 's1' )
272
        for h in irange( 1, k ):
273
            host = self.addHost( 'h%s' % h )
274
            self.addLink( host, switch )
275

    
276

    
277
class SingleSwitchReversedTopo( Topo ):
278
    """Single switch connected to k hosts, with reversed ports.
279
       The lowest-numbered host is connected to the highest-numbered port.
280
       Useful to verify that Mininet properly handles custom port numberings."""
281

    
282
    def build( self, k=2 ):
283
        "k: number of hosts"
284
        self.k = k
285
        switch = self.addSwitch( 's1' )
286
        for h in irange( 1, k ):
287
            host = self.addHost( 'h%s' % h )
288
            self.addLink( host, switch,
289
                          port1=0, port2=( k - h + 1 ) )
290

    
291

    
292
class LinearTopo( Topo ):
293
    "Linear topology of k switches, with n hosts per switch."
294

    
295
    def build( self, k=2, n=1, **opts):
296
        """k: number of switches
297
           n: number of hosts per switch"""
298
        self.k = k
299
        self.n = n
300

    
301
        if n == 1:
302
            genHostName = lambda i, j: 'h%s' % i
303
        else:
304
            genHostName = lambda i, j: 'h%ss%d' % ( j, i )
305

    
306
        lastSwitch = None
307
        for i in irange( 1, k ):
308
            # Add switch
309
            switch = self.addSwitch( 's%s' % i )
310
            # Add hosts to switch
311
            for j in irange( 1, n ):
312
                host = self.addHost( genHostName( i, j ) )
313
                self.addLink( host, switch )
314
            # Connect switch to previous
315
            if lastSwitch:
316
                self.addLink( switch, lastSwitch )
317
            lastSwitch = switch