Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ b739cd11

History | View | Annotate | Download (14 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
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': 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 parseArgs( self ):
172
        """Parse command-line args and return options object.
173
           returns: opts parse options dict"""
174

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

    
179
        usage = ( '%prog [options]\n'
180
                  '(type %prog -h for details)' )
181

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

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

    
236
        self.options, self.args = opts.parse_args()
237

    
238
        # We don't accept extra arguments after the options
239
        if self.args:
240
            opts.print_help()
241
            exit()
242

    
243
    def setup( self ):
244
        "Setup and validate environment."
245

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

    
254
    def begin( self ):
255
        "Create and run mininet."
256

    
257
        global CLI
258
        
259
        if self.options.clean:
260
            cleanup()
261
            exit()
262

    
263
        start = time.time()
264

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

    
287
        if self.validate:
288
            self.validate( self.options )
289

    
290
        ipBase = self.options.ipbase
291
        xterms = self.options.xterms
292
        mac = self.options.mac
293
        arp = self.options.arp
294
        pin = self.options.pin
295
        listenPort = None
296
        if not self.options.nolistenport:
297
            listenPort = self.options.listenport
298

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

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

    
323
        if self.options.nat:
324
            nat = mn.addNAT()
325
            nat.configDefault()
326

    
327
        if self.options.pre:
328
            CLI( mn, script=self.options.pre )
329

    
330
        test = self.options.test
331
        test = ALTSPELLING.get( test, test )
332

    
333
        mn.start()
334

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

    
348
        if self.options.post:
349
            CLI( mn, script=self.options.post )
350

    
351
        mn.stop()
352

    
353
        elapsed = float( time.time() - start )
354
        info( 'completed in %0.3f seconds\n' % elapsed )
355

    
356

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