pervio a la presentacion
[vsorcdistro/.git] / mininet / build / scripts-2.7 / mn
1 #!/usr/bin/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 import mininet.cli
25 from mininet.log import lg, LEVELS, info, debug, warn, error, output
26 from mininet.net import Mininet, MininetWithControlNet, VERSION
27 from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
28                            Ryu, NOX, RemoteController, findController,
29                            DefaultController, NullController,
30                            UserSwitch, OVSSwitch, OVSBridge,
31                            IVSSwitch )
32 from mininet.nodelib import LinuxBridge
33 from mininet.link import Link, TCLink, TCULink, OVSLink
34 from mininet.topo import ( SingleSwitchTopo, LinearTopo,
35                            SingleSwitchReversedTopo, MinimalTopo )
36 from mininet.topolib import TreeTopo, TorusTopo
37 from mininet.util import customClass, specialClass, splitArgs
38 from mininet.util import buildTopo
39
40 from functools import partial
41
42 # Experimental! cluster edition prototype
43 from mininet.examples.cluster import ( MininetCluster, RemoteHost,
44                                        RemoteOVSSwitch, RemoteLink,
45                                        SwitchBinPlacer, RandomPlacer,
46                                        ClusterCleanup )
47 from mininet.examples.clustercli import ClusterCLI
48
49 PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
50
51 # built in topologies, created only when run
52 TOPODEF = 'minimal'
53 TOPOS = { 'minimal': MinimalTopo,
54           'linear': LinearTopo,
55           'reversed': SingleSwitchReversedTopo,
56           'single': SingleSwitchTopo,
57           'tree': TreeTopo,
58           'torus': TorusTopo }
59
60 SWITCHDEF = 'default'
61 SWITCHES = { 'user': UserSwitch,
62              'ovs': OVSSwitch,
63              'ovsbr' : OVSBridge,
64              # Keep ovsk for compatibility with 2.0
65              'ovsk': OVSSwitch,
66              'ivs': IVSSwitch,
67              'lxbr': LinuxBridge,
68              'default': OVSSwitch }
69
70 HOSTDEF = 'proc'
71 HOSTS = { 'proc': Host,
72           'rt': specialClass( CPULimitedHost, defaults=dict( sched='rt' ) ),
73           'cfs': specialClass( CPULimitedHost, defaults=dict( sched='cfs' ) ) }
74
75 CONTROLLERDEF = 'default'
76 CONTROLLERS = { 'ref': Controller,
77                 'ovsc': OVSController,
78                 'nox': NOX,
79                 'remote': RemoteController,
80                 'ryu': Ryu,
81                 'default': DefaultController,  # Note: overridden below
82                 'none': NullController }
83
84 LINKDEF = 'default'
85 LINKS = { 'default': Link,  # Note: overridden below
86           'tc': TCLink,
87           'tcu': TCULink,
88           'ovs': OVSLink }
89
90 # TESTS dict can contain functions and/or Mininet() method names
91 # XXX: it would be nice if we could specify a default test, but
92 # this may be tricky
93 TESTS = { name: True
94           for name in ( 'pingall', 'pingpair', 'iperf', 'iperfudp' ) }
95
96 CLI = None  # Set below if needed
97
98 # Locally defined tests
99 def allTest( net ):
100     "Run ping and iperf tests"
101     net.waitConnected()
102     net.start()
103     net.ping()
104     net.iperf()
105
106 def nullTest( _net ):
107     "Null 'test' (does nothing)"
108     pass
109
110 TESTS.update( all=allTest, none=nullTest, build=nullTest )
111
112 # Map to alternate spellings of Mininet() methods
113 ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
114                 'iperfudp': 'iperfUdp' }
115
116 def runTests( mn, options ):
117     """Run tests
118        mn: Mininet object
119        option: list of test optinos """
120     # Split option into test name and parameters
121     for option in options:
122         # Multiple tests may be separated by '+' for now
123         for test in option.split( '+' ):
124             test, args, kwargs = splitArgs( test )
125             test = ALTSPELLING.get( test.lower(), test )
126             testfn = TESTS.get( test, test )
127             if callable( testfn ):
128                 testfn( mn, *args, **kwargs )
129             elif hasattr( mn, test ):
130                 mn.waitConnected()
131                 getattr( mn, test )( *args, **kwargs )
132             else:
133                 raise Exception( 'Test %s is unknown - please specify one of '
134                                  '%s ' % ( test, TESTS.keys() ) )
135
136
137 def addDictOption( opts, choicesDict, default, name, **kwargs ):
138     """Convenience function to add choices dicts to OptionParser.
139        opts: OptionParser instance
140        choicesDict: dictionary of valid choices, must include default
141        default: default choice key
142        name: long option name
143        kwargs: additional arguments to add_option"""
144     helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
145                 '[,param=value...]' )
146     helpList = [ '%s=%s' % ( k, v.__name__ )
147                  for k, v in choicesDict.items() ]
148     helpStr += ' ' + ( ' '.join( helpList ) )
149     params = dict( type='string', default=default, help=helpStr )
150     params.update( **kwargs )
151     opts.add_option( '--' + name, **params )
152
153 def version( *_args ):
154     "Print Mininet version and exit"
155     output( "%s\n" % VERSION )
156     exit()
157
158
159 class MininetRunner( object ):
160     "Build, setup, and run Mininet."
161
162     def __init__( self ):
163         "Init."
164         self.options = None
165         self.args = None  # May be used someday for more CLI scripts
166         self.validate = None
167
168         self.parseArgs()
169         self.setup()
170         self.begin()
171
172     def custom( self, _option, _opt_str, value, _parser ):
173         """Parse custom file and add params.
174            option: option e.g. --custom
175            opt_str: option string e.g. --custom
176            value: the value the follows the option
177            parser: option parser instance"""
178         files = []
179         if os.path.isfile( value ):
180             # Accept any single file (including those with commas)
181             files.append( value )
182         else:
183             # Accept a comma-separated list of filenames
184             files += value.split(',')
185
186         for fileName in files:
187             customs = {}
188             if os.path.isfile( fileName ):
189                 # pylint: disable=exec-used
190                 exec( compile( open( fileName ).read(), fileName, 'exec' ),
191                       customs, customs )
192                 for name, val in customs.items():
193                     self.setCustom( name, val )
194             else:
195                 raise Exception( 'could not find custom file: %s' % fileName )
196
197     def setCustom( self, name, value ):
198         "Set custom parameters for MininetRunner."
199         if name in ( 'topos', 'switches', 'hosts', 'controllers', 'links'
200                      'testnames', 'tests' ):
201             # Update dictionaries
202             param = name.upper()
203             globals()[ param ].update( value )
204         elif name == 'validate':
205             # Add custom validate function
206             self.validate = value
207         else:
208             # Add or modify global variable or class
209             globals()[ name ] = value
210
211     def setNat( self, _option, opt_str, value, parser ):
212         "Set NAT option(s)"
213         assert self  # satisfy pylint
214         parser.values.nat = True
215         # first arg, first char != '-'
216         if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-':
217             value = parser.rargs.pop( 0 )
218             _, args, kwargs = splitArgs( opt_str + ',' + value )
219             parser.values.nat_args = args
220             parser.values.nat_kwargs = kwargs
221         else:
222             parser.values.nat_args = []
223             parser.values.nat_kwargs = {}
224
225     def parseArgs( self ):
226         """Parse command-line args and return options object.
227            returns: opts parse options dict"""
228
229         desc = ( "The %prog utility creates Mininet network from the\n"
230                  "command line. It can create parametrized topologies,\n"
231                  "invoke the Mininet CLI, and run tests." )
232
233         usage = ( '%prog [options]\n'
234                   '(type %prog -h for details)' )
235
236         opts = OptionParser( description=desc, usage=usage )
237         addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
238         addDictOption( opts, HOSTS, HOSTDEF, 'host' )
239         addDictOption( opts, CONTROLLERS, [], 'controller', action='append' )
240         addDictOption( opts, LINKS, LINKDEF, 'link' )
241         addDictOption( opts, TOPOS, TOPODEF, 'topo' )
242
243         opts.add_option( '--clean', '-c', action='store_true',
244                          default=False, help='clean and exit' )
245         opts.add_option( '--custom', action='callback',
246                          callback=self.custom,
247                          type='string',
248                          help='read custom classes or params from .py file(s)'
249                          )
250         opts.add_option( '--test', default=[], action='append',
251                          dest='test', help='|'.join( TESTS.keys() ) )
252         opts.add_option( '--xterms', '-x', action='store_true',
253                          default=False, help='spawn xterms for each node' )
254         opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8',
255                          help='base IP address for hosts' )
256         opts.add_option( '--mac', action='store_true',
257                          default=False, help='automatically set host MACs' )
258         opts.add_option( '--arp', action='store_true',
259                          default=False, help='set all-pairs ARP entries' )
260         opts.add_option( '--verbosity', '-v', type='choice',
261                          choices=list( LEVELS.keys() ), default = 'info',
262                          help = '|'.join( LEVELS.keys() )  )
263         opts.add_option( '--innamespace', action='store_true',
264                          default=False, help='sw and ctrl in namespace?' )
265         opts.add_option( '--listenport', type='int', default=6654,
266                          help='base port for passive switch listening' )
267         opts.add_option( '--nolistenport', action='store_true',
268                          default=False, help="don't use passive listening " +
269                          "port")
270         opts.add_option( '--pre', type='string', default=None,
271                          help='CLI script to run before tests' )
272         opts.add_option( '--post', type='string', default=None,
273                          help='CLI script to run after tests' )
274         opts.add_option( '--pin', action='store_true',
275                          default=False, help="pin hosts to CPU cores "
276                          "(requires --host cfs or --host rt)" )
277         opts.add_option( '--nat', action='callback', callback=self.setNat,
278                          help="[option=val...] adds a NAT to the topology that"
279                          " connects Mininet hosts to the physical network."
280                          " Warning: This may route any traffic on the machine"
281                          " that uses Mininet's"
282                          " IP subnet into the Mininet network."
283                          " If you need to change"
284                          " Mininet's IP subnet, see the --ipbase option." )
285         opts.add_option( '--version', action='callback', callback=version,
286                          help='prints the version and exits' )
287         opts.add_option( '--cluster', type='string', default=None,
288                          metavar='server1,server2...',
289                          help=( 'run on multiple servers (experimental!)' ) )
290         opts.add_option( '--placement', type='choice',
291                          choices=list( PLACEMENT.keys() ), default='block',
292                          metavar='block|random',
293                          help=( 'node placement for --cluster '
294                                 '(experimental!) ' ) )
295
296         self.options, self.args = opts.parse_args()
297
298         # We don't accept extra arguments after the options
299         if self.args:
300             opts.print_help()
301             exit()
302
303     def setup( self ):
304         "Setup and validate environment."
305
306         # set logging verbosity
307         if LEVELS[self.options.verbosity] > LEVELS['output']:
308             warn( '*** WARNING: selected verbosity level (%s) will hide CLI '
309                     'output!\n'
310                     'Please restart Mininet with -v [debug, info, output].\n'
311                     % self.options.verbosity )
312         lg.setLogLevel( self.options.verbosity )
313
314     # Maybe we'll reorganize this someday...
315     # pylint: disable=too-many-branches,too-many-statements,global-statement
316
317     def begin( self ):
318         "Create and run mininet."
319
320         global CLI
321
322         opts = self.options
323
324         if opts.cluster:
325             servers = opts.cluster.split( ',' )
326             for server in servers:
327                 ClusterCleanup.add( server )
328
329         if opts.clean:
330             cleanup()
331             exit()
332
333         start = time.time()
334
335         if not opts.controller:
336             # Update default based on available controllers
337             CONTROLLERS[ 'default' ] = findController()
338             opts.controller = [ 'default' ]
339             if not CONTROLLERS[ 'default' ]:
340                 opts.controller = [ 'none' ]
341                 if opts.switch == 'default':
342                     info( '*** No default OpenFlow controller found '
343                           'for default switch!\n' )
344                     info( '*** Falling back to OVS Bridge\n' )
345                     opts.switch = 'ovsbr'
346                 elif opts.switch not in ( 'ovsbr', 'lxbr' ):
347                     raise Exception( "Could not find a default controller "
348                                      "for switch %s" %
349                                      opts.switch )
350
351         topo = buildTopo( TOPOS, opts.topo )
352         switch = customClass( SWITCHES, opts.switch )
353         host = customClass( HOSTS, opts.host )
354         controller = [ customClass( CONTROLLERS, c )
355                        for c in opts.controller ]
356
357         if opts.switch == 'user' and opts.link == 'default':
358             debug( '*** Using TCULink with UserSwitch\n' )
359             # Use link configured correctly for UserSwitch
360             opts.link = 'tcu'
361
362         link = customClass( LINKS, opts.link )
363
364         if self.validate:
365             self.validate( opts )
366
367         if opts.nolistenport:
368             opts.listenport = None
369
370         # Handle innamespace, cluster options
371         if opts.innamespace and opts.cluster:
372             error( "Please specify --innamespace OR --cluster\n" )
373             exit()
374         Net = MininetWithControlNet if opts.innamespace else Mininet
375         if opts.cluster:
376             warn( '*** WARNING: Experimental cluster mode!\n'
377                   '*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
378             host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
379             Net = partial( MininetCluster, servers=servers,
380                            placement=PLACEMENT[ opts.placement ] )
381             mininet.cli.CLI = ClusterCLI
382
383         mn = Net( topo=topo,
384                   switch=switch, host=host, controller=controller, link=link,
385                   ipBase=opts.ipbase, inNamespace=opts.innamespace,
386                   xterms=opts.xterms, autoSetMacs=opts.mac,
387                   autoStaticArp=opts.arp, autoPinCpus=opts.pin,
388                   listenPort=opts.listenport )
389
390         if opts.ensure_value( 'nat', False ):
391             with open( '/etc/resolv.conf' ) as f:
392                 if 'nameserver 127.' in f.read():
393                     warn( '*** Warning: loopback address in /etc/resolv.conf '
394                           'may break host DNS over NAT\n')
395             mn.addNAT( *opts.nat_args, **opts.nat_kwargs ).configDefault()
396
397         # --custom files can set CLI or change mininet.cli.CLI
398         CLI = mininet.cli.CLI if CLI is None else CLI
399
400         if opts.pre:
401             CLI( mn, script=opts.pre )
402
403         mn.start()
404
405         if opts.test:
406             runTests( mn, opts.test )
407         else:
408             CLI( mn )
409
410         if opts.post:
411             CLI( mn, script=opts.post )
412
413         mn.stop()
414
415         elapsed = float( time.time() - start )
416         info( 'completed in %0.3f seconds\n' % elapsed )
417
418
419 if __name__ == "__main__":
420     try:
421         MininetRunner()
422     except KeyboardInterrupt:
423         info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
424         cleanup()
425     except Exception:
426         # Print exception
427         type_, val_, trace_ = sys.exc_info()
428         errorMsg = ( "-"*80 + "\n" +
429                      "Caught exception. Cleaning up...\n\n" +
430                      "%s: %s\n" % ( type_.__name__, val_ ) +
431                      "-"*80 + "\n" )
432         error( errorMsg )
433         # Print stack trace to debug log
434         import traceback
435         stackTrace = traceback.format_exc()
436         debug( stackTrace + "\n" )
437         cleanup()