Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ ab97dfa1

History | View | Annotate | Download (14.8 KB)

1
#!/usr/bin/env python
2

    
3
"""
4
Mininet runner
5
author: Brandon Heller (brandonh@stanford.edu)
6

    
7
To see options:
8
  sudo mn -h
9

    
10
Example to pull custom params (topo, switch, etc.) from a file:
11
  sudo mn --custom ~/mininet/custom/custom_example.py
12
"""
13

    
14
from optparse import OptionParser
15
import os
16
import sys
17
import time
18

    
19
# Fix setuptools' evil madness, and open up (more?) security holes
20
if 'PYTHONPATH' in os.environ:
21
    sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
22

    
23
from mininet.clean import cleanup
24
from mininet.cli import CLI
25
from mininet.log import lg, LEVELS, info, debug, warn, error
26
from mininet.net import Mininet, MininetWithControlNet, VERSION
27
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
28
                           RYU, NOX, RemoteController, findController, DefaultController,
29
                           UserSwitch, OVSSwitch, OVSBridge,
30
                           OVSLegacyKernelSwitch, IVSSwitch )
31
from mininet.nodelib import LinuxBridge
32
from mininet.link import Link, TCLink
33
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
34
from mininet.topolib import TreeTopo, TorusTopo
35
from mininet.util import customConstructor, splitArgs
36
from mininet.util import buildTopo
37

    
38
from functools import partial
39

    
40
# Experimental! cluster edition prototype
41
from mininet.examples.cluster import ( MininetCluster, RemoteHost,
42
                                       RemoteOVSSwitch, RemoteLink,
43
                                       SwitchBinPlacer, RandomPlacer )
44
from mininet.examples.clustercli import ClusterCLI
45

    
46
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
47

    
48
# built in topologies, created only when run
49
TOPODEF = 'minimal'
50
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
51
          'linear': LinearTopo,
52
          'reversed': SingleSwitchReversedTopo,
53
          'single': SingleSwitchTopo,
54
          'tree': TreeTopo,
55
          'torus': TorusTopo }
56

    
57
SWITCHDEF = 'default'
58
SWITCHES = { 'user': UserSwitch,
59
             'ovs':  OVSSwitch,
60
             'ovsbr' : OVSBridge,
61
             # Keep ovsk for compatibility with 2.0
62
             'ovsk': OVSSwitch,
63
             'ovsl': OVSLegacyKernelSwitch,
64
             'ivs': IVSSwitch,
65
             'lxbr': LinuxBridge,
66
             'default': OVSSwitch }
67

    
68
HOSTDEF = 'proc'
69
HOSTS = { 'proc': Host,
70
          'rt': partial( CPULimitedHost, sched='rt' ),
71
          'cfs': partial( CPULimitedHost, sched='cfs' ) }
72

    
73
CONTROLLERDEF = 'default'
74
CONTROLLERS = { 'ref': Controller,
75
                'ovsc': OVSController,
76
                'nox': NOX,
77
                'remote': RemoteController,
78
                'ryu': RYU,
79
                'default': DefaultController,  # Note: replaced below
80
                'none': lambda name: None }
81

    
82
LINKDEF = 'default'
83
LINKS = { 'default': Link,
84
          'tc': TCLink }
85

    
86

    
87
# optional tests to run
88
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
89
          'none' ]
90

    
91
ALTSPELLING = { 'pingall': 'pingAll',
92
                'pingpair': 'pingPair',
93
                'iperfudp': 'iperfUdp',
94
                'iperfUDP': 'iperfUdp' }
95

    
96

    
97
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
98
    """Convenience function to add choices dicts to OptionParser.
99
       opts: OptionParser instance
100
       choicesDict: dictionary of valid choices, must include default
101
       default: default choice key
102
       name: long option name
103
       help: string"""
104
    if default not in choicesDict:
105
        raise Exception( 'Invalid  default %s for choices dict: %s' %
106
                         ( default, name ) )
107
    if not helpStr:
108
        helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
109
                    '[,param=value...]' )
110
    opts.add_option( '--' + name,
111
                     type='string',
112
                     default = default,
113
                     help = helpStr )
114

    
115

    
116
def version( *_args ):
117
    "Print Mininet version and exit"
118
    print "%s" % VERSION
119
    exit()
120

    
121

    
122
class MininetRunner( object ):
123
    "Build, setup, and run Mininet."
124

    
125
    def __init__( self ):
126
        "Init."
127
        self.options = None
128
        self.args = None  # May be used someday for more CLI scripts
129
        self.validate = None
130

    
131
        self.parseArgs()
132
        self.setup()
133
        self.begin()
134

    
135
    def custom( self, option, opt_str, value, parser ):
136
        """Parse custom file and add params.
137
           option: option e.g. --custom
138
           opt_str: option string e.g. --custom
139
           value: the value the follows the option
140
           parser: option parser instance"""
141
        files = []
142
        if os.path.isfile( value ):
143
            # Accept any single file (including those with commas)
144
            files.append( value )
145
        else:
146
            # Accept a comma-separated list of filenames
147
            files += value.split(',')
148
        
149
        for fileName in files:
150
            customs = {}
151
            if os.path.isfile( fileName ):
152
                execfile( fileName, customs, customs )
153
                for name, val in customs.iteritems():
154
                    self.setCustom( name, val )
155
            else:
156
                raise Exception( 'could not find custom file: %s' % fileName )
157

    
158
    def setCustom( self, name, value ):
159
        "Set custom parameters for MininetRunner."
160
        if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
161
            # Update dictionaries
162
            param = name.upper()
163
            globals()[ param ].update( value )
164
        elif name == 'validate':
165
            # Add custom validate function
166
            self.validate = value
167
        else:
168
            # Add or modify global variable or class
169
            globals()[ name ] = value
170

    
171
    def setNat( self, option, opt_str, value, parser ):
172
        parser.values.nat = True
173
        if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-': #first arg, first char != '-'
174
            value = parser.rargs.pop( 0 )
175
            _, args, kwargs = splitArgs( opt_str + ',' + value )
176
            parser.values.nat_args = args
177
            parser.values.nat_kwargs = kwargs
178
        else:
179
            parser.values.nat_args = []
180
            parser.values.nat_kwargs = {}
181

    
182
    def parseArgs( self ):
183
        """Parse command-line args and return options object.
184
           returns: opts parse options dict"""
185

    
186
        desc = ( "The %prog utility creates Mininet network from the\n"
187
                 "command line. It can create parametrized topologies,\n"
188
                 "invoke the Mininet CLI, and run tests." )
189

    
190
        usage = ( '%prog [options]\n'
191
                  '(type %prog -h for details)' )
192

    
193
        opts = OptionParser( description=desc, usage=usage )
194
        addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
195
        addDictOption( opts, HOSTS, HOSTDEF, 'host' )
196
        addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
197
        addDictOption( opts, LINKS, LINKDEF, 'link' )
198
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
199

    
200
        opts.add_option( '--clean', '-c', action='store_true',
201
                         default=False, help='clean and exit' )
202
        opts.add_option( '--custom', action='callback',
203
                         callback=self.custom,
204
                         type='string', help='read custom classes or params from .py file(s)' )
205
        opts.add_option( '--test', type='choice', choices=TESTS,
206
                         default=TESTS[ 0 ],
207
                         help='|'.join( TESTS ) )
208
        opts.add_option( '--xterms', '-x', action='store_true',
209
                         default=False, help='spawn xterms for each node' )
210
        opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8',
211
                         help='base IP address for hosts' )
212
        opts.add_option( '--mac', action='store_true',
213
                         default=False, help='automatically set host MACs' )
214
        opts.add_option( '--arp', action='store_true',
215
                         default=False, help='set all-pairs ARP entries' )
216
        opts.add_option( '--verbosity', '-v', type='choice',
217
                         choices=LEVELS.keys(), default = 'info',
218
                         help = '|'.join( LEVELS.keys() )  )
219
        opts.add_option( '--innamespace', action='store_true',
220
                         default=False, help='sw and ctrl in namespace?' )
221
        opts.add_option( '--listenport', type='int', default=6634,
222
                         help='base port for passive switch listening' )
223
        opts.add_option( '--nolistenport', action='store_true',
224
                         default=False, help="don't use passive listening " +
225
                         "port")
226
        opts.add_option( '--pre', type='string', default=None,
227
                         help='CLI script to run before tests' )
228
        opts.add_option( '--post', type='string', default=None,
229
                         help='CLI script to run after tests' )
230
        opts.add_option( '--pin', action='store_true',
231
                         default=False, help="pin hosts to CPU cores "
232
                         "(requires --host cfs or --host rt)" )
233
        opts.add_option( '--nat', action='callback', callback=self.setNat,
234
                         help="adds a NAT to the topology that connects Mininet hosts"
235
                         " to the physical network."
236
                         " Warning: This may route any traffic on the machine that uses Mininet's"
237
                         " IP subnet into the Mininet network. If you need to change"
238
                         " Mininet's IP subnet, see the --ipbase option." )
239
        opts.add_option( '--version', action='callback', callback=version,
240
                         help='prints the version and exits' )
241
        opts.add_option( '--cluster', type='string', default=None,
242
                         metavar='server1,server2...',
243
                         help=( 'run on multiple servers (experimental!)' ) )
244
        opts.add_option( '--placement', type='choice',
245
                         choices=PLACEMENT.keys(), default='block',
246
                         metavar='block|random',
247
                         help=( 'node placement for --cluster '
248
                                '(experimental!) ' ) )
249

    
250
        self.options, self.args = opts.parse_args()
251

    
252
        # We don't accept extra arguments after the options
253
        if self.args:
254
            opts.print_help()
255
            exit()
256

    
257
    def setup( self ):
258
        "Setup and validate environment."
259

    
260
        # set logging verbosity
261
        if LEVELS[self.options.verbosity] > LEVELS['output']:
262
            print ( '*** WARNING: selected verbosity level (%s) will hide CLI '
263
                    'output!\n'
264
                    'Please restart Mininet with -v [debug, info, output].'
265
                    % self.options.verbosity )
266
        lg.setLogLevel( self.options.verbosity )
267

    
268
    def begin( self ):
269
        "Create and run mininet."
270

    
271
        if self.options.clean:
272
            cleanup()
273
            exit()
274

    
275
        start = time.time()
276

    
277
        if self.options.controller == 'default':
278
            # Update default based on available controllers
279
            CONTROLLERS[ 'default' ] = findController()
280
            if CONTROLLERS[ 'default' ] is None:
281
                if self.options.switch == 'default':
282
                    # Fall back to OVS Bridge, which does not use an OF controller
283
                    info( '*** No default OpenFlow controller found for default switch!\n' )
284
                    info( '*** Falling back to OVS Bridge\n' )
285
                    self.options.switch = 'ovsbr'
286
                    self.options.controller = 'none'
287
                elif self.options.switch in ( 'ovsbr', 'lxbr' ):
288
                    self.options.controller = 'none'
289
                else:
290
                    raise Exception( "Could not find a default controller for switch %s" %
291
                                     self.options.switch )
292
        
293
        topo = buildTopo( TOPOS, self.options.topo )
294
        switch = customConstructor( SWITCHES, self.options.switch )
295
        host = customConstructor( HOSTS, self.options.host )
296
        controller = customConstructor( CONTROLLERS, self.options.controller )
297
        link = customConstructor( LINKS, self.options.link )
298

    
299
        if self.validate:
300
            self.validate( self.options )
301

    
302
        ipBase = self.options.ipbase
303
        xterms = self.options.xterms
304
        mac = self.options.mac
305
        arp = self.options.arp
306
        pin = self.options.pin
307
        listenPort = None
308
        if not self.options.nolistenport:
309
            listenPort = self.options.listenport
310

    
311
        # Handle inNamespace, cluster options
312
        inNamespace = self.options.innamespace
313
        cluster = self.options.cluster
314
        if inNamespace and cluster:
315
            print "Please specify --innamespace OR --cluster"
316
            exit()
317
        Net = MininetWithControlNet if inNamespace else Mininet
318
        cli = ClusterCLI if cluster else CLI
319
        if cluster:
320
            warn( '*** WARNING: Experimental cluster mode!\n'
321
              '*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
322
            host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
323
            Net = partial( MininetCluster, servers=cluster.split( ',' ),
324
                           placement=PLACEMENT[ self.options.placement ] )
325

    
326
        mn = Net( topo=topo,
327
                  switch=switch, host=host, controller=controller,
328
                  link=link,
329
                  ipBase=ipBase,
330
                  inNamespace=inNamespace,
331
                  xterms=xterms, autoSetMacs=mac,
332
                  autoStaticArp=arp, autoPinCpus=pin,
333
                  listenPort=listenPort )
334

    
335
        if self.options.ensure_value( 'nat', False ):
336
            nat = mn.addNAT( *self.options.nat_args, **self.options.nat_kwargs )
337
            nat.configDefault()
338

    
339
        if self.options.pre:
340
            cli( mn, script=self.options.pre )
341

    
342
        test = self.options.test
343
        test = ALTSPELLING.get( test, test )
344

    
345
        mn.start()
346

    
347
        if test == 'none':
348
            pass
349
        elif test == 'all':
350
            mn.waitConnected()
351
            mn.start()
352
            mn.ping()
353
            mn.iperf()
354
        elif test == 'cli':
355
            cli( mn )
356
        elif test != 'build':
357
            mn.waitConnected()
358
            getattr( mn, test )()
359

    
360
        if self.options.post:
361
            cli( mn, script=self.options.post )
362

    
363
        mn.stop()
364

    
365
        elapsed = float( time.time() - start )
366
        info( 'completed in %0.3f seconds\n' % elapsed )
367

    
368

    
369
if __name__ == "__main__":
370
    try:
371
        MininetRunner()
372
    except KeyboardInterrupt:
373
        info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
374
        cleanup()
375
    except Exception:
376
        # Print exception
377
        type_, val_, trace_ = sys.exc_info()
378
        errorMsg = ( "-"*80 + "\n" +
379
                     "Caught exception. Cleaning up...\n\n" +
380
                     "%s: %s\n" % ( type_.__name__, val_ ) +
381
                     "-"*80 + "\n" )
382
        error( errorMsg )
383
        # Print stack trace to debug log
384
        import traceback
385
        stackTrace = traceback.format_exc()
386
        debug( stackTrace + "\n" )
387
        cleanup()