Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ 125e6697

History | View | Annotate | Download (15.2 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
                           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 customClass, specialClass, 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
                                       ClusterCleanup )
46
from mininet.examples.clustercli import ClusterCLI
47

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

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

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

    
69
HOSTDEF = 'proc'
70
HOSTS = { 'proc': Host,
71
          'rt': specialClass( CPULimitedHost, defaults=dict( sched='rt' ) ),
72
          'cfs': specialClass( CPULimitedHost, defaults=dict( 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, **kwargs ):
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
       helpStr: help string
106
       kwargs: additional arguments to add_option"""
107
    if not helpStr:
108
        helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
109
                    '[,param=value...]' )
110
    params = dict( type='string', default=default, help=helpStr )
111
    params.update( **kwargs )
112
    opts.add_option( '--' + name, **params )
113

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

    
119

    
120
class MininetRunner( object ):
121
    "Build, setup, and run Mininet."
122

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

    
129
        self.parseArgs()
130
        self.setup()
131
        self.begin()
132

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
280
        if self.options.cluster:
281
            servers = self.options.cluster.split( ',' )
282
            for server in servers:
283
                ClusterCleanup.add( server )
284

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

    
289
        start = time.time()
290

    
291
        if not self.options.controller:
292
            # Update default based on available controllers
293
            CONTROLLERS[ 'default' ] = findController()
294
            self.options.controller = [ 'default' ]
295
            if not CONTROLLERS[ 'default' ]:
296
                self.options.controller = [ 'none' ]
297
                if self.options.switch == 'default':
298
                    info( '*** No default OpenFlow controller found '
299
                          'for default switch!\n' )
300
                    info( '*** Falling back to OVS Bridge\n' )
301
                    self.options.switch = 'ovsbr'
302
                elif self.options.switch not in ( 'ovsbr', 'lxbr' ):
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 = customClass( SWITCHES, self.options.switch )
309
        host = customClass( HOSTS, self.options.host )
310
        controller = [ customClass( CONTROLLERS, c )
311
                       for c in self.options.controller ]
312
        link = customClass( LINKS, self.options.link )
313

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

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

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

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

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

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

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

    
361
        mn.start()
362

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

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

    
379
        mn.stop()
380

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

    
384

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