Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ 19bc1df1

History | View | Annotate | Download (10.5 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, OVSKernelSwitch,
29
                           OVSLegacyKernelSwitch )
30
from mininet.link import Link, TCLink
31
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
32
from mininet.topolib import TreeTopo
33
from mininet.util import custom, customConstructor
34
from mininet.util import buildTopo
35

    
36

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

    
45
SWITCHDEF = 'ovsk'
46
SWITCHES = { 'user': UserSwitch,
47
             'ovsk': OVSKernelSwitch,
48
             'ovsl': OVSLegacyKernelSwitch }
49

    
50
HOSTDEF = 'proc'
51
HOSTS = { 'proc': Host,
52
          'rt': custom( CPULimitedHost, sched='rt' ),
53
          'cfs': custom( CPULimitedHost, sched='cfs' ) }
54

    
55
CONTROLLERDEF = 'ovsc'
56
CONTROLLERS = { 'ref': Controller,
57
                'ovsc': OVSController,
58
                'nox': NOX,
59
                'remote': RemoteController,
60
                'none': lambda name: None }
61

    
62
LINKDEF = 'default'
63
LINKS = { 'default': Link,
64
          'tc': TCLink }
65

    
66

    
67
# optional tests to run
68
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
69
          'none' ]
70

    
71
ALTSPELLING = { 'pingall': 'pingAll',
72
                'pingpair': 'pingPair',
73
                'iperfudp': 'iperfUdp',
74
                'iperfUDP': 'iperfUdp' }
75

    
76

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

    
95

    
96
def version( *_args ):
97
    "Print Mininet version and exit"
98
    print "%s" % VERSION
99
    exit()
100

    
101
class MininetRunner( object ):
102
    "Build, setup, and run Mininet."
103

    
104
    def __init__( self ):
105
        "Init."
106
        self.options = None
107
        self.args = None  # May be used someday for more CLI scripts
108
        self.validate = None
109

    
110
        self.parseArgs()
111
        self.setup()
112
        self.begin()
113

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

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

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

    
148
        desc = ( "The %prog utility creates Mininet network from the\n"
149
                 "command line. It can create parametrized topologies,\n"
150
                 "invoke the Mininet CLI, and run tests." )
151

    
152
        usage = ( '%prog [options]\n'
153
                  '(type %prog -h for details)' )
154

    
155
        opts = OptionParser( description=desc, usage=usage )
156
        addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
157
        addDictOption( opts, HOSTS, HOSTDEF, 'host' )
158
        addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
159
        addDictOption( opts, LINKS, LINKDEF, 'link' )
160
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
161

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

    
197
        self.options, self.args = opts.parse_args()
198

    
199
        # We don't accept extra arguments after the options
200
        if self.args:
201
            opts.print_help()
202
            exit()
203

    
204
    def setup( self ):
205
        "Setup and validate environment."
206

    
207
        # set logging verbosity
208
        if LEVELS[self.options.verbosity] > LEVELS['output']:
209
            print ( '*** WARNING: selected verbosity level (%s) will hide CLI '
210
                    'output!\n'
211
                    'Please restart Mininet with -v [debug, info, output].'
212
                    % self.options.verbosity )
213
        lg.setLogLevel( self.options.verbosity )
214

    
215
    def begin( self ):
216
        "Create and run mininet."
217

    
218
        if self.options.clean:
219
            cleanup()
220
            exit()
221

    
222
        start = time.time()
223

    
224
        topo = buildTopo( TOPOS, self.options.topo )
225
        switch = customConstructor( SWITCHES, self.options.switch )
226
        host = customConstructor( HOSTS, self.options.host )
227
        controller = customConstructor( CONTROLLERS, self.options.controller )
228
        link = customConstructor( LINKS, self.options.link )
229

    
230
        if self.validate:
231
            self.validate( self.options )
232

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

    
252
        if self.options.pre:
253
            CLI( mn, script=self.options.pre )
254

    
255
        test = self.options.test
256
        test = ALTSPELLING.get( test, test )
257

    
258
        mn.start()
259

    
260
        if test == 'none':
261
            pass
262
        elif test == 'all':
263
            mn.start()
264
            mn.ping()
265
            mn.iperf()
266
        elif test == 'cli':
267
            CLI( mn )
268
        elif test != 'build':
269
            getattr( mn, test )()
270

    
271
        if self.options.post:
272
            CLI( mn, script=self.options.post )
273

    
274
        mn.stop()
275

    
276
        elapsed = float( time.time() - start )
277
        info( 'completed in %0.3f seconds\n' % elapsed )
278

    
279

    
280
if __name__ == "__main__":
281
    try:
282
        MininetRunner()
283
    except KeyboardInterrupt:
284
        info( "Keyboard Interrupt. Shutting down and cleaning up...")
285
        cleanup()
286
    except Exception:
287
        # Print exception
288
        type_, val_, trace_ = sys.exc_info()
289
        errorMsg = ( "-"*80 + "\n" +
290
                     "Caught exception. Cleaning up...\n\n" +
291
                     "%s: %s\n" % ( type_.__name__, val_ ) +
292
                     "-"*80 + "\n" )
293
        error( errorMsg )
294
        # Print stack trace to debug log
295
        import traceback
296
        stackTrace = traceback.format_exc()
297
        debug( stackTrace + "\n" )
298
        cleanup()