carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
| 3 | """ |
| 4 | Mininet runner |
| 5 | author: Brandon Heller (brandonh@stanford.edu) |
| 6 | |
| 7 | To see options: |
| 8 | sudo mn -h |
| 9 | |
| 10 | Example to pull custom params (topo, switch, etc.) from a file: |
| 11 | sudo mn --custom ~/mininet/custom/custom_example.py |
| 12 | """ |
| 13 | |
| 14 | from optparse import OptionParser |
| 15 | import os |
| 16 | import sys |
| 17 | import time |
| 18 | |
| 19 | # Fix setuptools' evil madness, and open up (more?) security holes |
| 20 | if 'PYTHONPATH' in os.environ: |
| 21 | sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path |
| 22 | |
| 23 | from mininet.clean import cleanup |
| 24 | from mininet.cli import CLI |
| 25 | from mininet.log import lg, LEVELS, info |
| 26 | from mininet.net import Mininet, MininetWithControlNet, VERSION |
| 27 | from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, |
| 28 | NOX, RemoteController, UserSwitch, OVSKernelSwitch, |
| 29 | OVSLegacyKernelSwitch ) |
| 30 | from mininet.link import Link, TCLink |
| 31 | from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo |
| 32 | from mininet.topolib import TreeTopo |
| 33 | from mininet.util import custom, customConstructor |
| 34 | from mininet.util import buildTopo |
| 35 | |
| 36 | |
| 37 | # built in topologies, created only when run |
| 38 | TOPODEF = 'minimal' |
| 39 | TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), |
| 40 | 'linear': LinearTopo, |
| 41 | 'reversed': SingleSwitchReversedTopo, |
| 42 | 'single': SingleSwitchTopo, |
| 43 | 'tree': TreeTopo } |
| 44 | |
| 45 | SWITCHDEF = 'ovsk' |
| 46 | SWITCHES = { 'user': UserSwitch, |
| 47 | 'ovsk': OVSKernelSwitch, |
| 48 | 'ovsl': OVSLegacyKernelSwitch } |
| 49 | |
| 50 | HOSTDEF = 'proc' |
| 51 | HOSTS = { 'proc': Host, |
| 52 | 'rt': custom( CPULimitedHost, sched='rt' ), |
| 53 | 'cfs': custom( CPULimitedHost, sched='cfs' ) } |
| 54 | |
| 55 | CONTROLLERDEF = 'ovsc' |
| 56 | CONTROLLERS = { 'ref': Controller, |
| 57 | 'ovsc': OVSController, |
| 58 | 'nox': NOX, |
| 59 | 'remote': RemoteController, |
| 60 | 'none': lambda name: None } |
| 61 | |
| 62 | LINKDEF = 'default' |
| 63 | LINKS = { 'default': Link, |
| 64 | 'tc': TCLink } |
| 65 | |
| 66 | |
| 67 | # optional tests to run |
| 68 | TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', |
| 69 | 'none' ] |
| 70 | |
| 71 | ALTSPELLING = { 'pingall': 'pingAll', |
| 72 | 'pingpair': 'pingPair', |
| 73 | 'iperfudp': 'iperfUdp', |
| 74 | 'iperfUDP': 'iperfUdp', |
| 75 | 'prefixlen': 'prefixLen' } |
| 76 | |
| 77 | |
| 78 | def addDictOption( opts, choicesDict, default, name, helpStr=None ): |
| 79 | """Convenience function to add choices dicts to OptionParser. |
| 80 | opts: OptionParser instance |
| 81 | choicesDict: dictionary of valid choices, must include default |
| 82 | default: default choice key |
| 83 | name: long option name |
| 84 | help: string""" |
| 85 | if default not in choicesDict: |
| 86 | raise Exception( 'Invalid default %s for choices dict: %s' % |
| 87 | ( default, name ) ) |
| 88 | if not helpStr: |
| 89 | helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + |
| 90 | '[,param=value...]' ) |
| 91 | opts.add_option( '--' + name, |
| 92 | type='string', |
| 93 | default = default, |
| 94 | help = helpStr ) |
| 95 | |
| 96 | |
| 97 | def version( *_args ): |
| 98 | "Print Mininet version and exit" |
| 99 | print "%s" % VERSION |
| 100 | exit() |
| 101 | |
| 102 | class MininetRunner( object ): |
| 103 | "Build, setup, and run Mininet." |
| 104 | |
| 105 | def __init__( self ): |
| 106 | "Init." |
| 107 | self.options = None |
| 108 | self.args = None # May be used someday for more CLI scripts |
| 109 | self.validate = None |
| 110 | |
| 111 | self.parseArgs() |
| 112 | self.setup() |
| 113 | self.begin() |
| 114 | |
| 115 | def setCustom( self, name, value ): |
| 116 | "Set custom parameters for MininetRunner." |
| 117 | if name in ( 'topos', 'switches', 'hosts', 'controllers' ): |
| 118 | # Update dictionaries |
| 119 | param = name.upper() |
| 120 | globals()[ param ].update( value ) |
| 121 | elif name == 'validate': |
| 122 | # Add custom validate function |
| 123 | self.validate = value |
| 124 | else: |
| 125 | # Add or modify global variable or class |
| 126 | globals()[ name ] = value |
| 127 | |
| 128 | def parseCustomFile( self, fileName ): |
| 129 | "Parse custom file and add params before parsing cmd-line options." |
| 130 | customs = {} |
| 131 | if os.path.isfile( fileName ): |
| 132 | execfile( fileName, customs, customs ) |
| 133 | for name, val in customs.iteritems(): |
| 134 | self.setCustom( name, val ) |
| 135 | else: |
| 136 | raise Exception( 'could not find custom file: %s' % fileName ) |
| 137 | |
| 138 | def parseArgs( self ): |
| 139 | """Parse command-line args and return options object. |
| 140 | returns: opts parse options dict""" |
| 141 | if '--custom' in sys.argv: |
| 142 | index = sys.argv.index( '--custom' ) |
| 143 | if len( sys.argv ) > index + 1: |
| 144 | filename = sys.argv[ index + 1 ] |
| 145 | self.parseCustomFile( filename ) |
| 146 | else: |
| 147 | raise Exception( 'Custom file name not found' ) |
| 148 | |
| 149 | desc = ( "The %prog utility creates Mininet network from the\n" |
| 150 | "command line. It can create parametrized topologies,\n" |
| 151 | "invoke the Mininet CLI, and run tests." ) |
| 152 | |
| 153 | usage = ( '%prog [options]\n' |
| 154 | '(type %prog -h for details)' ) |
| 155 | |
| 156 | opts = OptionParser( description=desc, usage=usage ) |
| 157 | addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' ) |
| 158 | addDictOption( opts, HOSTS, HOSTDEF, 'host' ) |
| 159 | addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' ) |
| 160 | addDictOption( opts, LINKS, LINKDEF, 'link' ) |
| 161 | addDictOption( opts, TOPOS, TOPODEF, 'topo' ) |
| 162 | |
| 163 | opts.add_option( '--clean', '-c', action='store_true', |
| 164 | default=False, help='clean and exit' ) |
| 165 | opts.add_option( '--custom', type='string', default=None, |
| 166 | help='read custom topo and node params from .py' + |
| 167 | 'file' ) |
| 168 | opts.add_option( '--test', type='choice', choices=TESTS, |
| 169 | default=TESTS[ 0 ], |
| 170 | help='|'.join( TESTS ) ) |
| 171 | opts.add_option( '--xterms', '-x', action='store_true', |
| 172 | default=False, help='spawn xterms for each node' ) |
| 173 | opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8', |
| 174 | help='base IP address for hosts' ) |
| 175 | opts.add_option( '--mac', action='store_true', |
| 176 | default=False, help='automatically set host MACs' ) |
| 177 | opts.add_option( '--arp', action='store_true', |
| 178 | default=False, help='set all-pairs ARP entries' ) |
| 179 | opts.add_option( '--verbosity', '-v', type='choice', |
| 180 | choices=LEVELS.keys(), default = 'info', |
| 181 | help = '|'.join( LEVELS.keys() ) ) |
| 182 | opts.add_option( '--innamespace', action='store_true', |
| 183 | default=False, help='sw and ctrl in namespace?' ) |
| 184 | opts.add_option( '--listenport', type='int', default=6634, |
| 185 | help='base port for passive switch listening' ) |
| 186 | opts.add_option( '--nolistenport', action='store_true', |
| 187 | default=False, help="don't use passive listening " + |
| 188 | "port") |
| 189 | opts.add_option( '--pre', type='string', default=None, |
| 190 | help='CLI script to run before tests' ) |
| 191 | opts.add_option( '--post', type='string', default=None, |
| 192 | help='CLI script to run after tests' ) |
| 193 | opts.add_option( '--prefixlen', type='int', default=8, |
| 194 | help='prefix length (e.g. /8) for automatic ' |
| 195 | 'network configuration' ) |
| 196 | opts.add_option( '--pin', action='store_true', |
| 197 | default=False, help="pin hosts to CPU cores " |
| 198 | "(requires --host cfs or --host rt)" ) |
| 199 | opts.add_option( '--version', action='callback', callback=version ) |
| 200 | |
| 201 | self.options, self.args = opts.parse_args() |
| 202 | |
| 203 | def setup( self ): |
| 204 | "Setup and validate environment." |
| 205 | |
| 206 | # set logging verbosity |
| 207 | if LEVELS[self.options.verbosity] > LEVELS['output']: |
| 208 | print ( '*** WARNING: selected verbosity level (%s) will hide CLI ' |
| 209 | 'output!\n' |
| 210 | 'Please restart Mininet with -v [debug, info, output].' |
| 211 | % self.options.verbosity ) |
| 212 | lg.setLogLevel( self.options.verbosity ) |
| 213 | |
| 214 | def begin( self ): |
| 215 | "Create and run mininet." |
| 216 | |
| 217 | if self.options.clean: |
| 218 | cleanup() |
| 219 | exit() |
| 220 | |
| 221 | start = time.time() |
| 222 | |
| 223 | topo = buildTopo( TOPOS, self.options.topo ) |
| 224 | switch = customConstructor( SWITCHES, self.options.switch ) |
| 225 | host = customConstructor( HOSTS, self.options.host ) |
| 226 | controller = customConstructor( CONTROLLERS, self.options.controller ) |
| 227 | link = customConstructor( LINKS, self.options.link ) |
| 228 | |
| 229 | if self.validate: |
| 230 | self.validate( self.options ) |
| 231 | |
| 232 | inNamespace = self.options.innamespace |
| 233 | Net = MininetWithControlNet if inNamespace else Mininet |
| 234 | ipBase = self.options.ipbase |
| 235 | xterms = self.options.xterms |
| 236 | mac = self.options.mac |
| 237 | arp = self.options.arp |
| 238 | pin = self.options.pin |
| 239 | listenPort = None |
| 240 | if not self.options.nolistenport: |
| 241 | listenPort = self.options.listenport |
| 242 | mn = Net( topo=topo, |
| 243 | switch=switch, host=host, controller=controller, |
| 244 | link=link, |
| 245 | ipBase=ipBase, |
| 246 | inNamespace=inNamespace, |
| 247 | xterms=xterms, autoSetMacs=mac, |
| 248 | autoStaticArp=arp, autoPinCpus=pin, |
| 249 | listenPort=listenPort ) |
| 250 | |
| 251 | if self.options.pre: |
| 252 | CLI( mn, script=self.options.pre ) |
| 253 | |
| 254 | test = self.options.test |
| 255 | test = ALTSPELLING.get( test, test ) |
| 256 | |
| 257 | mn.start() |
| 258 | |
| 259 | if test == 'none': |
| 260 | pass |
| 261 | elif test == 'all': |
| 262 | mn.start() |
| 263 | mn.ping() |
| 264 | mn.iperf() |
| 265 | elif test == 'cli': |
| 266 | CLI( mn ) |
| 267 | elif test != 'build': |
| 268 | getattr( mn, test )() |
| 269 | |
| 270 | if self.options.post: |
| 271 | CLI( mn, script=self.options.post ) |
| 272 | |
| 273 | mn.stop() |
| 274 | |
| 275 | elapsed = float( time.time() - start ) |
| 276 | info( 'completed in %0.3f seconds\n' % elapsed ) |
| 277 | |
| 278 | |
| 279 | if __name__ == "__main__": |
| 280 | MininetRunner() |