Statistics
| Branch: | Tag: | Revision:

mininet / bin / mn @ 216a4b7c

History | View | Annotate | Download (10.2 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.path
16
import sys
17
import time
18

    
19
from mininet.clean import cleanup
20
from mininet.cli import CLI
21
from mininet.log import lg, LEVELS, info
22
from mininet.net import Mininet, init
23
from mininet.node import Host, CPULimitedHost, Controller, NOX
24
from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch
25
from mininet.link import Intf, TCIntf
26
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
27
from mininet.topolib import TreeTopo
28
from mininet.util import makeNumeric, custom
29

    
30
def customNode( constructors, argStr ):
31
    "Return custom Node constructor based on argStr"
32
    cname, noargs, kwargs = splitArgs( argStr )
33
    constructor = constructors.get( cname, None )
34
    if noargs:
35
        raise Exception( "please specify keyword  arguments for " + cname )
36
    if not constructor:
37
        raise Exception( "error: %s is unknown - please specify one of %s" %
38
               ( cname, constructors.keys() ) )
39
    def custom( *args, **params ):
40
        params.update( kwargs )
41
        print 'CONSTRUCTOR', constructor, 'ARGS', args, 'PARAMS', params
42
        return constructor( *args, **params )
43
    return custom
44

    
45
# built in topologies, created only when run
46
TOPODEF = 'minimal'
47
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
48
         'linear': LinearTopo,
49
         'reversed': SingleSwitchReversedTopo,
50
         'single': SingleSwitchTopo,
51
         'tree': TreeTopo }
52

    
53
SWITCHDEF = 'ovsk'
54
SWITCHES = { 'user': UserSwitch,
55
            'ovsk': OVSKernelSwitch }
56

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

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

    
68
INTFDEF = 'default'
69
INTFS = { 'default': Intf,
70
          'tc': TCIntf }
71

    
72

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

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

    
80

    
81
def splitArgs( argstr ):
82
    """Split argument string into usable python arguments
83
       argstr: argument string with format fn,arg2,kw1=arg3...
84
       returns: fn, args, kwargs"""
85
    split = argstr.split( ',' )
86
    fn = split[ 0 ]
87
    params = split[ 1: ]
88
    # Convert int and float args; removes the need for function
89
    # to be flexible with input arg formats.
90
    args = [ s for s in params if '=' not in s ]
91
    args = map( makeNumeric, args )
92
    kwargs = {}
93
    for s in [ p for p in params if '=' in p ]:
94
        key, val = s.split( '=' )
95
        kwargs[ key ] = makeNumeric( val )
96
    return fn, args, kwargs
97

    
98

    
99
def buildTopo( topoStr ):
100
    "Create topology from string with format (object, arg1, arg2,...)."
101
    topo, args, kwargs = splitArgs( topoStr )
102
    if topo not in TOPOS:
103
        raise Exception( 'Invalid topo name %s' % topo )
104
    return TOPOS[ topo ]( *args, **kwargs )
105

    
106

    
107
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
108
    """Convenience function to add choices dicts to OptionParser.
109
       opts: OptionParser instance
110
       choicesDict: dictionary of valid choices, must include default
111
       default: default choice key
112
       name: long option name
113
       help: string"""
114
    if default not in choicesDict:
115
        raise Exception( 'Invalid  default %s for choices dict: %s' %
116
                        ( default, name ) )
117
    if not helpStr:
118
        helpStr = '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]'
119
    opts.add_option( '--' + name,
120
                    type='string',
121
                    default = default,
122
                    help = helpStr )
123

    
124

    
125
class MininetRunner( object ):
126
    "Build, setup, and run Mininet."
127

    
128
    def __init__( self ):
129
        "Init."
130
        self.options = None
131
        self.args = None  # May be used someday for more CLI scripts
132
        self.validate = None
133

    
134
        self.parseArgs()
135
        self.setup()
136
        self.begin()
137

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

    
151
    def parseCustomFile( self, fileName ):
152
        "Parse custom file and add params before parsing cmd-line options."
153
        custom = {}
154
        if os.path.isfile( fileName ):
155
            execfile( fileName, custom, custom )
156
            for name in custom:
157
                self.setCustom( name, custom[ name ] )
158
        else:
159
            raise Exception( 'could not find custom file: %s' % fileName )
160

    
161
    def parseArgs( self ):
162
        """Parse command-line args and return options object.
163
           returns: opts parse options dict"""
164
        if '--custom' in sys.argv:
165
            index = sys.argv.index( '--custom' )
166
            if len( sys.argv ) > index + 1:
167
                custom = sys.argv[ index + 1 ]
168
                self.parseCustomFile( custom )
169
            else:
170
                raise Exception( 'Custom file name not found' )
171

    
172
        opts = OptionParser()
173
        addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
174
        addDictOption( opts, HOSTS, HOSTDEF, 'host' )
175
        addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
176
        addDictOption( opts, INTFS, INTFDEF, 'intf' )
177
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
178

    
179
        opts.add_option( '--clean', '-c', action='store_true',
180
                        default=False, help='clean and exit' )
181
        opts.add_option( '--custom', type='string', default=None,
182
                        help='read custom topo and node params from .py file' )
183
        opts.add_option( '--test', type='choice', choices=TESTS,
184
                        default=TESTS[ 0 ],
185
                        help='|'.join( TESTS ) )
186
        opts.add_option( '--xterms', '-x', action='store_true',
187
                        default=False, help='spawn xterms for each node' )
188
        opts.add_option( '--mac', action='store_true',
189
                        default=False, help='automatically set host MACs' )
190
        opts.add_option( '--arp', action='store_true',
191
                        default=False, help='set all-pairs ARP entries' )
192
        opts.add_option( '--verbosity', '-v', type='choice',
193
                        choices=LEVELS.keys(), default = 'info',
194
                        help = '|'.join( LEVELS.keys() )  )
195
        opts.add_option( '--ip', type='string', default='127.0.0.1',
196
                        help='ip address as a dotted decimal string for a'
197
                        'remote controller' )
198
        opts.add_option( '--innamespace', action='store_true',
199
                        default=False, help='sw and ctrl in namespace?' )
200
        opts.add_option( '--listenport', type='int', default=6634,
201
                         help='base port for passive switch listening' )
202
        opts.add_option( '--nolistenport', action='store_true',
203
                        default=False, help="don't use passive listening port")
204
        opts.add_option( '--pre', type='string', default=None,
205
                        help='CLI script to run before tests' )
206
        opts.add_option( '--post', type='string', default=None,
207
                        help='CLI script to run after tests' )
208
        opts.add_option( '--prefixlen', type='int', default=8,
209
                        help='prefix length (e.g. /8) for automatic '
210
                        'network configuration' )
211

    
212
        self.options, self.args = opts.parse_args()
213

    
214
    def setup( self ):
215
        "Setup and validate environment."
216

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

    
225
        # validate environment setup
226
        init()
227

    
228
    def begin( self ):
229
        "Create and run mininet."
230

    
231
        if self.options.clean:
232
            cleanup()
233
            exit()
234

    
235
        start = time.time()
236

    
237
        topo = buildTopo( self.options.topo )
238
        switch = customNode( SWITCHES,  self.options.switch )
239
        host = customNode( HOSTS, self.options.host )
240
        controller = customNode( CONTROLLERS, self.options.controller )
241
        intf = customNode( INTFS, self.options.intf )
242

    
243
        if self.validate:
244
            self.validate( self.options )
245

    
246
        inNamespace = self.options.innamespace
247
        xterms = self.options.xterms
248
        mac = self.options.mac
249
        arp = self.options.arp
250
        listenPort = None
251
        if not self.options.nolistenport:
252
            listenPort = self.options.listenport
253
        mn = Mininet( topo=topo, 
254
                      switch=switch, host=host, controller=controller, 
255
                      intf=intf,
256
                      inNamespace=inNamespace,
257
                      xterms=xterms, autoSetMacs=mac,
258
                      autoStaticArp=arp, 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.start()
272
            mn.ping()
273
            mn.iperf()
274
        elif test == 'cli':
275
            CLI( mn )
276
        elif test != 'build':
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
    MininetRunner()