#!/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 from mininet.cli import CLI from mininet.log import lg, LEVELS, info, debug, warn, error from mininet.net import Mininet, MininetWithControlNet, VERSION from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, RYU, NOX, RemoteController, findController, DefaultController, UserSwitch, OVSSwitch, OVSBridge, OVSLegacyKernelSwitch, IVSSwitch ) from mininet.nodelib import LinuxBridge from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo, TorusTopo from mininet.util import custom, customConstructor from mininet.util import buildTopo from functools import partial # Experimental! cluster edition prototype from mininet.examples.cluster import ( MininetCluster, RemoteHost, RemoteOVSSwitch, RemoteLink, SwitchBinPlacer, RandomPlacer ) from mininet.examples.clustercli import DemoCLI as ClusterCLI PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer } # built in topologies, created only when run TOPODEF = 'minimal' TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), '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, 'ovsl': OVSLegacyKernelSwitch, 'ivs': IVSSwitch, 'lxbr': LinuxBridge, 'default': OVSSwitch } HOSTDEF = 'proc' HOSTS = { 'proc': Host, 'rt': custom( CPULimitedHost, sched='rt' ), 'cfs': custom( CPULimitedHost, sched='cfs' ) } CONTROLLERDEF = 'default' CONTROLLERS = { 'ref': Controller, 'ovsc': OVSController, 'nox': NOX, 'remote': RemoteController, 'ryu': RYU, 'default': DefaultController, # Note: replaced below 'none': lambda name: None } LINKDEF = 'default' LINKS = { 'default': Link, 'tc': TCLink } # optional tests to run TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', 'none' ] ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair', 'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp' } def addDictOption( opts, choicesDict, default, name, helpStr=None ): """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 help: string""" if default not in choicesDict: raise Exception( 'Invalid default %s for choices dict: %s' % ( default, name ) ) if not helpStr: helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]' ) opts.add_option( '--' + name, type='string', default = default, help = helpStr ) def version( *_args ): "Print Mininet version and exit" print "%s" % 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 setCustom( self, name, value ): "Set custom parameters for MininetRunner." if name in ( 'topos', 'switches', 'hosts', 'controllers' ): # 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 parseCustomFile( self, fileName ): "Parse custom file and add params before parsing cmd-line options." customs = {} if os.path.isfile( fileName ): execfile( fileName, customs, customs ) for name, val in customs.iteritems(): self.setCustom( name, val ) else: raise Exception( 'could not find custom file: %s' % fileName ) def parseArgs( self ): """Parse command-line args and return options object. returns: opts parse options dict""" if '--custom' in sys.argv: index = sys.argv.index( '--custom' ) if len( sys.argv ) > index + 1: filename = sys.argv[ index + 1 ] self.parseCustomFile( filename ) else: raise Exception( 'Custom file name not found' ) 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, CONTROLLERDEF, 'controller' ) 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( '--custom', type='string', default=None, help='read custom topo and node params from .py' + 'file' ) opts.add_option( '--test', type='choice', choices=TESTS, default=TESTS[ 0 ], help='|'.join( TESTS ) ) 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=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=6634, 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='store_true', default=False, help="adds a NAT to the topology " "that connects Mininet to the physical network" ) opts.add_option( '--version', action='callback', callback=version ) opts.add_option( '--cluster', type='string', default=None, metavar='server1,server2...', help=( 'run on multiple servers (experimental!)' ) ) opts.add_option( '--placement', type='choice', choices=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']: print ( '*** WARNING: selected verbosity level (%s) will hide CLI ' 'output!\n' 'Please restart Mininet with -v [debug, info, output].' % self.options.verbosity ) lg.setLogLevel( self.options.verbosity ) def begin( self ): "Create and run mininet." global CLI if self.options.clean: cleanup() exit() start = time.time() if self.options.controller == 'default': # Update default based on available controllers CONTROLLERS[ 'default' ] = findController() if CONTROLLERS[ 'default' ] is None: if self.options.switch == 'default': # Fall back to OVS Bridge, which does not use an OF controller info( '*** No default OpenFlow controller found for default switch!\n' ) info( '*** Falling back to OVS Bridge\n' ) self.options.switch = 'ovsbr' self.options.controller = 'none' elif self.options.switch in ( 'ovsbr', 'lxbr' ): self.options.controller = 'none' else: raise Exception( "Could not find a default controller for switch %s" % self.options.switch ) topo = buildTopo( TOPOS, self.options.topo ) switch = customConstructor( SWITCHES, self.options.switch ) host = customConstructor( HOSTS, self.options.host ) controller = customConstructor( CONTROLLERS, self.options.controller ) link = customConstructor( LINKS, self.options.link ) if self.validate: self.validate( self.options ) ipBase = self.options.ipbase xterms = self.options.xterms mac = self.options.mac arp = self.options.arp pin = self.options.pin listenPort = None if not self.options.nolistenport: listenPort = self.options.listenport # Handle inNamespace, cluster options inNamespace = self.options.innamespace cluster = self.options.cluster if inNamespace and cluster: print "Please specify --innamespace OR --cluster" exit() Net = MininetWithControlNet if inNamespace else Mininet if cluster: warn( '*** WARNING: Experimental cluster mode!\n' '*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' ) host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink CLI = ClusterCLI Net = partial( MininetCluster, servers=cluster.split( ',' ), placement=PLACEMENT[ self.options.placement ] ) mn = Net( topo=topo, switch=switch, host=host, controller=controller, link=link, ipBase=ipBase, inNamespace=inNamespace, xterms=xterms, autoSetMacs=mac, autoStaticArp=arp, autoPinCpus=pin, listenPort=listenPort ) if self.options.nat: nat = mn.addNAT() nat.configDefault() if self.options.pre: CLI( mn, script=self.options.pre ) test = self.options.test test = ALTSPELLING.get( test, test ) mn.start() if test == 'none': pass elif test == 'all': mn.waitConnected() mn.start() mn.ping() mn.iperf() elif test == 'cli': CLI( mn ) elif test != 'build': mn.waitConnected() getattr( mn, test )() if self.options.post: CLI( mn, script=self.options.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()