Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ b5962e8e

History | View | Annotate | Download (10.8 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, error
26
from mininet.net import Mininet, MininetWithControlNet, VERSION
27
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
28
                           NOX, RemoteController, UserSwitch, OVSSwitch,
29
                           OVSLegacyKernelSwitch, IVSSwitch )
30
from mininet.nodelib import LinuxBridge
31
from mininet.link import Link, TCLink
32
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
33
from mininet.topolib import TreeTopo, TorusTopo
34
from mininet.util import custom, customConstructor
35
from mininet.util import buildTopo
36

    
37

    
38
# built in topologies, created only when run
39
TOPODEF = 'minimal'
40
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
41
          'linear': LinearTopo,
42
          'reversed': SingleSwitchReversedTopo,
43
          'single': SingleSwitchTopo,
44
          'tree': TreeTopo,
45
          'torus': TorusTopo }
46

    
47
SWITCHDEF = 'ovsk'
48
SWITCHES = { 'user': UserSwitch,
49
             'ovs':  OVSSwitch,
50
             # Keep ovsk for compatibility with 2.0
51
             'ovsk': OVSSwitch,
52
             'ovsl': OVSLegacyKernelSwitch,
53
             'ivs': IVSSwitch,
54
             'lxbr': LinuxBridge }
55

    
56
HOSTDEF = 'proc'
57
HOSTS = { 'proc': Host,
58
          'rt': custom( CPULimitedHost, sched='rt' ),
59
          'cfs': custom( CPULimitedHost, sched='cfs' ) }
60

    
61
CONTROLLERDEF = 'ovsc'
62
CONTROLLERS = { 'ref': Controller,
63
                'ovsc': OVSController,
64
                'nox': NOX,
65
                'remote': RemoteController,
66
                'none': lambda name: None }
67

    
68
LINKDEF = 'default'
69
LINKS = { 'default': Link,
70
          'tc': TCLink }
71

    
72

    
73
# optional tests to run
74
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
75
          'none' ]
76

    
77
ALTSPELLING = { 'pingall': 'pingAll',
78
                'pingpair': 'pingPair',
79
                'iperfudp': 'iperfUdp',
80
                'iperfUDP': 'iperfUdp' }
81

    
82

    
83
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
84
    """Convenience function to add choices dicts to OptionParser.
85
       opts: OptionParser instance
86
       choicesDict: dictionary of valid choices, must include default
87
       default: default choice key
88
       name: long option name
89
       help: string"""
90
    if default not in choicesDict:
91
        raise Exception( 'Invalid  default %s for choices dict: %s' %
92
                         ( default, name ) )
93
    if not helpStr:
94
        helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
95
                    '[,param=value...]' )
96
    opts.add_option( '--' + name,
97
                     type='string',
98
                     default = default,
99
                     help = helpStr )
100

    
101

    
102
def version( *_args ):
103
    "Print Mininet version and exit"
104
    print "%s" % VERSION
105
    exit()
106

    
107
class MininetRunner( object ):
108
    "Build, setup, and run Mininet."
109

    
110
    def __init__( self ):
111
        "Init."
112
        self.options = None
113
        self.args = None  # May be used someday for more CLI scripts
114
        self.validate = None
115

    
116
        self.parseArgs()
117
        self.setup()
118
        self.begin()
119

    
120
    def setCustom( self, name, value ):
121
        "Set custom parameters for MininetRunner."
122
        if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
123
            # Update dictionaries
124
            param = name.upper()
125
            globals()[ param ].update( value )
126
        elif name == 'validate':
127
            # Add custom validate function
128
            self.validate = value
129
        else:
130
            # Add or modify global variable or class
131
            globals()[ name ] = value
132

    
133
    def parseCustomFile( self, fileName ):
134
        "Parse custom file and add params before parsing cmd-line options."
135
        customs = {}
136
        if os.path.isfile( fileName ):
137
            execfile( fileName, customs, customs )
138
            for name, val in customs.iteritems():
139
                self.setCustom( name, val )
140
        else:
141
            raise Exception( 'could not find custom file: %s' % fileName )
142

    
143
    def parseArgs( self ):
144
        """Parse command-line args and return options object.
145
           returns: opts parse options dict"""
146
        if '--custom' in sys.argv:
147
            index = sys.argv.index( '--custom' )
148
            if len( sys.argv ) > index + 1:
149
                filename = sys.argv[ index + 1 ]
150
                self.parseCustomFile( filename )
151
            else:
152
                raise Exception( 'Custom file name not found' )
153

    
154
        desc = ( "The %prog utility creates Mininet network from the\n"
155
                 "command line. It can create parametrized topologies,\n"
156
                 "invoke the Mininet CLI, and run tests." )
157

    
158
        usage = ( '%prog [options]\n'
159
                  '(type %prog -h for details)' )
160

    
161
        opts = OptionParser( description=desc, usage=usage )
162
        addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
163
        addDictOption( opts, HOSTS, HOSTDEF, 'host' )
164
        addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
165
        addDictOption( opts, LINKS, LINKDEF, 'link' )
166
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
167

    
168
        opts.add_option( '--clean', '-c', action='store_true',
169
                         default=False, help='clean and exit' )
170
        opts.add_option( '--custom', type='string', default=None,
171
                         help='read custom topo and node params from .py' +
172
                         'file' )
173
        opts.add_option( '--test', type='choice', choices=TESTS,
174
                         default=TESTS[ 0 ],
175
                         help='|'.join( TESTS ) )
176
        opts.add_option( '--xterms', '-x', action='store_true',
177
                         default=False, help='spawn xterms for each node' )
178
        opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8',
179
                         help='base IP address for hosts' )
180
        opts.add_option( '--mac', action='store_true',
181
                         default=False, help='automatically set host MACs' )
182
        opts.add_option( '--arp', action='store_true',
183
                         default=False, help='set all-pairs ARP entries' )
184
        opts.add_option( '--verbosity', '-v', type='choice',
185
                         choices=LEVELS.keys(), default = 'info',
186
                         help = '|'.join( LEVELS.keys() )  )
187
        opts.add_option( '--innamespace', action='store_true',
188
                         default=False, help='sw and ctrl in namespace?' )
189
        opts.add_option( '--listenport', type='int', default=6634,
190
                         help='base port for passive switch listening' )
191
        opts.add_option( '--nolistenport', action='store_true',
192
                         default=False, help="don't use passive listening " +
193
                         "port")
194
        opts.add_option( '--pre', type='string', default=None,
195
                         help='CLI script to run before tests' )
196
        opts.add_option( '--post', type='string', default=None,
197
                         help='CLI script to run after tests' )
198
        opts.add_option( '--pin', action='store_true',
199
                         default=False, help="pin hosts to CPU cores "
200
                         "(requires --host cfs or --host rt)" )
201
        opts.add_option( '--version', action='callback', callback=version )
202

    
203
        self.options, self.args = opts.parse_args()
204

    
205
        # We don't accept extra arguments after the options
206
        if self.args:
207
            opts.print_help()
208
            exit()
209

    
210
    def setup( self ):
211
        "Setup and validate environment."
212

    
213
        # set logging verbosity
214
        if LEVELS[self.options.verbosity] > LEVELS['output']:
215
            print ( '*** WARNING: selected verbosity level (%s) will hide CLI '
216
                    'output!\n'
217
                    'Please restart Mininet with -v [debug, info, output].'
218
                    % self.options.verbosity )
219
        lg.setLogLevel( self.options.verbosity )
220

    
221
    def begin( self ):
222
        "Create and run mininet."
223

    
224
        if self.options.clean:
225
            cleanup()
226
            exit()
227

    
228
        start = time.time()
229

    
230
        topo = buildTopo( TOPOS, self.options.topo )
231
        switch = customConstructor( SWITCHES, self.options.switch )
232
        host = customConstructor( HOSTS, self.options.host )
233
        controller = customConstructor( CONTROLLERS, self.options.controller )
234
        link = customConstructor( LINKS, self.options.link )
235

    
236
        if self.validate:
237
            self.validate( self.options )
238

    
239
        inNamespace = self.options.innamespace
240
        Net = MininetWithControlNet if inNamespace else Mininet
241
        ipBase = self.options.ipbase
242
        xterms = self.options.xterms
243
        mac = self.options.mac
244
        arp = self.options.arp
245
        pin = self.options.pin
246
        listenPort = None
247
        if not self.options.nolistenport:
248
            listenPort = self.options.listenport
249
        mn = Net( topo=topo,
250
                  switch=switch, host=host, controller=controller,
251
                  link=link,
252
                  ipBase=ipBase,
253
                  inNamespace=inNamespace,
254
                  xterms=xterms, autoSetMacs=mac,
255
                  autoStaticArp=arp, autoPinCpus=pin,
256
                  listenPort=listenPort )
257

    
258
        if self.options.pre:
259
            CLI( mn, script=self.options.pre )
260

    
261
        test = self.options.test
262
        test = ALTSPELLING.get( test, test )
263

    
264
        mn.start()
265

    
266
        if test == 'none':
267
            pass
268
        elif test == 'all':
269
            mn.waitConnected()
270
            mn.start()
271
            mn.ping()
272
            mn.iperf()
273
        elif test == 'cli':
274
            CLI( mn )
275
        elif test != 'build':
276
            mn.waitConnected()
277
            getattr( mn, test )()
278

    
279
        if self.options.post:
280
            CLI( mn, script=self.options.post )
281

    
282
        mn.stop()
283

    
284
        elapsed = float( time.time() - start )
285
        info( 'completed in %0.3f seconds\n' % elapsed )
286

    
287

    
288
if __name__ == "__main__":
289
    try:
290
        MininetRunner()
291
    except KeyboardInterrupt:
292
        info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
293
        cleanup()
294
    except Exception:
295
        # Print exception
296
        type_, val_, trace_ = sys.exc_info()
297
        errorMsg = ( "-"*80 + "\n" +
298
                     "Caught exception. Cleaning up...\n\n" +
299
                     "%s: %s\n" % ( type_.__name__, val_ ) +
300
                     "-"*80 + "\n" )
301
        error( errorMsg )
302
        # Print stack trace to debug log
303
        import traceback
304
        stackTrace = traceback.format_exc()
305
        debug( stackTrace + "\n" )
306
        cleanup()