#!/usr/bin/env python """ Mininet runner author: Brandon Heller (brandonh@stanford.edu) To see options: sudo mn -h Example to pull custom params (topo, switch, etc.) from a file: sudo mn --custom ~/mininet/custom/custom_example.py """ from optparse import OptionParser import os import sys import time # Fix setuptools' evil madness, and open up (more?) security holes if 'PYTHONPATH' in os.environ: sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path from mininet.clean import cleanup import mininet.cli from mininet.log import lg, LEVELS, info, debug, warn, error, output from mininet.net import Mininet, MininetWithControlNet, VERSION from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, Ryu, NOX, RemoteController, findController, DefaultController, NullController, UserSwitch, OVSSwitch, OVSBridge, IVSSwitch ) from mininet.nodelib import LinuxBridge from mininet.link import Link, TCLink, TCULink, OVSLink from mininet.topo import ( SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo, MinimalTopo ) from mininet.topolib import TreeTopo, TorusTopo from mininet.util import customClass, specialClass, splitArgs from mininet.util import buildTopo from functools import partial # Experimental! cluster edition prototype from mininet.examples.cluster import ( MininetCluster, RemoteHost, RemoteOVSSwitch, RemoteLink, SwitchBinPlacer, RandomPlacer, ClusterCleanup, RemoteGRELink ) from mininet.examples.clustercli import ClusterCLI PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer } CLUSTERLINKS = {'ssh': RemoteLink, 'gre': RemoteGRELink} ClusterLink = CLUSTERLINKS['gre'] #GRE Tunnel permite un mayor ancho de banda entre los links # built in topologies, created only when run TOPODEF = 'minimal' TOPOS = { 'minimal': MinimalTopo, 'linear': LinearTopo, 'reversed': SingleSwitchReversedTopo, 'single': SingleSwitchTopo, 'tree': TreeTopo, 'torus': TorusTopo } SWITCHDEF = 'default' SWITCHES = { 'user': UserSwitch, 'ovs': OVSSwitch, 'ovsbr' : OVSBridge, # Keep ovsk for compatibility with 2.0 'ovsk': OVSSwitch, 'ivs': IVSSwitch, 'lxbr': LinuxBridge, 'default': OVSSwitch } HOSTDEF = 'proc' HOSTS = { 'proc': Host, 'rt': specialClass( CPULimitedHost, defaults=dict( sched='rt' ) ), 'cfs': specialClass( CPULimitedHost, defaults=dict( sched='cfs' ) ) } CONTROLLERDEF = 'default' CONTROLLERS = { 'ref': Controller, 'ovsc': OVSController, 'nox': NOX, 'remote': RemoteController, 'ryu': Ryu, 'default': DefaultController, # Note: overridden below 'none': NullController } LINKDEF = 'default' LINKS = { 'default': Link, # Note: overridden below 'tc': TCLink, 'tcu': TCULink, 'ovs': OVSLink } # TESTS dict can contain functions and/or Mininet() method names # XXX: it would be nice if we could specify a default test, but # this may be tricky TESTS = { name: True for name in ( 'pingall', 'pingpair', 'iperf', 'iperfudp' ) } CLI = None # Set below if needed # Locally defined tests def allTest( net ): "Run ping and iperf tests" net.waitConnected() net.start() net.ping() net.iperf() def nullTest( _net ): "Null 'test' (does nothing)" pass TESTS.update( all=allTest, none=nullTest, build=nullTest ) # Map to alternate spellings of Mininet() methods ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair', 'iperfudp': 'iperfUdp' } def runTests( mn, options ): """Run tests mn: Mininet object option: list of test optinos """ # Split option into test name and parameters for option in options: # Multiple tests may be separated by '+' for now for test in option.split( '+' ): test, args, kwargs = splitArgs( test ) test = ALTSPELLING.get( test.lower(), test ) testfn = TESTS.get( test, test ) if callable( testfn ): testfn( mn, *args, **kwargs ) elif hasattr( mn, test ): mn.waitConnected() getattr( mn, test )( *args, **kwargs ) else: raise Exception( 'Test %s is unknown - please specify one of ' '%s ' % ( test, TESTS.keys() ) ) def addDictOption( opts, choicesDict, default, name, **kwargs ): """Convenience function to add choices dicts to OptionParser. opts: OptionParser instance choicesDict: dictionary of valid choices, must include default default: default choice key name: long option name kwargs: additional arguments to add_option""" helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]' ) helpList = [ '%s=%s' % ( k, v.__name__ ) for k, v in choicesDict.items() ] helpStr += ' ' + ( ' '.join( helpList ) ) params = dict( type='string', default=default, help=helpStr ) params.update( **kwargs ) opts.add_option( '--' + name, **params ) def version( *_args ): "Print Mininet version and exit" output( "%s\n" % VERSION ) exit() class MininetRunner( object ): "Build, setup, and run Mininet." def __init__( self ): "Init." self.options = None self.args = None # May be used someday for more CLI scripts self.validate = None self.parseArgs() self.setup() self.begin() def custom( self, _option, _opt_str, value, _parser ): """Parse custom file and add params. option: option e.g. --custom opt_str: option string e.g. --custom value: the value the follows the option parser: option parser instance""" files = [] if os.path.isfile( value ): # Accept any single file (including those with commas) files.append( value ) else: # Accept a comma-separated list of filenames files += value.split(',') for fileName in files: customs = {} if os.path.isfile( fileName ): # pylint: disable=exec-used exec( compile( open( fileName ).read(), fileName, 'exec' ), customs, customs ) for name, val in customs.items(): self.setCustom( name, val ) else: raise Exception( 'could not find custom file: %s' % fileName ) def setCustom( self, name, value ): "Set custom parameters for MininetRunner." if name in ( 'topos', 'switches', 'hosts', 'controllers', 'links' 'testnames', 'tests' ): # Update dictionaries param = name.upper() globals()[ param ].update( value ) elif name == 'validate': # Add custom validate function self.validate = value else: # Add or modify global variable or class globals()[ name ] = value def setNat( self, _option, opt_str, value, parser ): "Set NAT option(s)" assert self # satisfy pylint parser.values.nat = True # first arg, first char != '-' if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-': value = parser.rargs.pop( 0 ) _, args, kwargs = splitArgs( opt_str + ',' + value ) parser.values.nat_args = args parser.values.nat_kwargs = kwargs else: parser.values.nat_args = [] parser.values.nat_kwargs = {} def parseArgs( self ): """Parse command-line args and return options object. returns: opts parse options dict""" desc = ( "The %prog utility creates Mininet network from the\n" "command line. It can create parametrized topologies,\n" "invoke the Mininet CLI, and run tests." ) usage = ( '%prog [options]\n' '(type %prog -h for details)' ) opts = OptionParser( description=desc, usage=usage ) addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' ) addDictOption( opts, HOSTS, HOSTDEF, 'host' ) addDictOption( opts, CONTROLLERS, [], 'controller', action='append' ) addDictOption( opts, LINKS, LINKDEF, 'link' ) addDictOption( opts, TOPOS, TOPODEF, 'topo' ) opts.add_option( '--clean', '-c', action='store_true', default=False, help='clean and exit' ) opts.add_option( '--cluster', type='string', default=None, metavar='server1,server2...', help=( 'run on multiple servers (experimental!)' ) ) opts.add_option( '--custom', action='callback', callback=self.custom, type='string', help='read custom classes or params from .py file(s)' ) opts.add_option( '--test', default=[], action='append', dest='test', help='|'.join( TESTS.keys() ) ) opts.add_option( '--xterms', '-x', action='store_true', default=False, help='spawn xterms for each node' ) opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8', help='base IP address for hosts' ) opts.add_option( '--mac', action='store_true', default=False, help='automatically set host MACs' ) opts.add_option( '--arp', action='store_true', default=False, help='set all-pairs ARP entries' ) opts.add_option( '--verbosity', '-v', type='choice', choices=list( LEVELS.keys() ), default = 'info', help = '|'.join( LEVELS.keys() ) ) opts.add_option( '--innamespace', action='store_true', default=False, help='sw and ctrl in namespace?' ) opts.add_option( '--listenport', type='int', default=6654, help='base port for passive switch listening' ) opts.add_option( '--nolistenport', action='store_true', default=False, help="don't use passive listening " + "port") opts.add_option( '--pre', type='string', default=None, help='CLI script to run before tests' ) opts.add_option( '--post', type='string', default=None, help='CLI script to run after tests' ) opts.add_option( '--pin', action='store_true', default=False, help="pin hosts to CPU cores " "(requires --host cfs or --host rt)" ) opts.add_option( '--nat', action='callback', callback=self.setNat, help="[option=val...] adds a NAT to the topology that" " connects Mininet hosts to the physical network." " Warning: This may route any traffic on the machine" " that uses Mininet's" " IP subnet into the Mininet network." " If you need to change" " Mininet's IP subnet, see the --ipbase option." ) opts.add_option( '--version', action='callback', callback=version, help='prints the version and exits' ) # opts.add_option( '--cluster', type='string', default=None, # metavar='server1,server2...', # help=( 'run on multiple servers (experimental!)' ) ) opts.add_option( '--placement', type='choice', choices=list( PLACEMENT.keys() ), default='block', metavar='block|random', help=( 'node placement for --cluster ' '(experimental!) ' ) ) self.options, self.args = opts.parse_args() # We don't accept extra arguments after the options if self.args: opts.print_help() exit() def setup( self ): "Setup and validate environment." # set logging verbosity if LEVELS[self.options.verbosity] > LEVELS['output']: warn( '*** WARNING: selected verbosity level (%s) will hide CLI ' 'output!\n' 'Please restart Mininet with -v [debug, info, output].\n' % self.options.verbosity ) lg.setLogLevel( self.options.verbosity ) # Maybe we'll reorganize this someday... # pylint: disable=too-many-branches,too-many-statements,global-statement def begin( self ): "Create and run mininet." global CLI opts = self.options if opts.cluster: servers = opts.cluster.split( ',' ) for server in servers: ClusterCleanup.add( server ) if opts.clean: cleanup() exit() start = time.time() if not opts.controller: # Update default based on available controllers CONTROLLERS[ 'default' ] = findController() opts.controller = [ 'default' ] if not CONTROLLERS[ 'default' ]: opts.controller = [ 'none' ] if opts.switch == 'default': info( '*** No default OpenFlow controller found ' 'for default switch!\n' ) info( '*** Falling back to OVS Bridge\n' ) opts.switch = 'ovsbr' elif opts.switch not in ( 'ovsbr', 'lxbr' ): raise Exception( "Could not find a default controller " "for switch %s" % opts.switch ) topo = buildTopo( TOPOS, opts.topo ) switch = customClass( SWITCHES, opts.switch ) host = customClass( HOSTS, opts.host ) controller = [ customClass( CONTROLLERS, c ) for c in opts.controller ] if opts.switch == 'user' and opts.link == 'default': debug( '*** Using TCULink with UserSwitch\n' ) # Use link configured correctly for UserSwitch opts.link = 'tcu' link = customClass( LINKS, opts.link ) if self.validate: self.validate( opts ) if opts.nolistenport: opts.listenport = None # Handle innamespace, cluster options if opts.innamespace and opts.cluster: error( "Please specify --innamespace OR --cluster\n" ) exit() Net = MininetWithControlNet if opts.innamespace else Mininet if opts.cluster: warn( '*** WARNING: Modo Cluster ON!\n' '*** Using RemoteHost, RemoteOVSSwitch, RemoteGRELink\n' ) host, switch, link = RemoteHost, RemoteOVSSwitch, ClusterLink Net = partial( MininetCluster, servers=servers, placement=PLACEMENT[ opts.placement ] ) mininet.cli.CLI = ClusterCLI mn = Net( topo=topo, switch=switch, host=host, controller=controller, link=link, ipBase=opts.ipbase, inNamespace=opts.innamespace, xterms=opts.xterms, autoSetMacs=opts.mac, autoStaticArp=opts.arp, autoPinCpus=opts.pin, listenPort=opts.listenport ) if opts.ensure_value( 'nat', False ): with open( '/etc/resolv.conf' ) as f: if 'nameserver 127.' in f.read(): warn( '*** Warning: loopback address in /etc/resolv.conf ' 'may break host DNS over NAT\n') mn.addNAT( *opts.nat_args, **opts.nat_kwargs ).configDefault() # --custom files can set CLI or change mininet.cli.CLI CLI = mininet.cli.CLI if CLI is None else CLI if opts.pre: CLI( mn, script=opts.pre ) mn.start() if opts.test: runTests( mn, opts.test ) else: CLI( mn ) if opts.post: CLI( mn, script=opts.post ) mn.stop() elapsed = float( time.time() - start ) info( 'completed in %0.3f seconds\n' % elapsed ) if __name__ == "__main__": try: MininetRunner() except KeyboardInterrupt: info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n") cleanup() except Exception: # Print exception type_, val_, trace_ = sys.exc_info() errorMsg = ( "-"*80 + "\n" + "Caught exception. Cleaning up...\n\n" + "%s: %s\n" % ( type_.__name__, val_ ) + "-"*80 + "\n" ) error( errorMsg ) # Print stack trace to debug log import traceback stackTrace = traceback.format_exc() debug( stackTrace + "\n" ) cleanup()