Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ 49654212

History | View | Annotate | Download (15 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
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

    
87

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

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

    
97

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

    
116

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

    
122

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

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

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

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

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

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

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

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

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

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

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

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

    
257
        self.options, self.args = opts.parse_args()
258

    
259
        # We don't accept extra arguments after the options
260
        if self.args:
261
            opts.print_help()
262
            exit()
263

    
264
    def setup( self ):
265
        "Setup and validate environment."
266

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

    
275
    # Maybe we'll reorganize this someday...
276
    # pylint: disable=too-many-branches,too-many-statements
277

    
278
    def begin( self ):
279
        "Create and run mininet."
280

    
281
        if self.options.clean:
282
            cleanup()
283
            exit()
284

    
285
        start = time.time()
286

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

    
304
        topo = buildTopo( TOPOS, self.options.topo )
305
        switch = customConstructor( SWITCHES, self.options.switch )
306
        host = customConstructor( HOSTS, self.options.host )
307
        controller = customConstructor( CONTROLLERS, self.options.controller )
308
        link = customConstructor( LINKS, self.options.link )
309

    
310
        if self.validate:
311
            self.validate( self.options )
312

    
313
        ipBase = self.options.ipbase
314
        xterms = self.options.xterms
315
        mac = self.options.mac
316
        arp = self.options.arp
317
        pin = self.options.pin
318
        listenPort = None
319
        if not self.options.nolistenport:
320
            listenPort = self.options.listenport
321

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

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

    
346
        if self.options.ensure_value( 'nat', False ):
347
            nat = mn.addNAT( *self.options.nat_args, **self.options.nat_kwargs )
348
            nat.configDefault()
349

    
350
        if self.options.pre:
351
            cli( mn, script=self.options.pre )
352

    
353
        test = self.options.test
354
        test = ALTSPELLING.get( test, test )
355

    
356
        mn.start()
357

    
358
        if test == 'none':
359
            pass
360
        elif test == 'all':
361
            mn.waitConnected()
362
            mn.start()
363
            mn.ping()
364
            mn.iperf()
365
        elif test == 'cli':
366
            cli( mn )
367
        elif test != 'build':
368
            mn.waitConnected()
369
            getattr( mn, test )()
370

    
371
        if self.options.post:
372
            cli( mn, script=self.options.post )
373

    
374
        mn.stop()
375

    
376
        elapsed = float( time.time() - start )
377
        info( 'completed in %0.3f seconds\n' % elapsed )
378

    
379

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