mininet / bin / mn @ b739cd11
History | View | Annotate | Download (14 KB)
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, debug, warn, error |
26 |
from mininet.net import Mininet, MininetWithControlNet, VERSION |
27 |
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, |
28 |
RYU, NOX, RemoteController, findController, DefaultController, |
29 |
UserSwitch, OVSSwitch, OVSBridge, |
30 |
OVSLegacyKernelSwitch, IVSSwitch ) |
31 |
from mininet.nodelib import LinuxBridge |
32 |
from mininet.link import Link, TCLink |
33 |
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo |
34 |
from mininet.topolib import TreeTopo, TorusTopo |
35 |
from mininet.util import customConstructor |
36 |
from mininet.util import buildTopo |
37 |
|
38 |
from functools import partial |
39 |
|
40 |
# Experimental! cluster edition prototype |
41 |
from mininet.examples.cluster import ( MininetCluster, RemoteHost, |
42 |
RemoteOVSSwitch, RemoteLink, |
43 |
SwitchBinPlacer, RandomPlacer ) |
44 |
from mininet.examples.clustercli import DemoCLI as ClusterCLI |
45 |
|
46 |
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer } |
47 |
|
48 |
# built in topologies, created only when run |
49 |
TOPODEF = 'minimal' |
50 |
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), |
51 |
'linear': LinearTopo, |
52 |
'reversed': SingleSwitchReversedTopo, |
53 |
'single': SingleSwitchTopo, |
54 |
'tree': TreeTopo, |
55 |
'torus': TorusTopo } |
56 |
|
57 |
SWITCHDEF = 'default' |
58 |
SWITCHES = { 'user': UserSwitch, |
59 |
'ovs': OVSSwitch, |
60 |
'ovsbr' : OVSBridge, |
61 |
# Keep ovsk for compatibility with 2.0 |
62 |
'ovsk': OVSSwitch, |
63 |
'ovsl': OVSLegacyKernelSwitch, |
64 |
'ivs': IVSSwitch, |
65 |
'lxbr': LinuxBridge, |
66 |
'default': OVSSwitch } |
67 |
|
68 |
HOSTDEF = 'proc' |
69 |
HOSTS = { 'proc': Host, |
70 |
'rt': partial( CPULimitedHost, sched='rt' ), |
71 |
'cfs': partial( CPULimitedHost, sched='cfs' ) } |
72 |
|
73 |
CONTROLLERDEF = 'default' |
74 |
CONTROLLERS = { 'ref': Controller, |
75 |
'ovsc': OVSController, |
76 |
'nox': NOX, |
77 |
'remote': RemoteController, |
78 |
'ryu': RYU, |
79 |
'default': DefaultController, # Note: replaced below |
80 |
'none': lambda name: None } |
81 |
|
82 |
LINKDEF = 'default' |
83 |
LINKS = { 'default': Link, |
84 |
'tc': TCLink } |
85 |
|
86 |
|
87 |
# optional tests to run |
88 |
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', |
89 |
'none' ] |
90 |
|
91 |
ALTSPELLING = { 'pingall': 'pingAll', |
92 |
'pingpair': 'pingPair', |
93 |
'iperfudp': 'iperfUdp', |
94 |
'iperfUDP': 'iperfUdp' } |
95 |
|
96 |
|
97 |
def addDictOption( opts, choicesDict, default, name, helpStr=None ): |
98 |
"""Convenience function to add choices dicts to OptionParser. |
99 |
opts: OptionParser instance |
100 |
choicesDict: dictionary of valid choices, must include default |
101 |
default: default choice key |
102 |
name: long option name |
103 |
help: string""" |
104 |
if default not in choicesDict: |
105 |
raise Exception( 'Invalid default %s for choices dict: %s' % |
106 |
( default, name ) ) |
107 |
if not helpStr: |
108 |
helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + |
109 |
'[,param=value...]' ) |
110 |
opts.add_option( '--' + name, |
111 |
type='string', |
112 |
default = default, |
113 |
help = helpStr ) |
114 |
|
115 |
|
116 |
def version( *_args ): |
117 |
"Print Mininet version and exit" |
118 |
print "%s" % VERSION |
119 |
exit() |
120 |
|
121 |
|
122 |
class MininetRunner( object ): |
123 |
"Build, setup, and run Mininet." |
124 |
|
125 |
def __init__( self ): |
126 |
"Init." |
127 |
self.options = None |
128 |
self.args = None # May be used someday for more CLI scripts |
129 |
self.validate = None |
130 |
|
131 |
self.parseArgs() |
132 |
self.setup() |
133 |
self.begin() |
134 |
|
135 |
def custom( self, option, opt_str, value, parser ): |
136 |
"""Parse custom file and add params. |
137 |
option: option e.g. --custom |
138 |
opt_str: option string e.g. --custom |
139 |
value: the value the follows the option |
140 |
parser: option parser instance""" |
141 |
files = [] |
142 |
if os.path.isfile( value ): |
143 |
# Accept any single file (including those with commas) |
144 |
files.append( value ) |
145 |
else: |
146 |
# Accept a comma-separated list of filenames |
147 |
files += value.split(',') |
148 |
|
149 |
for fileName in files: |
150 |
customs = {} |
151 |
if os.path.isfile( fileName ): |
152 |
execfile( fileName, customs, customs ) |
153 |
for name, val in customs.iteritems(): |
154 |
self.setCustom( name, val ) |
155 |
else: |
156 |
raise Exception( 'could not find custom file: %s' % fileName ) |
157 |
|
158 |
def setCustom( self, name, value ): |
159 |
"Set custom parameters for MininetRunner." |
160 |
if name in ( 'topos', 'switches', 'hosts', 'controllers' ): |
161 |
# Update dictionaries |
162 |
param = name.upper() |
163 |
globals()[ param ].update( value ) |
164 |
elif name == 'validate': |
165 |
# Add custom validate function |
166 |
self.validate = value |
167 |
else: |
168 |
# Add or modify global variable or class |
169 |
globals()[ name ] = value |
170 |
|
171 |
def parseArgs( self ): |
172 |
"""Parse command-line args and return options object. |
173 |
returns: opts parse options dict""" |
174 |
|
175 |
desc = ( "The %prog utility creates Mininet network from the\n" |
176 |
"command line. It can create parametrized topologies,\n" |
177 |
"invoke the Mininet CLI, and run tests." ) |
178 |
|
179 |
usage = ( '%prog [options]\n' |
180 |
'(type %prog -h for details)' ) |
181 |
|
182 |
opts = OptionParser( description=desc, usage=usage ) |
183 |
addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' ) |
184 |
addDictOption( opts, HOSTS, HOSTDEF, 'host' ) |
185 |
addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' ) |
186 |
addDictOption( opts, LINKS, LINKDEF, 'link' ) |
187 |
addDictOption( opts, TOPOS, TOPODEF, 'topo' ) |
188 |
|
189 |
opts.add_option( '--clean', '-c', action='store_true', |
190 |
default=False, help='clean and exit' ) |
191 |
opts.add_option( '--custom', action='callback', |
192 |
callback=partial( self.custom, self=self ), |
193 |
type='string', help='read custom classes or params from .py file(s)' ) |
194 |
opts.add_option( '--test', type='choice', choices=TESTS, |
195 |
default=TESTS[ 0 ], |
196 |
help='|'.join( TESTS ) ) |
197 |
opts.add_option( '--xterms', '-x', action='store_true', |
198 |
default=False, help='spawn xterms for each node' ) |
199 |
opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8', |
200 |
help='base IP address for hosts' ) |
201 |
opts.add_option( '--mac', action='store_true', |
202 |
default=False, help='automatically set host MACs' ) |
203 |
opts.add_option( '--arp', action='store_true', |
204 |
default=False, help='set all-pairs ARP entries' ) |
205 |
opts.add_option( '--verbosity', '-v', type='choice', |
206 |
choices=LEVELS.keys(), default = 'info', |
207 |
help = '|'.join( LEVELS.keys() ) ) |
208 |
opts.add_option( '--innamespace', action='store_true', |
209 |
default=False, help='sw and ctrl in namespace?' ) |
210 |
opts.add_option( '--listenport', type='int', default=6634, |
211 |
help='base port for passive switch listening' ) |
212 |
opts.add_option( '--nolistenport', action='store_true', |
213 |
default=False, help="don't use passive listening " + |
214 |
"port") |
215 |
opts.add_option( '--pre', type='string', default=None, |
216 |
help='CLI script to run before tests' ) |
217 |
opts.add_option( '--post', type='string', default=None, |
218 |
help='CLI script to run after tests' ) |
219 |
opts.add_option( '--pin', action='store_true', |
220 |
default=False, help="pin hosts to CPU cores " |
221 |
"(requires --host cfs or --host rt)" ) |
222 |
opts.add_option( '--nat', action='store_true', |
223 |
default=False, help="adds a NAT to the topology " |
224 |
"that connects Mininet to the physical network" ) |
225 |
opts.add_option( '--version', action='callback', callback=version, |
226 |
help='prints the version and exits' ) |
227 |
opts.add_option( '--cluster', type='string', default=None, |
228 |
metavar='server1,server2...', |
229 |
help=( 'run on multiple servers (experimental!)' ) ) |
230 |
opts.add_option( '--placement', type='choice', |
231 |
choices=PLACEMENT.keys(), default='block', |
232 |
metavar='block|random', |
233 |
help=( 'node placement for --cluster ' |
234 |
'(experimental!) ' ) ) |
235 |
|
236 |
self.options, self.args = opts.parse_args() |
237 |
|
238 |
# We don't accept extra arguments after the options |
239 |
if self.args: |
240 |
opts.print_help() |
241 |
exit() |
242 |
|
243 |
def setup( self ): |
244 |
"Setup and validate environment." |
245 |
|
246 |
# set logging verbosity |
247 |
if LEVELS[self.options.verbosity] > LEVELS['output']: |
248 |
print ( '*** WARNING: selected verbosity level (%s) will hide CLI ' |
249 |
'output!\n' |
250 |
'Please restart Mininet with -v [debug, info, output].' |
251 |
% self.options.verbosity ) |
252 |
lg.setLogLevel( self.options.verbosity ) |
253 |
|
254 |
def begin( self ): |
255 |
"Create and run mininet." |
256 |
|
257 |
global CLI |
258 |
|
259 |
if self.options.clean: |
260 |
cleanup() |
261 |
exit() |
262 |
|
263 |
start = time.time() |
264 |
|
265 |
if self.options.controller == 'default': |
266 |
# Update default based on available controllers |
267 |
CONTROLLERS[ 'default' ] = findController() |
268 |
if CONTROLLERS[ 'default' ] is None: |
269 |
if self.options.switch == 'default': |
270 |
# Fall back to OVS Bridge, which does not use an OF controller |
271 |
info( '*** No default OpenFlow controller found for default switch!\n' ) |
272 |
info( '*** Falling back to OVS Bridge\n' ) |
273 |
self.options.switch = 'ovsbr' |
274 |
self.options.controller = 'none' |
275 |
elif self.options.switch in ( 'ovsbr', 'lxbr' ): |
276 |
self.options.controller = 'none' |
277 |
else: |
278 |
raise Exception( "Could not find a default controller for switch %s" % |
279 |
self.options.switch ) |
280 |
|
281 |
topo = buildTopo( TOPOS, self.options.topo ) |
282 |
switch = customConstructor( SWITCHES, self.options.switch ) |
283 |
host = customConstructor( HOSTS, self.options.host ) |
284 |
controller = customConstructor( CONTROLLERS, self.options.controller ) |
285 |
link = customConstructor( LINKS, self.options.link ) |
286 |
|
287 |
if self.validate: |
288 |
self.validate( self.options ) |
289 |
|
290 |
ipBase = self.options.ipbase |
291 |
xterms = self.options.xterms |
292 |
mac = self.options.mac |
293 |
arp = self.options.arp |
294 |
pin = self.options.pin |
295 |
listenPort = None |
296 |
if not self.options.nolistenport: |
297 |
listenPort = self.options.listenport |
298 |
|
299 |
# Handle inNamespace, cluster options |
300 |
inNamespace = self.options.innamespace |
301 |
cluster = self.options.cluster |
302 |
if inNamespace and cluster: |
303 |
print "Please specify --innamespace OR --cluster" |
304 |
exit() |
305 |
Net = MininetWithControlNet if inNamespace else Mininet |
306 |
if cluster: |
307 |
warn( '*** WARNING: Experimental cluster mode!\n' |
308 |
'*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' ) |
309 |
host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink |
310 |
CLI = ClusterCLI |
311 |
Net = partial( MininetCluster, servers=cluster.split( ',' ), |
312 |
placement=PLACEMENT[ self.options.placement ] ) |
313 |
|
314 |
mn = Net( topo=topo, |
315 |
switch=switch, host=host, controller=controller, |
316 |
link=link, |
317 |
ipBase=ipBase, |
318 |
inNamespace=inNamespace, |
319 |
xterms=xterms, autoSetMacs=mac, |
320 |
autoStaticArp=arp, autoPinCpus=pin, |
321 |
listenPort=listenPort ) |
322 |
|
323 |
if self.options.nat: |
324 |
nat = mn.addNAT() |
325 |
nat.configDefault() |
326 |
|
327 |
if self.options.pre: |
328 |
CLI( mn, script=self.options.pre ) |
329 |
|
330 |
test = self.options.test |
331 |
test = ALTSPELLING.get( test, test ) |
332 |
|
333 |
mn.start() |
334 |
|
335 |
if test == 'none': |
336 |
pass |
337 |
elif test == 'all': |
338 |
mn.waitConnected() |
339 |
mn.start() |
340 |
mn.ping() |
341 |
mn.iperf() |
342 |
elif test == 'cli': |
343 |
CLI( mn ) |
344 |
elif test != 'build': |
345 |
mn.waitConnected() |
346 |
getattr( mn, test )() |
347 |
|
348 |
if self.options.post: |
349 |
CLI( mn, script=self.options.post ) |
350 |
|
351 |
mn.stop() |
352 |
|
353 |
elapsed = float( time.time() - start ) |
354 |
info( 'completed in %0.3f seconds\n' % elapsed ) |
355 |
|
356 |
|
357 |
if __name__ == "__main__": |
358 |
try: |
359 |
MininetRunner() |
360 |
except KeyboardInterrupt: |
361 |
info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n") |
362 |
cleanup() |
363 |
except Exception: |
364 |
# Print exception |
365 |
type_, val_, trace_ = sys.exc_info() |
366 |
errorMsg = ( "-"*80 + "\n" + |
367 |
"Caught exception. Cleaning up...\n\n" + |
368 |
"%s: %s\n" % ( type_.__name__, val_ ) + |
369 |
"-"*80 + "\n" ) |
370 |
error( errorMsg ) |
371 |
# Print stack trace to debug log |
372 |
import traceback |
373 |
stackTrace = traceback.format_exc() |
374 |
debug( stackTrace + "\n" ) |
375 |
cleanup() |