Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ 125e6697

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