Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ f11dbe81

History | View | Annotate | Download (10.9 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, 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

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

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

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

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

    
70
LINKDEF = 'default'
71
LINKS = { 'default': Link,
72
          'tc': TCLink }
73

    
74

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

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

    
84

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

    
103

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

    
109
class MininetRunner( object ):
110
    "Build, setup, and run Mininet."
111

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

    
118
        self.parseArgs()
119
        self.setup()
120
        self.begin()
121

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

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

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

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

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

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

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

    
205
        self.options, self.args = opts.parse_args()
206

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

    
212
    def setup( self ):
213
        "Setup and validate environment."
214

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

    
223
    def begin( self ):
224
        "Create and run mininet."
225

    
226
        if self.options.clean:
227
            cleanup()
228
            exit()
229

    
230
        start = time.time()
231

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

    
238
        if self.validate:
239
            self.validate( self.options )
240

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

    
260
        if self.options.pre:
261
            CLI( mn, script=self.options.pre )
262

    
263
        test = self.options.test
264
        test = ALTSPELLING.get( test, test )
265

    
266
        mn.start()
267

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

    
281
        if self.options.post:
282
            CLI( mn, script=self.options.post )
283

    
284
        mn.stop()
285

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

    
289

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