Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ 3ac0dd70

History | View | Annotate | Download (14.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, 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 custom, customConstructor
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 DemoCLI as 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': custom( CPULimitedHost, sched='rt' ),
71
          'cfs': custom( 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
def custom( option, opt_str, value, parser, *args, **kwargs ):
122
    """Parse custom file and add params.
123
       option: option e.g. --custom
124
       opt_str: option string e.g. --custom
125
       value: the value the follows the option
126
       parser: option parser instance
127
       args: empty tuple
128
       kwargs: dict that contains { self : MininetRunner reference }"""
129

    
130
    self = kwargs['self']
131
    files = []
132
    if os.path.isfile( value ):
133
        # accept any single file (including those with commas)
134
        files.append( value )
135
    else:
136
        # accept a comma-separated list of filenames
137
        files += value.split(',')
138

    
139
    for fileName in files:
140
        customs = {}
141
        if os.path.isfile( fileName ):
142
            execfile( fileName, customs, customs )
143
            for name, val in customs.iteritems():
144
                self.setCustom( name, val )
145
        else:
146
            raise Exception( 'could not find custom file: %s' % fileName )
147

    
148
class MininetRunner( object ):
149
    "Build, setup, and run Mininet."
150

    
151
    def __init__( self ):
152
        "Init."
153
        self.options = None
154
        self.args = None  # May be used someday for more CLI scripts
155
        self.validate = None
156

    
157
        self.parseArgs()
158
        self.setup()
159
        self.begin()
160

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

    
174
    def parseArgs( self ):
175
        """Parse command-line args and return options object.
176
           returns: opts parse options dict"""
177

    
178
        desc = ( "The %prog utility creates Mininet network from the\n"
179
                 "command line. It can create parametrized topologies,\n"
180
                 "invoke the Mininet CLI, and run tests." )
181

    
182
        usage = ( '%prog [options]\n'
183
                  '(type %prog -h for details)' )
184

    
185
        opts = OptionParser( description=desc, usage=usage )
186
        addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
187
        addDictOption( opts, HOSTS, HOSTDEF, 'host' )
188
        addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
189
        addDictOption( opts, LINKS, LINKDEF, 'link' )
190
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
191

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

    
239
        self.options, self.args = opts.parse_args()
240

    
241
        # We don't accept extra arguments after the options
242
        if self.args:
243
            opts.print_help()
244
            exit()
245

    
246
    def setup( self ):
247
        "Setup and validate environment."
248

    
249
        # set logging verbosity
250
        if LEVELS[self.options.verbosity] > LEVELS['output']:
251
            print ( '*** WARNING: selected verbosity level (%s) will hide CLI '
252
                    'output!\n'
253
                    'Please restart Mininet with -v [debug, info, output].'
254
                    % self.options.verbosity )
255
        lg.setLogLevel( self.options.verbosity )
256

    
257
    def begin( self ):
258
        "Create and run mininet."
259

    
260
        global CLI
261
        
262
        if self.options.clean:
263
            cleanup()
264
            exit()
265

    
266
        start = time.time()
267

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

    
290
        if self.validate:
291
            self.validate( self.options )
292

    
293
        ipBase = self.options.ipbase
294
        xterms = self.options.xterms
295
        mac = self.options.mac
296
        arp = self.options.arp
297
        pin = self.options.pin
298
        listenPort = None
299
        if not self.options.nolistenport:
300
            listenPort = self.options.listenport
301

    
302
        # Handle inNamespace, cluster options
303
        inNamespace = self.options.innamespace
304
        cluster = self.options.cluster
305
        if inNamespace and cluster:
306
            print "Please specify --innamespace OR --cluster"
307
            exit()
308
        Net = MininetWithControlNet if inNamespace else Mininet
309
        if cluster:
310
            warn( '*** WARNING: Experimental cluster mode!\n'
311
              '*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
312
            host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
313
            CLI = ClusterCLI
314
            Net = partial( MininetCluster, servers=cluster.split( ',' ),
315
                           placement=PLACEMENT[ self.options.placement ] )
316

    
317
        mn = Net( topo=topo,
318
                  switch=switch, host=host, controller=controller,
319
                  link=link,
320
                  ipBase=ipBase,
321
                  inNamespace=inNamespace,
322
                  xterms=xterms, autoSetMacs=mac,
323
                  autoStaticArp=arp, autoPinCpus=pin,
324
                  listenPort=listenPort )
325

    
326
        if self.options.nat:
327
            nat = mn.addNAT()
328
            nat.configDefault()
329

    
330
        if self.options.pre:
331
            CLI( mn, script=self.options.pre )
332

    
333
        test = self.options.test
334
        test = ALTSPELLING.get( test, test )
335

    
336
        mn.start()
337

    
338
        if test == 'none':
339
            pass
340
        elif test == 'all':
341
            mn.waitConnected()
342
            mn.start()
343
            mn.ping()
344
            mn.iperf()
345
        elif test == 'cli':
346
            CLI( mn )
347
        elif test != 'build':
348
            mn.waitConnected()
349
            getattr( mn, test )()
350

    
351
        if self.options.post:
352
            CLI( mn, script=self.options.post )
353

    
354
        mn.stop()
355

    
356
        elapsed = float( time.time() - start )
357
        info( 'completed in %0.3f seconds\n' % elapsed )
358

    
359

    
360
if __name__ == "__main__":
361
    try:
362
        MininetRunner()
363
    except KeyboardInterrupt:
364
        info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
365
        cleanup()
366
    except Exception:
367
        # Print exception
368
        type_, val_, trace_ = sys.exc_info()
369
        errorMsg = ( "-"*80 + "\n" +
370
                     "Caught exception. Cleaning up...\n\n" +
371
                     "%s: %s\n" % ( type_.__name__, val_ ) +
372
                     "-"*80 + "\n" )
373
        error( errorMsg )
374
        # Print stack trace to debug log
375
        import traceback
376
        stackTrace = traceback.format_exc()
377
        debug( stackTrace + "\n" )
378
        cleanup()