Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ ef3c8856

History | View | Annotate | Download (15.4 KB)

1 a6b47322 Brandon Heller
#!/usr/bin/env python
2
3 8895862a Bob Lantz
"""
4
Mininet runner
5
author: Brandon Heller (brandonh@stanford.edu)
6 befa1310 Brandon Heller
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 8895862a Bob Lantz
"""
13 a6b47322 Brandon Heller
14
from optparse import OptionParser
15 e2b799b8 Bob Lantz
import os
16 befa1310 Brandon Heller
import sys
17 a6b47322 Brandon Heller
import time
18
19 e2b799b8 Bob Lantz
# 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 3eb5abe6 Brandon Heller
from mininet.clean import cleanup
24 d869d820 Bob Lantz
from mininet.cli import CLI
25 c265deed Bob Lantz
from mininet.log import lg, LEVELS, info, debug, warn, error
26 39128f8c Bob Lantz
from mininet.net import Mininet, MininetWithControlNet, VERSION
27 612b21cb Bob Lantz
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
28 c2be20f0 Bob Lantz
                           Ryu, NOX, RemoteController, findController,
29 6cb68f26 Bob Lantz
                           DefaultController, NullController,
30 bdad3e8c Bob Lantz
                           UserSwitch, OVSSwitch, OVSBridge,
31 79c944ae Bob Lantz
                           IVSSwitch )
32 38addf2e Bob Lantz
from mininet.nodelib import LinuxBridge
33 c069542c Bob Lantz
from mininet.link import Link, TCLink, OVSLink
34 6cb68f26 Bob Lantz
from mininet.topo import ( SingleSwitchTopo, LinearTopo,
35
                           SingleSwitchReversedTopo, MinimalTopo )
36 b5962e8e Bob Lantz
from mininet.topolib import TreeTopo, TorusTopo
37 f6f6d928 Bob Lantz
from mininet.util import customClass, specialClass, splitArgs
38 928c0761 Brandon Heller
from mininet.util import buildTopo
39 a6b47322 Brandon Heller
40 c265deed Bob Lantz
from functools import partial
41
42
# Experimental! cluster edition prototype
43
from mininet.examples.cluster import ( MininetCluster, RemoteHost,
44
                                       RemoteOVSSwitch, RemoteLink,
45 acdcf9b6 Bob Lantz
                                       SwitchBinPlacer, RandomPlacer,
46
                                       ClusterCleanup )
47 dd876e69 Bob Lantz
from mininet.examples.clustercli import ClusterCLI
48 c265deed Bob Lantz
49
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
50 2db4268b Bob Lantz
51 a6b47322 Brandon Heller
# built in topologies, created only when run
52 8895862a Bob Lantz
TOPODEF = 'minimal'
53 6cb68f26 Bob Lantz
TOPOS = { 'minimal': MinimalTopo,
54 edf60032 Brandon Heller
          'linear': LinearTopo,
55
          'reversed': SingleSwitchReversedTopo,
56
          'single': SingleSwitchTopo,
57 b5962e8e Bob Lantz
          'tree': TreeTopo,
58
          'torus': TorusTopo }
59 8895862a Bob Lantz
60 1b69ea13 Bob Lantz
SWITCHDEF = 'default'
61 7d557fd7 Bob Lantz
SWITCHES = { 'user': UserSwitch,
62 7a3159c9 Bob Lantz
             'ovs': OVSSwitch,
63 1b69ea13 Bob Lantz
             'ovsbr' : OVSBridge,
64 38addf2e Bob Lantz
             # Keep ovsk for compatibility with 2.0
65
             'ovsk': OVSSwitch,
66
             'ivs': IVSSwitch,
67 1b69ea13 Bob Lantz
             'lxbr': LinuxBridge,
68
             'default': OVSSwitch }
69 a6b47322 Brandon Heller
70 216a4b7c Bob Lantz
HOSTDEF = 'proc'
71
HOSTS = { 'proc': Host,
72 f6f6d928 Bob Lantz
          'rt': specialClass( CPULimitedHost, defaults=dict( sched='rt' ) ),
73
          'cfs': specialClass( CPULimitedHost, defaults=dict( sched='cfs' ) ) }
74 a6b47322 Brandon Heller
75 72fd120d Cody Burkard
CONTROLLERDEF = 'default'
76 6cb68f26 Bob Lantz
77 8895862a Bob Lantz
CONTROLLERS = { 'ref': Controller,
78 9addfc13 Bob Lantz
                'ovsc': OVSController,
79 14ff3ad3 Bob Lantz
                'nox': NOX,
80 9addfc13 Bob Lantz
                'remote': RemoteController,
81 c2be20f0 Bob Lantz
                'ryu': Ryu,
82 1b69ea13 Bob Lantz
                'default': DefaultController,  # Note: replaced below
83 6cb68f26 Bob Lantz
                'none': NullController }
84 a6b47322 Brandon Heller
85 ff568819 Bob Lantz
LINKDEF = 'default'
86
LINKS = { 'default': Link,
87 c069542c Bob Lantz
          'tc': TCLink,
88
          'ovs': OVSLink }
89 216a4b7c Bob Lantz
90
91 a6b47322 Brandon Heller
# optional tests to run
92 d869d820 Bob Lantz
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
93 edf60032 Brandon Heller
          'none' ]
94 8895862a Bob Lantz
95 edf60032 Brandon Heller
ALTSPELLING = { 'pingall': 'pingAll',
96
                'pingpair': 'pingPair',
97
                'iperfudp': 'iperfUdp',
98 8838c30e Bob Lantz
                'iperfUDP': 'iperfUdp' }
99 8895862a Bob Lantz
100 216a4b7c Bob Lantz
101 6cb68f26 Bob Lantz
def addDictOption( opts, choicesDict, default, name, **kwargs ):
102
    """Convenience function to add choices dicts to OptionParser.
103
       opts: OptionParser instance
104
       choicesDict: dictionary of valid choices, must include default
105
       default: default choice key
106
       name: long option name
107
       kwargs: additional arguments to add_option"""
108 c60764c3 David Mahler
    helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
109
                '[,param=value...]' )
110 6cb68f26 Bob Lantz
    helpList = [ '%s=%s' % ( k, v.__name__ )
111
                 for k, v in choicesDict.items() ]
112
    helpStr += ' ' + ( ' '.join( helpList ) )
113 125e6697 Bob Lantz
    params = dict( type='string', default=default, help=helpStr )
114
    params.update( **kwargs )
115
    opts.add_option( '--' + name, **params )
116 a6b47322 Brandon Heller
117 39128f8c Bob Lantz
def version( *_args ):
118
    "Print Mininet version and exit"
119 d85a58fe Bob Lantz
    print "%s" % VERSION
120 39128f8c Bob Lantz
    exit()
121
122 3ac0dd70 Brian O'Connor
123 8895862a Bob Lantz
class MininetRunner( object ):
124
    "Build, setup, and run Mininet."
125 a6b47322 Brandon Heller
126 8895862a Bob Lantz
    def __init__( self ):
127
        "Init."
128 a6b47322 Brandon Heller
        self.options = None
129 d869d820 Bob Lantz
        self.args = None  # May be used someday for more CLI scripts
130 befa1310 Brandon Heller
        self.validate = None
131 723d068c Brandon Heller
132 8895862a Bob Lantz
        self.parseArgs()
133 a6b47322 Brandon Heller
        self.setup()
134
        self.begin()
135 e0cfcdd5 Bob Lantz
136 49654212 Bob Lantz
    def custom( self, _option, _opt_str, value, _parser ):
137 b739cd11 Bob Lantz
        """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 5a530af1 Bob Lantz
150 b739cd11 Bob Lantz
        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 c3a44400 Bob Lantz
    def setCustom( self, name, value ):
160 e0cfcdd5 Bob Lantz
        "Set custom parameters for MininetRunner."
161 c3a44400 Bob Lantz
        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 e0cfcdd5 Bob Lantz
172 49654212 Bob Lantz
    def setNat( self, _option, opt_str, value, parser ):
173
        "Set NAT option(s)"
174
        assert self  # satisfy pylint
175 af1ccf93 Brian O'Connor
        parser.values.nat = True
176 49654212 Bob Lantz
        # first arg, first char != '-'
177
        if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-':
178 af1ccf93 Brian O'Connor
            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 8895862a Bob Lantz
    def parseArgs( self ):
187
        """Parse command-line args and return options object.
188
           returns: opts parse options dict"""
189 befa1310 Brandon Heller
190 f2e7884a Bob Lantz
        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 01e0758e Bob Lantz
        usage = ( '%prog [options]\n'
195
                  '(type %prog -h for details)' )
196
197
        opts = OptionParser( description=desc, usage=usage )
198 6cb68f26 Bob Lantz
        addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
199 8895862a Bob Lantz
        addDictOption( opts, HOSTS, HOSTDEF, 'host' )
200 125e6697 Bob Lantz
        addDictOption( opts, CONTROLLERS, [], 'controller', action='append' )
201 ff568819 Bob Lantz
        addDictOption( opts, LINKS, LINKDEF, 'link' )
202 216a4b7c Bob Lantz
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
203 8895862a Bob Lantz
204 3eb5abe6 Brandon Heller
        opts.add_option( '--clean', '-c', action='store_true',
205 edf60032 Brandon Heller
                         default=False, help='clean and exit' )
206 b739cd11 Bob Lantz
        opts.add_option( '--custom', action='callback',
207 593fc365 Brian O'Connor
                         callback=self.custom,
208 49654212 Bob Lantz
                         type='string',
209 7a3159c9 Bob Lantz
                         help='read custom classes or params from .py file(s)'
210
                         )
211
212 8895862a Bob Lantz
        opts.add_option( '--test', type='choice', choices=TESTS,
213 edf60032 Brandon Heller
                         default=TESTS[ 0 ],
214
                         help='|'.join( TESTS ) )
215 8895862a Bob Lantz
        opts.add_option( '--xterms', '-x', action='store_true',
216 edf60032 Brandon Heller
                         default=False, help='spawn xterms for each node' )
217 82f483f5 Bob Lantz
        opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8',
218
                         help='base IP address for hosts' )
219 8895862a Bob Lantz
        opts.add_option( '--mac', action='store_true',
220 edf60032 Brandon Heller
                         default=False, help='automatically set host MACs' )
221 8895862a Bob Lantz
        opts.add_option( '--arp', action='store_true',
222 edf60032 Brandon Heller
                         default=False, help='set all-pairs ARP entries' )
223 8895862a Bob Lantz
        opts.add_option( '--verbosity', '-v', type='choice',
224 edf60032 Brandon Heller
                         choices=LEVELS.keys(), default = 'info',
225
                         help = '|'.join( LEVELS.keys() )  )
226 d869d820 Bob Lantz
        opts.add_option( '--innamespace', action='store_true',
227 edf60032 Brandon Heller
                         default=False, help='sw and ctrl in namespace?' )
228 bd964adb Brandon Heller
        opts.add_option( '--listenport', type='int', default=6634,
229 216a4b7c Bob Lantz
                         help='base port for passive switch listening' )
230 0a9358c9 Brandon Heller
        opts.add_option( '--nolistenport', action='store_true',
231 9330a33f Brandon Heller
                         default=False, help="don't use passive listening " +
232
                         "port")
233 d869d820 Bob Lantz
        opts.add_option( '--pre', type='string', default=None,
234 edf60032 Brandon Heller
                         help='CLI script to run before tests' )
235 d869d820 Bob Lantz
        opts.add_option( '--post', type='string', default=None,
236 edf60032 Brandon Heller
                         help='CLI script to run after tests' )
237 197b083f Bob Lantz
        opts.add_option( '--pin', action='store_true',
238
                         default=False, help="pin hosts to CPU cores "
239
                         "(requires --host cfs or --host rt)" )
240 af1ccf93 Brian O'Connor
        opts.add_option( '--nat', action='callback', callback=self.setNat,
241 ef3c8856 Bob Lantz
                         help="[option=val...] adds a NAT to the topology that"
242 49654212 Bob Lantz
                         " connects Mininet hosts to the physical network."
243
                         " Warning: This may route any traffic on the machine"
244
                         " that uses Mininet's"
245
                         " IP subnet into the Mininet network."
246
                         " If you need to change"
247 af1ccf93 Brian O'Connor
                         " Mininet's IP subnet, see the --ipbase option." )
248 3ac0dd70 Brian O'Connor
        opts.add_option( '--version', action='callback', callback=version,
249
                         help='prints the version and exits' )
250 c265deed Bob Lantz
        opts.add_option( '--cluster', type='string', default=None,
251
                         metavar='server1,server2...',
252
                         help=( 'run on multiple servers (experimental!)' ) )
253
        opts.add_option( '--placement', type='choice',
254
                         choices=PLACEMENT.keys(), default='block',
255
                         metavar='block|random',
256
                         help=( 'node placement for --cluster '
257
                                '(experimental!) ' ) )
258 87737a70 Bob Lantz
259 d869d820 Bob Lantz
        self.options, self.args = opts.parse_args()
260 8895862a Bob Lantz
261 294bbad4 Bob Lantz
        # We don't accept extra arguments after the options
262
        if self.args:
263
            opts.print_help()
264
            exit()
265
266 8895862a Bob Lantz
    def setup( self ):
267
        "Setup and validate environment."
268 a6b47322 Brandon Heller
269
        # set logging verbosity
270 cdeaca86 Brandon Heller
        if LEVELS[self.options.verbosity] > LEVELS['output']:
271 1a40cd04 Brandon Heller
            print ( '*** WARNING: selected verbosity level (%s) will hide CLI '
272
                    'output!\n'
273 1dcc0476 Bob Lantz
                    'Please restart Mininet with -v [debug, info, output].'
274
                    % self.options.verbosity )
275 8895862a Bob Lantz
        lg.setLogLevel( self.options.verbosity )
276 a6b47322 Brandon Heller
277 49654212 Bob Lantz
    # Maybe we'll reorganize this someday...
278
    # pylint: disable=too-many-branches,too-many-statements
279
280 8895862a Bob Lantz
    def begin( self ):
281
        "Create and run mininet."
282 a6b47322 Brandon Heller
283 acdcf9b6 Bob Lantz
        if self.options.cluster:
284
            servers = self.options.cluster.split( ',' )
285
            for server in servers:
286
                ClusterCleanup.add( server )
287
288 3eb5abe6 Brandon Heller
        if self.options.clean:
289
            cleanup()
290
            exit()
291
292 a6b47322 Brandon Heller
        start = time.time()
293
294 125e6697 Bob Lantz
        if not self.options.controller:
295 1b69ea13 Bob Lantz
            # Update default based on available controllers
296
            CONTROLLERS[ 'default' ] = findController()
297 125e6697 Bob Lantz
            self.options.controller = [ 'default' ]
298
            if not CONTROLLERS[ 'default' ]:
299
                self.options.controller = [ 'none' ]
300 1b69ea13 Bob Lantz
                if self.options.switch == 'default':
301 49654212 Bob Lantz
                    info( '*** No default OpenFlow controller found '
302
                          'for default switch!\n' )
303 1b69ea13 Bob Lantz
                    info( '*** Falling back to OVS Bridge\n' )
304
                    self.options.switch = 'ovsbr'
305 125e6697 Bob Lantz
                elif self.options.switch not in ( 'ovsbr', 'lxbr' ):
306 49654212 Bob Lantz
                    raise Exception( "Could not find a default controller "
307
                                     "for switch %s" %
308 1b69ea13 Bob Lantz
                                     self.options.switch )
309 5a530af1 Bob Lantz
310 928c0761 Brandon Heller
        topo = buildTopo( TOPOS, self.options.topo )
311 f6f6d928 Bob Lantz
        switch = customClass( SWITCHES, self.options.switch )
312
        host = customClass( HOSTS, self.options.host )
313 125e6697 Bob Lantz
        controller = [ customClass( CONTROLLERS, c )
314
                       for c in self.options.controller ]
315 f6f6d928 Bob Lantz
        link = customClass( LINKS, self.options.link )
316 a6b47322 Brandon Heller
317 befa1310 Brandon Heller
        if self.validate:
318 c3a44400 Bob Lantz
            self.validate( self.options )
319 befa1310 Brandon Heller
320 82f483f5 Bob Lantz
        ipBase = self.options.ipbase
321 8a034f4f Brandon Heller
        xterms = self.options.xterms
322 376bcba4 Brandon Heller
        mac = self.options.mac
323
        arp = self.options.arp
324 197b083f Bob Lantz
        pin = self.options.pin
325 0a9358c9 Brandon Heller
        listenPort = None
326
        if not self.options.nolistenport:
327
            listenPort = self.options.listenport
328 c265deed Bob Lantz
329
        # Handle inNamespace, cluster options
330
        inNamespace = self.options.innamespace
331
        cluster = self.options.cluster
332
        if inNamespace and cluster:
333
            print "Please specify --innamespace OR --cluster"
334
            exit()
335
        Net = MininetWithControlNet if inNamespace else Mininet
336 43461905 Bob Lantz
        cli = ClusterCLI if cluster else CLI
337 c265deed Bob Lantz
        if cluster:
338
            warn( '*** WARNING: Experimental cluster mode!\n'
339 7a3159c9 Bob Lantz
                  '*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
340 c265deed Bob Lantz
            host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
341 acdcf9b6 Bob Lantz
            Net = partial( MininetCluster, servers=servers,
342 c265deed Bob Lantz
                           placement=PLACEMENT[ self.options.placement ] )
343
344 14ff3ad3 Bob Lantz
        mn = Net( topo=topo,
345
                  switch=switch, host=host, controller=controller,
346 ff568819 Bob Lantz
                  link=link,
347 14ff3ad3 Bob Lantz
                  ipBase=ipBase,
348
                  inNamespace=inNamespace,
349
                  xterms=xterms, autoSetMacs=mac,
350 197b083f Bob Lantz
                  autoStaticArp=arp, autoPinCpus=pin,
351
                  listenPort=listenPort )
352 87737a70 Bob Lantz
353 ab97dfa1 Brian O'Connor
        if self.options.ensure_value( 'nat', False ):
354 7a3159c9 Bob Lantz
            nat = mn.addNAT( *self.options.nat_args,
355
                             **self.options.nat_kwargs )
356 e0af1602 Brian O'Connor
            nat.configDefault()
357 ffeb16eb Brian O'Connor
358 d869d820 Bob Lantz
        if self.options.pre:
359 43461905 Bob Lantz
            cli( mn, script=self.options.pre )
360 a6b47322 Brandon Heller
361 eeb9cb3c Brandon Heller
        test = self.options.test
362 d869d820 Bob Lantz
        test = ALTSPELLING.get( test, test )
363 f95aebb4 Bob Lantz
364 d869d820 Bob Lantz
        mn.start()
365 f95aebb4 Bob Lantz
366 dfc08a86 Brandon Heller
        if test == 'none':
367 d869d820 Bob Lantz
            pass
368 dfc08a86 Brandon Heller
        elif test == 'all':
369 84ea8d7f Cody Burkard
            mn.waitConnected()
370 dfc08a86 Brandon Heller
            mn.start()
371
            mn.ping()
372
            mn.iperf()
373 d869d820 Bob Lantz
        elif test == 'cli':
374 c4fc6304 Bob Lantz
            cli( mn )
375 dfc08a86 Brandon Heller
        elif test != 'build':
376 84ea8d7f Cody Burkard
            mn.waitConnected()
377 d869d820 Bob Lantz
            getattr( mn, test )()
378
379
        if self.options.post:
380 c4fc6304 Bob Lantz
            cli( mn, script=self.options.post )
381 d869d820 Bob Lantz
382
        mn.stop()
383 a6b47322 Brandon Heller
384 8895862a Bob Lantz
        elapsed = float( time.time() - start )
385 4e69ae83 Brandon Heller
        info( 'completed in %0.3f seconds\n' % elapsed )
386 a6b47322 Brandon Heller
387
388
if __name__ == "__main__":
389 bda2317d Brian O'Connor
    try:
390
        MininetRunner()
391
    except KeyboardInterrupt:
392 bb0006b6 Brian O'Connor
        info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
393 bda2317d Brian O'Connor
        cleanup()
394
    except Exception:
395 19bc1df1 Brian O'Connor
        # Print exception
396 bda2317d Brian O'Connor
        type_, val_, trace_ = sys.exc_info()
397
        errorMsg = ( "-"*80 + "\n" +
398
                     "Caught exception. Cleaning up...\n\n" +
399
                     "%s: %s\n" % ( type_.__name__, val_ ) +
400
                     "-"*80 + "\n" )
401
        error( errorMsg )
402
        # Print stack trace to debug log
403
        import traceback
404
        stackTrace = traceback.format_exc()
405
        debug( stackTrace + "\n" )
406
        cleanup()