Update and rename MantenerFIFO to MantenerFIFO.md
[vsorcdistro/.git] / mininet / bin / mn
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 Example to pull custom params (topo, switch, etc.) from a file:
10   sudo mn --custom ~/mininet/custom/custom_example.py
11 """
12
13 from optparse import OptionParser
14 import os
15 import sys
16 import time
17
18 # Fix setuptools' evil madness, and open up (more?) security holes
19 if 'PYTHONPATH' in os.environ:
20     sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
21
22 from mininet.clean import cleanup
23 import mininet.cli
24 from mininet.log import lg, LEVELS, info, debug, warn, error, output
25 from mininet.net import Mininet, MininetWithControlNet, VERSION
26 from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
27                            Ryu, NOX, RemoteController, findController,
28                            DefaultController, NullController,
29                            UserSwitch, OVSSwitch, OVSBridge,
30                            IVSSwitch )
31 from mininet.nodelib import LinuxBridge
32 from mininet.link import Link, TCLink, TCULink, OVSLink
33 from mininet.topo import ( SingleSwitchTopo, LinearTopo,
34                            SingleSwitchReversedTopo, MinimalTopo )
35 from mininet.topolib import TreeTopo, TorusTopo
36 from mininet.util import customClass, specialClass, splitArgs
37 from mininet.util import buildTopo
38
39 from functools import partial
40
41 # Experimental! cluster edition prototype
42 from mininet.examples.cluster import ( MininetCluster, RemoteHost,
43                                        RemoteOVSSwitch, RemoteLink,
44                                        SwitchBinPlacer, RandomPlacer,
45                                        ClusterCleanup, RemoteGRELink )
46 from mininet.examples.clustercli import ClusterCLI
47
48 PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
49 CLUSTERLINKS = {'ssh': RemoteLink, 'gre': RemoteGRELink}
50 ClusterLink = CLUSTERLINKS['gre'] #GRE Tunnel permite un mayor ancho de banda entre los links
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( '--cluster', type='string', default=None,
246                          metavar='server1,server2...',
247                          help=( 'run on multiple servers (experimental!)' ) )
248         opts.add_option( '--custom', action='callback',
249                          callback=self.custom,
250                          type='string',
251                          help='read custom classes or params from .py file(s)'
252                          )
253         opts.add_option( '--test', default=[], action='append',
254                          dest='test', help='|'.join( TESTS.keys() ) )
255         opts.add_option( '--xterms', '-x', action='store_true',
256                          default=False, help='spawn xterms for each node' )
257         opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8',
258                          help='base IP address for hosts' )
259         opts.add_option( '--mac', action='store_true',
260                          default=False, help='automatically set host MACs' )
261         opts.add_option( '--arp', action='store_true',
262                          default=False, help='set all-pairs ARP entries' )
263         opts.add_option( '--verbosity', '-v', type='choice',
264                          choices=list( LEVELS.keys() ), default = 'info',
265                          help = '|'.join( LEVELS.keys() )  )
266         opts.add_option( '--innamespace', action='store_true',
267                          default=False, help='sw and ctrl in namespace?' )
268         opts.add_option( '--listenport', type='int', default=6654,
269                          help='base port for passive switch listening' )
270         opts.add_option( '--nolistenport', action='store_true',
271                          default=False, help="don't use passive listening " +
272                          "port")
273         opts.add_option( '--pre', type='string', default=None,
274                          help='CLI script to run before tests' )
275         opts.add_option( '--post', type='string', default=None,
276                          help='CLI script to run after tests' )
277         opts.add_option( '--pin', action='store_true',
278                          default=False, help="pin hosts to CPU cores "
279                          "(requires --host cfs or --host rt)" )
280         opts.add_option( '--nat', action='callback', callback=self.setNat,
281                          help="[option=val...] adds a NAT to the topology that"
282                          " connects Mininet hosts to the physical network."
283                          " Warning: This may route any traffic on the machine"
284                          " that uses Mininet's"
285                          " IP subnet into the Mininet network."
286                          " If you need to change"
287                          " Mininet's IP subnet, see the --ipbase option." )
288         opts.add_option( '--version', action='callback', callback=version,
289                          help='prints the version and exits' )
290         # opts.add_option( '--cluster', type='string', default=None,
291         #                  metavar='server1,server2...',
292         #                  help=( 'run on multiple servers (experimental!)' ) )
293         opts.add_option( '--placement', type='choice',
294                          choices=list( PLACEMENT.keys() ), default='block',
295                          metavar='block|random',
296                          help=( 'node placement for --cluster '
297                                 '(experimental!) ' ) )
298
299         self.options, self.args = opts.parse_args()
300
301         # We don't accept extra arguments after the options
302         if self.args:
303             opts.print_help()
304             exit()
305
306     def setup( self ):
307         "Setup and validate environment."
308
309         # set logging verbosity
310         if LEVELS[self.options.verbosity] > LEVELS['output']:
311             warn( '*** WARNING: selected verbosity level (%s) will hide CLI '
312                     'output!\n'
313                     'Please restart Mininet with -v [debug, info, output].\n'
314                     % self.options.verbosity )
315         lg.setLogLevel( self.options.verbosity )
316
317     # Maybe we'll reorganize this someday...
318     # pylint: disable=too-many-branches,too-many-statements,global-statement
319
320     def begin( self ):
321         "Create and run mininet."
322
323         global CLI
324
325         opts = self.options
326
327         if opts.cluster:
328             servers = opts.cluster.split( ',' )
329             for server in servers:
330                 ClusterCleanup.add( server )
331
332         if opts.clean:
333             cleanup()
334             exit()
335
336         start = time.time()
337
338         if not opts.controller:
339             # Update default based on available controllers
340             CONTROLLERS[ 'default' ] = findController()
341             opts.controller = [ 'default' ]
342             if not CONTROLLERS[ 'default' ]:
343                 opts.controller = [ 'none' ]
344                 if opts.switch == 'default':
345                     info( '*** No default OpenFlow controller found '
346                           'for default switch!\n' )
347                     info( '*** Falling back to OVS Bridge\n' )
348                     opts.switch = 'ovsbr'
349                 elif opts.switch not in ( 'ovsbr', 'lxbr' ):
350                     raise Exception( "Could not find a default controller "
351                                      "for switch %s" %
352                                      opts.switch )
353
354         topo = buildTopo( TOPOS, opts.topo )
355         switch = customClass( SWITCHES, opts.switch )
356         host = customClass( HOSTS, opts.host )
357         controller = [ customClass( CONTROLLERS, c )
358                        for c in opts.controller ]
359
360         if opts.switch == 'user' and opts.link == 'default':
361             debug( '*** Using TCULink with UserSwitch\n' )
362             # Use link configured correctly for UserSwitch
363             opts.link = 'tcu'
364
365         link = customClass( LINKS, opts.link )
366
367         if self.validate:
368             self.validate( opts )
369
370         if opts.nolistenport:
371             opts.listenport = None
372
373         # Handle innamespace, cluster options
374         if opts.innamespace and opts.cluster:
375             error( "Please specify --innamespace OR --cluster\n" )
376             exit()
377         Net = MininetWithControlNet if opts.innamespace else Mininet
378         if opts.cluster:
379             warn( '*** WARNING: Modo Cluster ON!\n'
380                   '*** Using RemoteHost, RemoteOVSSwitch, RemoteGRELink\n' )
381             host, switch, link = RemoteHost, RemoteOVSSwitch, ClusterLink
382             Net = partial( MininetCluster, servers=servers,
383                            placement=PLACEMENT[ opts.placement ] )
384             mininet.cli.CLI = ClusterCLI
385
386         mn = Net( topo=topo,
387                   switch=switch, host=host, controller=controller, link=link,
388                   ipBase=opts.ipbase, inNamespace=opts.innamespace,
389                   xterms=opts.xterms, autoSetMacs=opts.mac,
390                   autoStaticArp=opts.arp, autoPinCpus=opts.pin,
391                   listenPort=opts.listenport )
392
393         if opts.ensure_value( 'nat', False ):
394             with open( '/etc/resolv.conf' ) as f:
395                 if 'nameserver 127.' in f.read():
396                     warn( '*** Warning: loopback address in /etc/resolv.conf '
397                           'may break host DNS over NAT\n')
398             mn.addNAT( *opts.nat_args, **opts.nat_kwargs ).configDefault()
399
400         # --custom files can set CLI or change mininet.cli.CLI
401         CLI = mininet.cli.CLI if CLI is None else CLI
402
403         if opts.pre:
404             CLI( mn, script=opts.pre )
405
406         mn.start()
407
408         if opts.test:
409             runTests( mn, opts.test )
410         else:
411             CLI( mn )
412
413         if opts.post:
414             CLI( mn, script=opts.post )
415
416         mn.stop()
417
418         elapsed = float( time.time() - start )
419         info( 'completed in %0.3f seconds\n' % elapsed )
420
421
422 if __name__ == "__main__":
423     try:
424         MininetRunner()
425     except KeyboardInterrupt:
426         info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
427         cleanup()
428     except Exception:
429         # Print exception
430         type_, val_, trace_ = sys.exc_info()
431         errorMsg = ( "-"*80 + "\n" +
432                      "Caught exception. Cleaning up...\n\n" +
433                      "%s: %s\n" % ( type_.__name__, val_ ) +
434                      "-"*80 + "\n" )
435         error( errorMsg )
436         # Print stack trace to debug log
437         import traceback
438         stackTrace = traceback.format_exc()
439         debug( stackTrace + "\n" )
440         cleanup()