Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ 686a9993

History | View | Annotate | Download (12.7 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, DefaultController,
29
                           UserSwitch, OVSSwitch,
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 = 'ovsk'
58
SWITCHES = { 'user': UserSwitch,
59
             'ovs':  OVSSwitch,
60
             # Keep ovsk for compatibility with 2.0
61
             'ovsk': OVSSwitch,
62
             'ovsl': OVSLegacyKernelSwitch,
63
             'ivs': IVSSwitch,
64
             'lxbr': LinuxBridge }
65

    
66
HOSTDEF = 'proc'
67
HOSTS = { 'proc': Host,
68
          'rt': custom( CPULimitedHost, sched='rt' ),
69
          'cfs': custom( CPULimitedHost, sched='cfs' ) }
70

    
71
CONTROLLERDEF = 'default'
72
CONTROLLERS = { 'ref': Controller,
73
                'ovsc': OVSController,
74
                'nox': NOX,
75
                'remote': RemoteController,
76
                'default': DefaultController,
77
                'ryu': RYU,
78
                'none': lambda name: None }
79

    
80
LINKDEF = 'default'
81
LINKS = { 'default': Link,
82
          'tc': TCLink }
83

    
84

    
85
# optional tests to run
86
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
87
          'none' ]
88

    
89
ALTSPELLING = { 'pingall': 'pingAll',
90
                'pingpair': 'pingPair',
91
                'iperfudp': 'iperfUdp',
92
                'iperfUDP': 'iperfUdp' }
93

    
94

    
95
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
96
    """Convenience function to add choices dicts to OptionParser.
97
       opts: OptionParser instance
98
       choicesDict: dictionary of valid choices, must include default
99
       default: default choice key
100
       name: long option name
101
       help: string"""
102
    if default not in choicesDict:
103
        raise Exception( 'Invalid  default %s for choices dict: %s' %
104
                         ( default, name ) )
105
    if not helpStr:
106
        helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
107
                    '[,param=value...]' )
108
    opts.add_option( '--' + name,
109
                     type='string',
110
                     default = default,
111
                     help = helpStr )
112

    
113

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

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

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

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

    
132
    def setCustom( self, name, value ):
133
        "Set custom parameters for MininetRunner."
134
        if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
135
            # Update dictionaries
136
            param = name.upper()
137
            globals()[ param ].update( value )
138
        elif name == 'validate':
139
            # Add custom validate function
140
            self.validate = value
141
        else:
142
            # Add or modify global variable or class
143
            globals()[ name ] = value
144

    
145
    def parseCustomFile( self, fileName ):
146
        "Parse custom file and add params before parsing cmd-line options."
147
        customs = {}
148
        if os.path.isfile( fileName ):
149
            execfile( fileName, customs, customs )
150
            for name, val in customs.iteritems():
151
                self.setCustom( name, val )
152
        else:
153
            raise Exception( 'could not find custom file: %s' % fileName )
154

    
155
    def parseArgs( self ):
156
        """Parse command-line args and return options object.
157
           returns: opts parse options dict"""
158
        if '--custom' in sys.argv:
159
            index = sys.argv.index( '--custom' )
160
            if len( sys.argv ) > index + 1:
161
                filename = sys.argv[ index + 1 ]
162
                self.parseCustomFile( filename )
163
            else:
164
                raise Exception( 'Custom file name not found' )
165

    
166
        desc = ( "The %prog utility creates Mininet network from the\n"
167
                 "command line. It can create parametrized topologies,\n"
168
                 "invoke the Mininet CLI, and run tests." )
169

    
170
        usage = ( '%prog [options]\n'
171
                  '(type %prog -h for details)' )
172

    
173
        opts = OptionParser( description=desc, usage=usage )
174
        addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
175
        addDictOption( opts, HOSTS, HOSTDEF, 'host' )
176
        addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
177
        addDictOption( opts, LINKS, LINKDEF, 'link' )
178
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
179

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

    
226
        self.options, self.args = opts.parse_args()
227

    
228
        # We don't accept extra arguments after the options
229
        if self.args:
230
            opts.print_help()
231
            exit()
232

    
233
    def setup( self ):
234
        "Setup and validate environment."
235

    
236
        # set logging verbosity
237
        if LEVELS[self.options.verbosity] > LEVELS['output']:
238
            print ( '*** WARNING: selected verbosity level (%s) will hide CLI '
239
                    'output!\n'
240
                    'Please restart Mininet with -v [debug, info, output].'
241
                    % self.options.verbosity )
242
        lg.setLogLevel( self.options.verbosity )
243

    
244
    def begin( self ):
245
        "Create and run mininet."
246

    
247
        global CLI
248
        
249
        if self.options.clean:
250
            cleanup()
251
            exit()
252

    
253
        start = time.time()
254

    
255
        topo = buildTopo( TOPOS, self.options.topo )
256
        switch = customConstructor( SWITCHES, self.options.switch )
257
        host = customConstructor( HOSTS, self.options.host )
258
        controller = customConstructor( CONTROLLERS, self.options.controller )
259
        link = customConstructor( LINKS, self.options.link )
260

    
261
        if self.validate:
262
            self.validate( self.options )
263

    
264
        ipBase = self.options.ipbase
265
        xterms = self.options.xterms
266
        mac = self.options.mac
267
        arp = self.options.arp
268
        pin = self.options.pin
269
        listenPort = None
270
        if not self.options.nolistenport:
271
            listenPort = self.options.listenport
272

    
273
        # Handle inNamespace, cluster options
274
        inNamespace = self.options.innamespace
275
        cluster = self.options.cluster
276
        if inNamespace and cluster:
277
            print "Please specify --innamespace OR --cluster"
278
            exit()
279
        Net = MininetWithControlNet if inNamespace else Mininet
280
        if cluster:
281
            warn( '*** WARNING: Experimental cluster mode!\n'
282
              '*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
283
            host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
284
            CLI = ClusterCLI
285
            Net = partial( MininetCluster, servers=cluster.split( ',' ),
286
                           placement=PLACEMENT[ self.options.placement ] )
287

    
288
        mn = Net( topo=topo,
289
                  switch=switch, host=host, controller=controller,
290
                  link=link,
291
                  ipBase=ipBase,
292
                  inNamespace=inNamespace,
293
                  xterms=xterms, autoSetMacs=mac,
294
                  autoStaticArp=arp, autoPinCpus=pin,
295
                  listenPort=listenPort )
296

    
297
        if self.options.nat:
298
            nat = mn.addNAT()
299
            nat.configDefault()
300

    
301
        if self.options.pre:
302
            CLI( mn, script=self.options.pre )
303

    
304
        test = self.options.test
305
        test = ALTSPELLING.get( test, test )
306

    
307
        mn.start()
308

    
309
        if test == 'none':
310
            pass
311
        elif test == 'all':
312
            mn.waitConnected()
313
            mn.start()
314
            mn.ping()
315
            mn.iperf()
316
        elif test == 'cli':
317
            CLI( mn )
318
        elif test != 'build':
319
            mn.waitConnected()
320
            getattr( mn, test )()
321

    
322
        if self.options.post:
323
            CLI( mn, script=self.options.post )
324

    
325
        mn.stop()
326

    
327
        elapsed = float( time.time() - start )
328
        info( 'completed in %0.3f seconds\n' % elapsed )
329

    
330

    
331
if __name__ == "__main__":
332
    try:
333
        MininetRunner()
334
    except KeyboardInterrupt:
335
        info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
336
        cleanup()
337
    except Exception:
338
        # Print exception
339
        type_, val_, trace_ = sys.exc_info()
340
        errorMsg = ( "-"*80 + "\n" +
341
                     "Caught exception. Cleaning up...\n\n" +
342
                     "%s: %s\n" % ( type_.__name__, val_ ) +
343
                     "-"*80 + "\n" )
344
        error( errorMsg )
345
        # Print stack trace to debug log
346
        import traceback
347
        stackTrace = traceback.format_exc()
348
        debug( stackTrace + "\n" )
349
        cleanup()