Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ c916f3ee

History | View | Annotate | Download (15.1 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,
29
                           DefaultController,
30
                           UserSwitch, OVSSwitch, OVSBridge,
31
                           OVSLegacyKernelSwitch, IVSSwitch )
32
from mininet.nodelib import LinuxBridge
33
from mininet.link import Link, TCLink, OVSLink
34
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
35
from mininet.topolib import TreeTopo, TorusTopo
36
from mininet.util import customConstructor, splitArgs
37
from mininet.util import buildTopo
38

    
39
from functools import partial
40

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

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

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

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

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

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

    
83
LINKDEF = 'default'
84
LINKS = { 'default': Link,
85
          'tc': TCLink,
86
          'ovs': OVSLink }
87

    
88

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

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

    
98

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

    
117

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

    
123

    
124
class MininetRunner( object ):
125
    "Build, setup, and run Mininet."
126

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

    
133
        self.parseArgs()
134
        self.setup()
135
        self.begin()
136

    
137
    def custom( self, _option, _opt_str, value, _parser ):
138
        """Parse custom file and add params.
139
           option: option e.g. --custom
140
           opt_str: option string e.g. --custom
141
           value: the value the follows the option
142
           parser: option parser instance"""
143
        files = []
144
        if os.path.isfile( value ):
145
            # Accept any single file (including those with commas)
146
            files.append( value )
147
        else:
148
            # Accept a comma-separated list of filenames
149
            files += value.split(',')
150

    
151
        for fileName in files:
152
            customs = {}
153
            if os.path.isfile( fileName ):
154
                execfile( fileName, customs, customs )
155
                for name, val in customs.iteritems():
156
                    self.setCustom( name, val )
157
            else:
158
                raise Exception( 'could not find custom file: %s' % fileName )
159

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

    
173
    def setNat( self, _option, opt_str, value, parser ):
174
        "Set NAT option(s)"
175
        assert self  # satisfy pylint
176
        parser.values.nat = True
177
        # first arg, first char != '-'
178
        if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-':
179
            value = parser.rargs.pop( 0 )
180
            _, args, kwargs = splitArgs( opt_str + ',' + value )
181
            parser.values.nat_args = args
182
            parser.values.nat_kwargs = kwargs
183
        else:
184
            parser.values.nat_args = []
185
            parser.values.nat_kwargs = {}
186

    
187
    def parseArgs( self ):
188
        """Parse command-line args and return options object.
189
           returns: opts parse options dict"""
190

    
191
        desc = ( "The %prog utility creates Mininet network from the\n"
192
                 "command line. It can create parametrized topologies,\n"
193
                 "invoke the Mininet CLI, and run tests." )
194

    
195
        usage = ( '%prog [options]\n'
196
                  '(type %prog -h for details)' )
197

    
198
        opts = OptionParser( description=desc, usage=usage )
199
        addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
200
        addDictOption( opts, HOSTS, HOSTDEF, 'host' )
201
        addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
202
        addDictOption( opts, LINKS, LINKDEF, 'link' )
203
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
204

    
205
        opts.add_option( '--clean', '-c', action='store_true',
206
                         default=False, help='clean and exit' )
207
        opts.add_option( '--custom', action='callback',
208
                         callback=self.custom,
209
                         type='string',
210
                         help='read custom classes or params from .py file(s)'
211
                         )
212

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

    
260
        self.options, self.args = opts.parse_args()
261

    
262
        # We don't accept extra arguments after the options
263
        if self.args:
264
            opts.print_help()
265
            exit()
266

    
267
    def setup( self ):
268
        "Setup and validate environment."
269

    
270
        # set logging verbosity
271
        if LEVELS[self.options.verbosity] > LEVELS['output']:
272
            print ( '*** WARNING: selected verbosity level (%s) will hide CLI '
273
                    'output!\n'
274
                    'Please restart Mininet with -v [debug, info, output].'
275
                    % self.options.verbosity )
276
        lg.setLogLevel( self.options.verbosity )
277

    
278
    # Maybe we'll reorganize this someday...
279
    # pylint: disable=too-many-branches,too-many-statements
280

    
281
    def begin( self ):
282
        "Create and run mininet."
283

    
284
        if self.options.clean:
285
            cleanup()
286
            exit()
287

    
288
        start = time.time()
289

    
290
        if self.options.controller == 'default':
291
            # Update default based on available controllers
292
            CONTROLLERS[ 'default' ] = findController()
293
            if CONTROLLERS[ 'default' ] is None:
294
                if self.options.switch == 'default':
295
                    info( '*** No default OpenFlow controller found '
296
                          'for default switch!\n' )
297
                    info( '*** Falling back to OVS Bridge\n' )
298
                    self.options.switch = 'ovsbr'
299
                    self.options.controller = 'none'
300
                elif self.options.switch in ( 'ovsbr', 'lxbr' ):
301
                    self.options.controller = 'none'
302
                else:
303
                    raise Exception( "Could not find a default controller "
304
                                     "for switch %s" %
305
                                     self.options.switch )
306

    
307
        topo = buildTopo( TOPOS, self.options.topo )
308
        switch = customConstructor( SWITCHES, self.options.switch )
309
        host = customConstructor( HOSTS, self.options.host )
310
        controller = customConstructor( CONTROLLERS, self.options.controller )
311
        link = customConstructor( LINKS, self.options.link )
312

    
313
        if self.validate:
314
            self.validate( self.options )
315

    
316
        ipBase = self.options.ipbase
317
        xterms = self.options.xterms
318
        mac = self.options.mac
319
        arp = self.options.arp
320
        pin = self.options.pin
321
        listenPort = None
322
        if not self.options.nolistenport:
323
            listenPort = self.options.listenport
324

    
325
        # Handle inNamespace, cluster options
326
        inNamespace = self.options.innamespace
327
        cluster = self.options.cluster
328
        if inNamespace and cluster:
329
            print "Please specify --innamespace OR --cluster"
330
            exit()
331
        Net = MininetWithControlNet if inNamespace else Mininet
332
        cli = ClusterCLI if cluster else CLI
333
        if cluster:
334
            warn( '*** WARNING: Experimental cluster mode!\n'
335
                  '*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
336
            host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
337
            Net = partial( MininetCluster, servers=cluster.split( ',' ),
338
                           placement=PLACEMENT[ self.options.placement ] )
339

    
340
        mn = Net( topo=topo,
341
                  switch=switch, host=host, controller=controller,
342
                  link=link,
343
                  ipBase=ipBase,
344
                  inNamespace=inNamespace,
345
                  xterms=xterms, autoSetMacs=mac,
346
                  autoStaticArp=arp, autoPinCpus=pin,
347
                  listenPort=listenPort )
348

    
349
        if self.options.ensure_value( 'nat', False ):
350
            nat = mn.addNAT( *self.options.nat_args,
351
                             **self.options.nat_kwargs )
352
            nat.configDefault()
353

    
354
        if self.options.pre:
355
            cli( mn, script=self.options.pre )
356

    
357
        test = self.options.test
358
        test = ALTSPELLING.get( test, test )
359

    
360
        mn.start()
361

    
362
        if test == 'none':
363
            pass
364
        elif test == 'all':
365
            mn.waitConnected()
366
            mn.start()
367
            mn.ping()
368
            mn.iperf()
369
        elif test == 'cli':
370
            cli( mn )
371
        elif test != 'build':
372
            mn.waitConnected()
373
            getattr( mn, test )()
374

    
375
        if self.options.post:
376
            cli( mn, script=self.options.post )
377

    
378
        mn.stop()
379

    
380
        elapsed = float( time.time() - start )
381
        info( 'completed in %0.3f seconds\n' % elapsed )
382

    
383

    
384
if __name__ == "__main__":
385
    try:
386
        MininetRunner()
387
    except KeyboardInterrupt:
388
        info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
389
        cleanup()
390
    except Exception:
391
        # Print exception
392
        type_, val_, trace_ = sys.exc_info()
393
        errorMsg = ( "-"*80 + "\n" +
394
                     "Caught exception. Cleaning up...\n\n" +
395
                     "%s: %s\n" % ( type_.__name__, val_ ) +
396
                     "-"*80 + "\n" )
397
        error( errorMsg )
398
        # Print stack trace to debug log
399
        import traceback
400
        stackTrace = traceback.format_exc()
401
        debug( stackTrace + "\n" )
402
        cleanup()