#!/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 import time from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info, warn from mininet.net import Mininet, init from mininet.node import Host, CPULimitedHost, Controller, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch from mininet.link import Intf, TCIntf from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo 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 args: # raise Exception( "please specify keyword arguments for " + cname ) if not constructor: raise Exception( "error: %s is unknown - please specify one of %s" % ( cname, constructors.keys() ) ) def custom( name, *args, **params ): 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 custom # built in topologies, created only when run TOPODEF = 'minimal' TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), 'linear': LinearTopo, 'reversed': SingleSwitchReversedTopo, '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 } INTFDEF = 'default' INTFS = { 'default': Intf, 'tc': TCIntf } # optional tests to run TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', 'none' ] 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 = [ s for s in params if '=' not in s ] args = map( makeNumeric, args ) 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 ): "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." custom = {} if os.path.isfile( fileName ): execfile( fileName, custom, custom ) for name in custom: self.setCustom( name, custom[ name ] ) 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: custom = sys.argv[ index + 1 ] self.parseCustomFile( custom ) 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, INTFS, INTFDEF, 'intf' ) 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( '--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?' ) 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( '--prefixlen', type='int', default=8, help='prefix length (e.g. /8) for automatic ' 'network configuration' ) 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 ) # validate environment setup init() 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 ) intf = customNode( INTFS, self.options.intf ) if self.validate: self.validate( self.options ) inNamespace = self.options.innamespace xterms = self.options.xterms mac = self.options.mac arp = self.options.arp listenPort = None if not self.options.nolistenport: listenPort = self.options.listenport mn = Mininet( topo=topo, switch=switch, host=host, controller=controller, intf=intf, inNamespace=inNamespace, xterms=xterms, autoSetMacs=mac, autoStaticArp=arp, listenPort=listenPort ) 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.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()