5 author: Brandon Heller (brandonh@stanford.edu)
9 Example to pull custom params (topo, switch, etc.) from a file:
10 sudo mn --custom ~/mininet/custom/custom_example.py
13 from optparse import OptionParser
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
22 from mininet.clean import cleanup
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,
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
39 from functools import partial
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
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
53 TOPOS = { 'minimal': MinimalTopo,
55 'reversed': SingleSwitchReversedTopo,
56 'single': SingleSwitchTopo,
61 SWITCHES = { 'user': UserSwitch,
64 # Keep ovsk for compatibility with 2.0
68 'default': OVSSwitch }
71 HOSTS = { 'proc': Host,
72 'rt': specialClass( CPULimitedHost, defaults=dict( sched='rt' ) ),
73 'cfs': specialClass( CPULimitedHost, defaults=dict( sched='cfs' ) ) }
75 CONTROLLERDEF = 'default'
76 CONTROLLERS = { 'ref': Controller,
77 'ovsc': OVSController,
79 'remote': RemoteController,
81 'default': DefaultController, # Note: overridden below
82 'none': NullController }
85 LINKS = { 'default': Link, # Note: overridden below
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
94 for name in ( 'pingall', 'pingpair', 'iperf', 'iperfudp' ) }
96 CLI = None # Set below if needed
98 # Locally defined tests
100 "Run ping and iperf tests"
106 def nullTest( _net ):
107 "Null 'test' (does nothing)"
110 TESTS.update( all=allTest, none=nullTest, build=nullTest )
112 # Map to alternate spellings of Mininet() methods
113 ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
114 'iperfudp': 'iperfUdp' }
116 def runTests( mn, options ):
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 ):
131 getattr( mn, test )( *args, **kwargs )
133 raise Exception( 'Test %s is unknown - please specify one of '
134 '%s ' % ( test, TESTS.keys() ) )
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 )
153 def version( *_args ):
154 "Print Mininet version and exit"
155 output( "%s\n" % VERSION )
159 class MininetRunner( object ):
160 "Build, setup, and run Mininet."
162 def __init__( self ):
165 self.args = None # May be used someday for more CLI scripts
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"""
179 if os.path.isfile( value ):
180 # Accept any single file (including those with commas)
181 files.append( value )
183 # Accept a comma-separated list of filenames
184 files += value.split(',')
186 for fileName in files:
188 if os.path.isfile( fileName ):
189 # pylint: disable=exec-used
190 exec( compile( open( fileName ).read(), fileName, 'exec' ),
192 for name, val in customs.items():
193 self.setCustom( name, val )
195 raise Exception( 'could not find custom file: %s' % fileName )
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
203 globals()[ param ].update( value )
204 elif name == 'validate':
205 # Add custom validate function
206 self.validate = value
208 # Add or modify global variable or class
209 globals()[ name ] = value
211 def setNat( self, _option, opt_str, value, parser ):
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
222 parser.values.nat_args = []
223 parser.values.nat_kwargs = {}
225 def parseArgs( self ):
226 """Parse command-line args and return options object.
227 returns: opts parse options dict"""
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." )
233 usage = ( '%prog [options]\n'
234 '(type %prog -h for details)' )
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' )
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,
251 help='read custom classes or params from .py file(s)'
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 " +
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!) ' ) )
299 self.options, self.args = opts.parse_args()
301 # We don't accept extra arguments after the options
307 "Setup and validate environment."
309 # set logging verbosity
310 if LEVELS[self.options.verbosity] > LEVELS['output']:
311 warn( '*** WARNING: selected verbosity level (%s) will hide CLI '
313 'Please restart Mininet with -v [debug, info, output].\n'
314 % self.options.verbosity )
315 lg.setLogLevel( self.options.verbosity )
317 # Maybe we'll reorganize this someday...
318 # pylint: disable=too-many-branches,too-many-statements,global-statement
321 "Create and run mininet."
328 servers = opts.cluster.split( ',' )
329 for server in servers:
330 ClusterCleanup.add( server )
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 "
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 ]
360 if opts.switch == 'user' and opts.link == 'default':
361 debug( '*** Using TCULink with UserSwitch\n' )
362 # Use link configured correctly for UserSwitch
365 link = customClass( LINKS, opts.link )
368 self.validate( opts )
370 if opts.nolistenport:
371 opts.listenport = None
373 # Handle innamespace, cluster options
374 if opts.innamespace and opts.cluster:
375 error( "Please specify --innamespace OR --cluster\n" )
377 Net = MininetWithControlNet if opts.innamespace else Mininet
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
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 )
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()
400 # --custom files can set CLI or change mininet.cli.CLI
401 CLI = mininet.cli.CLI if CLI is None else CLI
404 CLI( mn, script=opts.pre )
409 runTests( mn, opts.test )
414 CLI( mn, script=opts.post )
418 elapsed = float( time.time() - start )
419 info( 'completed in %0.3f seconds\n' % elapsed )
422 if __name__ == "__main__":
425 except KeyboardInterrupt:
426 info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
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_ ) +
436 # Print stack trace to debug log
438 stackTrace = traceback.format_exc()
439 debug( stackTrace + "\n" )