Skip to content
Snippets Groups Projects
mn 10.6 KiB
Newer Older
#!/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.path
import sys
from mininet.clean import cleanup
from mininet.cli import CLI
from mininet.log import lg, LEVELS, info, warn
from mininet.net import Mininet, MininetWithControlNet
from mininet.node import Host, CPULimitedHost, Controller, OVSController, NOX
from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch
from mininet.link import Link, TCLink
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
Brandon Heller's avatar
Brandon Heller committed
from mininet.topolib import TreeTopo
from mininet.util import makeNumeric, custom

def customNode( constructors, argStr ):
    "Return custom Node constructor based on argStr"
    cname, newargs, kwargs = splitArgs( argStr )
    constructor = constructors.get( cname, None )
    if not constructor:
        raise Exception( "error: %s is unknown - please specify one of %s" %
               ( cname, constructors.keys() ) )

    def customized( name, *args, **params ):
        "Customized Node constructor"
        params.update( kwargs )
        if not newargs:
            return constructor( name, *args, **params )
        if args:
            warn( 'warning: %s replacing %s with %s\n',
                  constructor, args, newargs )
        return constructor( name, *newargs, **params )

    return customized
# built in topologies, created only when run
TOPODEF = 'minimal'
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
         'linear': LinearTopo,
         'reversed': SingleSwitchReversedTopo,
Brandon Heller's avatar
Brandon Heller committed
         'single': SingleSwitchTopo,
         'tree': TreeTopo }
SWITCHDEF = 'ovsk'
SWITCHES = { 'user': UserSwitch,
            'ovsk': OVSKernelSwitch }
HOSTDEF = 'proc'
HOSTS = { 'proc': Host,
          'rt': custom( CPULimitedHost, sched='rt' ),
          'cfs': custom( CPULimitedHost, sched='cfs' ) }
CONTROLLERDEF = 'ref'
CONTROLLERS = { 'ref': Controller,
                'nox': NOX,
                'remote': RemoteController,
                'none': lambda name: None }
LINKDEF = 'default'
LINKS = { 'default': Link,
          'tc': TCLink }
# optional tests to run
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
    'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' }

def splitArgs( argstr ):
    """Split argument string into usable python arguments
       argstr: argument string with format fn,arg2,kw1=arg3...
       returns: fn, args, kwargs"""
    split = argstr.split( ',' )
    fn = split[ 0 ]
    params = split[ 1: ]
    # Convert int and float args; removes the need for function
    # to be flexible with input arg formats.
    args = [ makeNumeric( s ) for s in params if '=' not in s ]
    kwargs = {}
    for s in [ p for p in params if '=' in p ]:
        key, val = s.split( '=' )
        kwargs[ key ] = makeNumeric( val )
    return fn, args, kwargs

def buildTopo( topoStr ):
    "Create topology from string with format (object, arg1, arg2,...)."
    topo, args, kwargs = splitArgs( topoStr )
    if topo not in TOPOS:
        raise Exception( 'Invalid topo name %s' % topo )
    return TOPOS[ topo ]( *args, **kwargs )
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 )
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 ):
Bob Lantz's avatar
Bob Lantz committed
        "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 )
            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' )

        opts = OptionParser()
        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( '--ip', type='string', default='127.0.0.1',
                        help='ip address as a dotted decimal string for a'
                        'remote controller' )
        opts.add_option( '--innamespace', action='store_true',
                        default=False, help='sw and ctrl in namespace?' )
Bob Lantz's avatar
Bob Lantz committed
        opts.add_option( '--listenport', type='int', default=6635,
                         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( '--prefixlen', type='int', default=8,
                        help='prefix length (e.g. /8) for automatic '
                        'network configuration' )
Bob Lantz's avatar
Bob Lantz committed

        self.options, self.args = opts.parse_args()

    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."
        if self.options.clean:
            cleanup()
            exit()

        start = time.time()

        topo = buildTopo( self.options.topo )
        switch = customNode( SWITCHES, self.options.switch )
        host = customNode( HOSTS, self.options.host )
        controller = customNode( CONTROLLERS, self.options.controller )
        link = customNode( LINKS, self.options.link )
        if self.validate:
            self.validate( self.options )
        inNamespace = self.options.innamespace
        Net = MininetWithControlNet if inNamespace else Mininet
        ipBase = self.options.ipbase
Brandon Heller's avatar
Brandon Heller committed
        xterms = self.options.xterms
        mac = self.options.mac
        arp = self.options.arp
        listenPort = None
        if not self.options.nolistenport:
            listenPort = self.options.listenport
        mn = Net( topo=topo,
                  switch=switch, host=host, controller=controller,
                  ipBase=ipBase,
                  inNamespace=inNamespace,
                  xterms=xterms, autoSetMacs=mac,
                  autoStaticArp=arp, listenPort=listenPort )
Bob Lantz's avatar
Bob Lantz committed

        if self.options.pre:
            CLI( mn, script=self.options.pre )
Brandon Heller's avatar
Brandon Heller committed
        test = self.options.test
        test = ALTSPELLING.get( test, test )
        elif test == 'all':
            mn.start()
            mn.ping()
            mn.iperf()
        elif test == 'cli':
            CLI( mn )
        elif test != 'build':
            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__":
    MininetRunner()