Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 779ea5f0

History | View | Annotate | Download (30.5 KB)

1 281f6e59 Bob Lantz
"""
2

3 a6bcad8f Bob Lantz
    Mininet: A simple networking testbed for OpenFlow/SDN!
4 281f6e59 Bob Lantz

5
author: Bob Lantz (rlantz@cs.stanford.edu)
6
author: Brandon Heller (brandonh@stanford.edu)
7 98d4f189 Bob Lantz

8 08cef003 Bob Lantz
Mininet creates scalable OpenFlow test networks by using
9 723d068c Brandon Heller
process-based virtualization and network namespaces.
10 98d4f189 Bob Lantz

11 eddef947 Bob Lantz
Simulated hosts are created as processes in separate network
12
namespaces. This allows a complete OpenFlow network to be simulated on
13
top of a single Linux kernel.
14 98d4f189 Bob Lantz

15
Each host has:
16 7d4b7b7f Bob Lantz

17 281f6e59 Bob Lantz
A virtual console (pipes to a shell)
18
A virtual interfaces (half of a veth pair)
19
A parent shell (and possibly some child processes) in a namespace
20 723d068c Brandon Heller

21 eddef947 Bob Lantz
Hosts have a network interface which is configured via ifconfig/ip
22 8f20b95d Brandon Heller
link/etc.
23 98d4f189 Bob Lantz

24 55dd9368 Bob Lantz
This version supports both the kernel and user space datapaths
25 1bda2d21 Bob Lantz
from the OpenFlow reference implementation (openflowswitch.org)
26
as well as OpenVSwitch (openvswitch.org.)
27 08cef003 Bob Lantz

28 98d4f189 Bob Lantz
In kernel datapath mode, the controller and switches are simply
29
processes in the root namespace.
30

31 281f6e59 Bob Lantz
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
32 55dd9368 Bob Lantz
attached to the one side of a veth pair; the other side resides in the
33
host namespace. In this mode, switch processes can simply connect to the
34 eddef947 Bob Lantz
controller via the loopback interface.
35 98d4f189 Bob Lantz

36 31b43002 Bob Lantz
In user datapath mode, the controller and switches can be full-service
37 eddef947 Bob Lantz
nodes that live in their own network namespaces and have management
38 bbe5f8a3 Bob Lantz
interfaces and IP addresses on a control network (e.g. 192.168.123.1,
39 281f6e59 Bob Lantz
currently routed although it could be bridged.)
40 98d4f189 Bob Lantz

41
In addition to a management interface, user mode switches also have
42 55dd9368 Bob Lantz
several switch interfaces, halves of veth pairs whose other halves
43
reside in the host nodes that the switches are connected to.
44 98d4f189 Bob Lantz

45 31b43002 Bob Lantz
Consistent, straightforward naming is important in order to easily
46
identify hosts, switches and controllers, both from the CLI and
47
from program code. Interfaces are named to make it easy to identify
48
which interfaces belong to which node.
49

50
The basic naming scheme is as follows:
51 281f6e59 Bob Lantz

52 80be5642 Bob Lantz
    Host nodes are named h1-hN
53
    Switch nodes are named s1-sN
54 31b43002 Bob Lantz
    Controller nodes are named c0-cN
55
    Interfaces are named {nodename}-eth0 .. {nodename}-ethN
56

57 80be5642 Bob Lantz
Note: If the network topology is created using mininet.topo, then
58
node numbers are unique among hosts and switches (e.g. we have
59
h1..hN and SN..SN+M) and also correspond to their default IP addresses
60
of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
61
hN. This mapping allows easy determination of a node's IP
62
address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
63

64 bbe5f8a3 Bob Lantz
Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
65
"ping 10.1" is equivalent to "ping 10.0.0.1".
66

67 31b43002 Bob Lantz
Currently we wrap the entire network in a 'mininet' object, which
68
constructs a simulated network based on a network topology created
69 80be5642 Bob Lantz
using a topology object (e.g. LinearTopo) from mininet.topo or
70
mininet.topolib, and a Controller which the switches will connect
71
to. Several configuration options are provided for functions such as
72 31b43002 Bob Lantz
automatically setting MAC addresses, populating the ARP table, or
73 15b482e3 Brandon Heller
even running a set of terminals to allow direct interaction with nodes.
74 31b43002 Bob Lantz

75 80be5642 Bob Lantz
After the network is created, it can be started using start(), and a
76
variety of useful tasks maybe performed, including basic connectivity
77
and bandwidth tests and running the mininet CLI.
78 31b43002 Bob Lantz

79
Once the network is up and running, test code can easily get access
80 80be5642 Bob Lantz
to host and switch objects which can then be used for arbitrary
81
experiments, typically involving running a series of commands on the
82
hosts.
83 31b43002 Bob Lantz

84
After all desired tests or activities have been completed, the stop()
85
method may be called to shut down the network.
86 281f6e59 Bob Lantz

87
"""
88
89 8b5062a3 Brandon Heller
import os
90
import re
91 ec7b211c Bob Lantz
import select
92 8a034f4f Brandon Heller
import signal
93 98d4f189 Bob Lantz
from time import sleep
94 876e66e5 Rich Lane
from itertools import chain, groupby
95 98d4f189 Bob Lantz
96 496b5f9e Bob Lantz
from mininet.cli import CLI
97 cdeaca86 Brandon Heller
from mininet.log import info, error, debug, output
98 72fd120d Cody Burkard
from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller
99 e8146dd1 Bob Lantz
from mininet.link import Link, Intf
100 bcfb3009 Brandon Heller
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
101 5a8bb489 Bob Lantz
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
102 15b482e3 Brandon Heller
from mininet.term import cleanUpScreens, makeTerms
103 8b5062a3 Brandon Heller
104 39128f8c Bob Lantz
# Mininet version: should be consistent with README and LICENSE
105 96952b92 Bob Lantz
VERSION = "2.1.0+"
106 39128f8c Bob Lantz
107 80a8fa62 Bob Lantz
class Mininet( object ):
108
    "Network emulation with hosts spawned in network namespaces."
109
110 eaf5888a Bob Lantz
    def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
111 a19cc915 Cody Burkard
                  controller=DefaultController, link=Link, intf=Intf,
112 edf60032 Brandon Heller
                  build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
113
                  inNamespace=False,
114
                  autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
115
                  listenPort=None ):
116 80a8fa62 Bob Lantz
        """Create Mininet object.
117 019bff82 Bob Lantz
           topo: Topo (topology) object or None
118 a6bcad8f Bob Lantz
           switch: default Switch class
119
           host: default Host class/constructor
120
           controller: default Controller class/constructor
121
           link: default Link class/constructor
122 216a4b7c Bob Lantz
           intf: default Intf class/constructor
123 84a91a14 Bob Lantz
           ipBase: base IP address for hosts,
124 956bf4f2 Brandon Heller
           build: build now from topo?
125 80a8fa62 Bob Lantz
           xterms: if build now, spawn xterms?
126
           cleanup: if build now, cleanup before creating?
127
           inNamespace: spawn switches and controller in net namespaces?
128 197b083f Bob Lantz
           autoSetMacs: set MAC addrs automatically like IP addresses?
129 ccca871a Brandon Heller
           autoStaticArp: set all-pairs static MAC addrs?
130 197b083f Bob Lantz
           autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
131 ccca871a Brandon Heller
           listenPort: base listening port to open; will be incremented for
132
               each additional switch in the net if inNamespace=False"""
133 216a4b7c Bob Lantz
        self.topo = topo
134 8b5062a3 Brandon Heller
        self.switch = switch
135
        self.host = host
136
        self.controller = controller
137 a6bcad8f Bob Lantz
        self.link = link
138 216a4b7c Bob Lantz
        self.intf = intf
139 82f483f5 Bob Lantz
        self.ipBase = ipBase
140 5a8bb489 Bob Lantz
        self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
141
        self.nextIP = 1  # start for address allocation
142 80a8fa62 Bob Lantz
        self.inNamespace = inNamespace
143 376bcba4 Brandon Heller
        self.xterms = xterms
144
        self.cleanup = cleanup
145 80a8fa62 Bob Lantz
        self.autoSetMacs = autoSetMacs
146
        self.autoStaticArp = autoStaticArp
147 197b083f Bob Lantz
        self.autoPinCpus = autoPinCpus
148
        self.numCores = numCores()
149
        self.nextCore = 0  # next core for pinning hosts to CPUs
150 ccca871a Brandon Heller
        self.listenPort = listenPort
151 8b5062a3 Brandon Heller
152 019bff82 Bob Lantz
        self.hosts = []
153
        self.switches = []
154
        self.controllers = []
155 84a91a14 Bob Lantz
156 cf6f6704 Bob Lantz
        self.nameToNode = {}  # name to Node (Host/Switch) objects
157 84a91a14 Bob Lantz
158 cf6f6704 Bob Lantz
        self.terms = []  # list of spawned xterm processes
159 8a034f4f Brandon Heller
160 8e3699ec Bob Lantz
        Mininet.init()  # Initialize Mininet if necessary
161 b055728f Brandon Heller
162 99c035d9 Bob Lantz
        self.built = False
163
        if topo and build:
164 d44a5843 Bob Lantz
            self.build()
165 8b5062a3 Brandon Heller
166 5a8bb489 Bob Lantz
    def addHost( self, name, cls=None, **params ):
167 80a8fa62 Bob Lantz
        """Add host.
168 019bff82 Bob Lantz
           name: name of host to add
169 5a8bb489 Bob Lantz
           cls: custom host class/constructor (optional)
170 84a91a14 Bob Lantz
           params: parameters for host
171 019bff82 Bob Lantz
           returns: added host"""
172 5a8bb489 Bob Lantz
        # Default IP and MAC addresses
173
        defaults = { 'ip': ipAdd( self.nextIP,
174
                                  ipBaseNum=self.ipBaseNum,
175 0f832c92 Bob Lantz
                                  prefixLen=self.prefixLen ) +
176
                                  '/%s' % self.prefixLen }
177 5a8bb489 Bob Lantz
        if self.autoSetMacs:
178
            defaults[ 'mac'] = macColonHex( self.nextIP )
179 197b083f Bob Lantz
        if self.autoPinCpus:
180
            defaults[ 'cores' ] = self.nextCore
181
            self.nextCore = ( self.nextCore + 1 ) % self.numCores
182 5a8bb489 Bob Lantz
        self.nextIP += 1
183
        defaults.update( params )
184
        if not cls:
185 9005ce32 Bob Lantz
            cls = self.host
186 5a8bb489 Bob Lantz
        h = cls( name, **defaults )
187 a6bcad8f Bob Lantz
        self.hosts.append( h )
188
        self.nameToNode[ name ] = h
189
        return h
190
191 5a8bb489 Bob Lantz
    def addSwitch( self, name, cls=None, **params ):
192 80a8fa62 Bob Lantz
        """Add switch.
193 019bff82 Bob Lantz
           name: name of switch to add
194 5a8bb489 Bob Lantz
           cls: custom switch class/constructor (optional)
195 ccca871a Brandon Heller
           returns: added switch
196 84a91a14 Bob Lantz
           side effect: increments listenPort ivar ."""
197 14ff3ad3 Bob Lantz
        defaults = { 'listenPort': self.listenPort,
198 a6bcad8f Bob Lantz
                     'inNamespace': self.inNamespace }
199 84a91a14 Bob Lantz
        defaults.update( params )
200 5a8bb489 Bob Lantz
        if not cls:
201
            cls = self.switch
202
        sw = cls( name, **defaults )
203 0a9358c9 Brandon Heller
        if not self.inNamespace and self.listenPort:
204 ccca871a Brandon Heller
            self.listenPort += 1
205 019bff82 Bob Lantz
        self.switches.append( sw )
206
        self.nameToNode[ name ] = sw
207
        return sw
208 80a8fa62 Bob Lantz
209 a6bcad8f Bob Lantz
    def addController( self, name='c0', controller=None, **params ):
210 80a8fa62 Bob Lantz
        """Add controller.
211 e30f2c99 Bob Lantz
           controller: Controller class"""
212 35029978 Bob Lantz
        # Get controller class
213 e30f2c99 Bob Lantz
        if not controller:
214
            controller = self.controller
215 35029978 Bob Lantz
        # Construct new controller if one is not given
216 72fd120d Cody Burkard
        if isinstance( name, Controller ):
217 15146d90 Brian O'Connor
            controller_new = name
218 35029978 Bob Lantz
            # Pylint thinks controller is a str()
219
            # pylint: disable=E1103
220 15146d90 Brian O'Connor
            name = controller_new.name
221 35029978 Bob Lantz
            # pylint: enable=E1103
222 15146d90 Brian O'Connor
        else:
223
            controller_new = controller( name, **params )
224 35029978 Bob Lantz
        # Add new controller to net
225 72fd120d Cody Burkard
        if controller_new: # allow controller-less setups
226 f32a5468 Brandon Heller
            self.controllers.append( controller_new )
227 82b72072 Bob Lantz
            self.nameToNode[ name ] = controller_new
228 eaf5888a Bob Lantz
        return controller_new
229 e8146dd1 Bob Lantz
230 bd558875 Bob Lantz
    # BL: We now have four ways to look up nodes
231
    # This may (should?) be cleaned up in the future.
232 548580d8 Bob Lantz
    def getNodeByName( self, *args ):
233
        "Return node(s) with given name(s)"
234
        if len( args ) == 1:
235
            return self.nameToNode[ args[ 0 ] ]
236
        return [ self.nameToNode[ n ] for n in args ]
237 8b5062a3 Brandon Heller
238 089e8130 Bob Lantz
    def get( self, *args ):
239
        "Convenience alias for getNodeByName"
240
        return self.getNodeByName( *args )
241
242 bd558875 Bob Lantz
    # Even more convenient syntax for node lookup and iteration
243 15146d90 Brian O'Connor
    def __getitem__( self, key ):
244 bd558875 Bob Lantz
        """net [ name ] operator: Return node(s) with given name(s)"""
245 15146d90 Brian O'Connor
        return self.nameToNode[ key ]
246 bd558875 Bob Lantz
247
    def __iter__( self ):
248 09b12391 Brian O'Connor
        "return iterator over node names"
249 9281719d Brian O'Connor
        for node in chain( self.hosts, self.switches, self.controllers ):
250
            yield node.name
251 bd558875 Bob Lantz
252 8e04a9f8 Brian O'Connor
    def __len__( self ):
253 9281719d Brian O'Connor
        "returns number of nodes in net"
254 35029978 Bob Lantz
        return ( len( self.hosts ) + len( self.switches ) +
255
                 len( self.controllers ) )
256 8e04a9f8 Brian O'Connor
257
    def __contains__( self, item ):
258 9281719d Brian O'Connor
        "returns True if net contains named node"
259 15146d90 Brian O'Connor
        return item in self.nameToNode
260 8e04a9f8 Brian O'Connor
261
    def keys( self ):
262 9281719d Brian O'Connor
        "return a list of all node names or net's keys"
263 15146d90 Brian O'Connor
        return list( self )
264 8e04a9f8 Brian O'Connor
265
    def values( self ):
266 9281719d Brian O'Connor
        "return a list of all nodes or net's values"
267 15146d90 Brian O'Connor
        return [ self[name] for name in self ]
268 8e04a9f8 Brian O'Connor
269
    def items( self ):
270 9281719d Brian O'Connor
        "return (key,value) tuple list for every node in net"
271 8e04a9f8 Brian O'Connor
        return zip( self.keys(), self.values() )
272
273 e8146dd1 Bob Lantz
    def addLink( self, node1, node2, port1=None, port2=None,
274 5a8bb489 Bob Lantz
                 cls=None, **params ):
275 e8146dd1 Bob Lantz
        """"Add a link from node1 to node2
276
            node1: source node
277
            node2: dest node
278
            port1: source port
279
            port2: dest port
280
            returns: link object"""
281
        defaults = { 'port1': port1,
282
                     'port2': port2,
283
                     'intf': self.intf }
284
        defaults.update( params )
285 5a8bb489 Bob Lantz
        if not cls:
286
            cls = self.link
287 e8146dd1 Bob Lantz
        return cls( node1, node2, **defaults )
288 5a8bb489 Bob Lantz
289 80be5642 Bob Lantz
    def configHosts( self ):
290 80a8fa62 Bob Lantz
        "Configure a set of hosts."
291 019bff82 Bob Lantz
        for host in self.hosts:
292 216a4b7c Bob Lantz
            info( host.name + ' ' )
293 e1ca7196 Bob Lantz
            intf = host.defaultIntf()
294
            if intf:
295 dd21df3c Bob Lantz
                host.configDefault()
296 e1ca7196 Bob Lantz
            else:
297
                # Don't configure nonexistent intf
298
                host.configDefault( ip=None, mac=None )
299 8b5062a3 Brandon Heller
            # You're low priority, dude!
300 84a91a14 Bob Lantz
            # BL: do we want to do this here or not?
301
            # May not make sense if we have CPU lmiting...
302
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
303 a9c28885 Bob Lantz
            # This may not be the right place to do this, but
304
            # it needs to be done somewhere.
305
            host.cmd( 'ifconfig lo up' )
306 d5886525 Bob Lantz
        info( '\n' )
307 80a8fa62 Bob Lantz
308 84a91a14 Bob Lantz
    def buildFromTopo( self, topo=None ):
309 019bff82 Bob Lantz
        """Build mininet from a topology object
310 80a8fa62 Bob Lantz
           At the end of this function, everything should be connected
311
           and up."""
312 d44a5843 Bob Lantz
313
        # Possibly we should clean up here and/or validate
314
        # the topo
315 376bcba4 Brandon Heller
        if self.cleanup:
316 d44a5843 Bob Lantz
            pass
317
318 d5886525 Bob Lantz
        info( '*** Creating network\n' )
319 84a91a14 Bob Lantz
320 15146d90 Brian O'Connor
        if not self.controllers and self.controller:
321 84a91a14 Bob Lantz
            # Add a default controller
322
            info( '*** Adding controller\n' )
323 f58f83c0 Bob Lantz
            classes = self.controller
324
            if type( classes ) is not list:
325
                classes = [ classes ]
326
            for i, cls in enumerate( classes ):
327
                self.addController( 'c%d' % i, cls )
328 84a91a14 Bob Lantz
329 d5886525 Bob Lantz
        info( '*** Adding hosts:\n' )
330 5a8bb489 Bob Lantz
        for hostName in topo.hosts():
331
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
332
            info( hostName + ' ' )
333 84a91a14 Bob Lantz
334 d5886525 Bob Lantz
        info( '\n*** Adding switches:\n' )
335 5a8bb489 Bob Lantz
        for switchName in topo.switches():
336
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
337
            info( switchName + ' ' )
338 84a91a14 Bob Lantz
339 724f1144 Bob Lantz
        info( '\n*** Adding links:\n' )
340 5a8bb489 Bob Lantz
        for srcName, dstName in topo.links(sort=True):
341
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
342 e8146dd1 Bob Lantz
            params = topo.linkInfo( srcName, dstName )
343 5a8bb489 Bob Lantz
            srcPort, dstPort = topo.port( srcName, dstName )
344 e1246c37 Bob Lantz
            self.addLink( src, dst, srcPort, dstPort, **params )
345 5a8bb489 Bob Lantz
            info( '(%s, %s) ' % ( src.name, dst.name ) )
346 84a91a14 Bob Lantz
347 d5886525 Bob Lantz
        info( '\n' )
348 80a8fa62 Bob Lantz
349 84a91a14 Bob Lantz
    def configureControlNetwork( self ):
350 14ff3ad3 Bob Lantz
        "Control net config hook: override in subclass"
351
        raise Exception( 'configureControlNetwork: '
352 edf60032 Brandon Heller
                         'should be overriden in subclass', self )
353 84a91a14 Bob Lantz
354 d44a5843 Bob Lantz
    def build( self ):
355
        "Build mininet."
356
        if self.topo:
357
            self.buildFromTopo( self.topo )
358 824afb84 Rémy Léone
        if self.inNamespace:
359 d44a5843 Bob Lantz
            self.configureControlNetwork()
360 d5886525 Bob Lantz
        info( '*** Configuring hosts\n' )
361 80be5642 Bob Lantz
        self.configHosts()
362 376bcba4 Brandon Heller
        if self.xterms:
363 15b482e3 Brandon Heller
            self.startTerms()
364 80a8fa62 Bob Lantz
        if self.autoStaticArp:
365
            self.staticArp()
366 99c035d9 Bob Lantz
        self.built = True
367 80a8fa62 Bob Lantz
368 15b482e3 Brandon Heller
    def startTerms( self ):
369
        "Start a terminal for each node."
370 4316be95 Brian O'Connor
        if 'DISPLAY' not in os.environ:
371
            error( "Error starting terms: Cannot connect to display\n" )
372
            return
373 15b482e3 Brandon Heller
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
374 8a034f4f Brandon Heller
        cleanUpScreens()
375 15b482e3 Brandon Heller
        self.terms += makeTerms( self.controllers, 'controller' )
376
        self.terms += makeTerms( self.switches, 'switch' )
377
        self.terms += makeTerms( self.hosts, 'host' )
378 8a034f4f Brandon Heller
379 80a8fa62 Bob Lantz
    def stopXterms( self ):
380
        "Kill each xterm."
381 8a034f4f Brandon Heller
        for term in self.terms:
382 80a8fa62 Bob Lantz
            os.kill( term.pid, signal.SIGKILL )
383 8a034f4f Brandon Heller
        cleanUpScreens()
384 8b5062a3 Brandon Heller
385 80a8fa62 Bob Lantz
    def staticArp( self ):
386
        "Add all-pairs ARP entries to remove the need to handle broadcast."
387 019bff82 Bob Lantz
        for src in self.hosts:
388
            for dst in self.hosts:
389 376bcba4 Brandon Heller
                if src != dst:
390 1bda2d21 Bob Lantz
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
391 376bcba4 Brandon Heller
392 80a8fa62 Bob Lantz
    def start( self ):
393 99c035d9 Bob Lantz
        "Start controller and switches."
394
        if not self.built:
395
            self.build()
396 d5886525 Bob Lantz
        info( '*** Starting controller\n' )
397 019bff82 Bob Lantz
        for controller in self.controllers:
398
            controller.start()
399
        info( '*** Starting %s switches\n' % len( self.switches ) )
400
        for switch in self.switches:
401 efc9a01c Bob Lantz
            info( switch.name + ' ')
402 80a8fa62 Bob Lantz
            switch.start( self.controllers )
403 d5886525 Bob Lantz
        info( '\n' )
404 80a8fa62 Bob Lantz
405
    def stop( self ):
406 d5886525 Bob Lantz
        "Stop the controller(s), switches and hosts"
407 8a034f4f Brandon Heller
        if self.terms:
408 d5886525 Bob Lantz
            info( '*** Stopping %i terms\n' % len( self.terms ) )
409 80a8fa62 Bob Lantz
            self.stopXterms()
410 019bff82 Bob Lantz
        info( '*** Stopping %i switches\n' % len( self.switches ) )
411 876e66e5 Rich Lane
        for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
412 a0bc1002 Bob Lantz
            if hasattr( swclass, 'batchShutdown' ):
413 876e66e5 Rich Lane
                swclass.batchShutdown( switches )
414 019bff82 Bob Lantz
        for switch in self.switches:
415 84a91a14 Bob Lantz
            info( switch.name + ' ' )
416 8b5062a3 Brandon Heller
            switch.stop()
417 d5886525 Bob Lantz
        info( '\n' )
418 10be691b Bob Lantz
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
419
        for host in self.hosts:
420
            info( host.name + ' ' )
421
            host.terminate()
422
        info( '\n' )
423 019bff82 Bob Lantz
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
424
        for controller in self.controllers:
425 84a91a14 Bob Lantz
            info( controller.name + ' ' )
426 019bff82 Bob Lantz
            controller.stop()
427 84a91a14 Bob Lantz
        info( '\n*** Done\n' )
428 8b5062a3 Brandon Heller
429 67516aa4 Bob Lantz
    def run( self, test, *args, **kwargs ):
430 80a8fa62 Bob Lantz
        "Perform a complete start/test/stop cycle."
431 8b5062a3 Brandon Heller
        self.start()
432 d5886525 Bob Lantz
        info( '*** Running test\n' )
433 67516aa4 Bob Lantz
        result = test( *args, **kwargs )
434 8b5062a3 Brandon Heller
        self.stop()
435
        return result
436
437 259d7133 Bob Lantz
    def monitor( self, hosts=None, timeoutms=-1 ):
438 ec7b211c Bob Lantz
        """Monitor a set of hosts (or all hosts by default),
439
           and return their output, a line at a time.
440 259d7133 Bob Lantz
           hosts: (optional) set of hosts to monitor
441
           timeoutms: (optional) timeout value in ms
442
           returns: iterator which returns host, line"""
443 ec7b211c Bob Lantz
        if hosts is None:
444
            hosts = self.hosts
445
        poller = select.poll()
446 cf6f6704 Bob Lantz
        Node = hosts[ 0 ]  # so we can call class method fdToNode
447 ec7b211c Bob Lantz
        for host in hosts:
448
            poller.register( host.stdout )
449
        while True:
450 259d7133 Bob Lantz
            ready = poller.poll( timeoutms )
451 ec7b211c Bob Lantz
            for fd, event in ready:
452
                host = Node.fdToNode( fd )
453
                if event & select.POLLIN:
454
                    line = host.readline()
455 259d7133 Bob Lantz
                    if line is not None:
456 ec7b211c Bob Lantz
                        yield host, line
457 259d7133 Bob Lantz
            # Return if non-blocking
458
            if not ready and timeoutms >= 0:
459
                yield None, None
460 ec7b211c Bob Lantz
461 84a91a14 Bob Lantz
    # XXX These test methods should be moved out of this class.
462
    # Probably we should create a tests.py for them
463
464 8b5062a3 Brandon Heller
    @staticmethod
465 80a8fa62 Bob Lantz
    def _parsePing( pingOutput ):
466
        "Parse ping output and return packets sent, received."
467 3fac5a43 Brandon Heller
        # Check for downed link
468
        if 'connect: Network is unreachable' in pingOutput:
469 824afb84 Rémy Léone
            return 1, 0
470 8b5062a3 Brandon Heller
        r = r'(\d+) packets transmitted, (\d+) received'
471 80a8fa62 Bob Lantz
        m = re.search( r, pingOutput )
472 7a506047 Brandon Heller
        if m is None:
473 d5886525 Bob Lantz
            error( '*** Error: could not parse ping output: %s\n' %
474 2e089b5e Brandon Heller
                   pingOutput )
475 824afb84 Rémy Léone
            return 1, 0
476 80a8fa62 Bob Lantz
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
477 8b5062a3 Brandon Heller
        return sent, received
478
479 1f1d590c Brandon Heller
    def ping( self, hosts=None, timeout=None ):
480 80a8fa62 Bob Lantz
        """Ping between all specified hosts.
481 019bff82 Bob Lantz
           hosts: list of hosts
482 1f1d590c Brandon Heller
           timeout: time to wait for a response, as string
483 80a8fa62 Bob Lantz
           returns: ploss packet loss percentage"""
484 d44a5843 Bob Lantz
        # should we check if running?
485 8b5062a3 Brandon Heller
        packets = 0
486
        lost = 0
487
        ploss = None
488
        if not hosts:
489 019bff82 Bob Lantz
            hosts = self.hosts
490 cdeaca86 Brandon Heller
            output( '*** Ping: testing ping reachability\n' )
491 019bff82 Bob Lantz
        for node in hosts:
492 cdeaca86 Brandon Heller
            output( '%s -> ' % node.name )
493 019bff82 Bob Lantz
            for dest in hosts:
494 8b5062a3 Brandon Heller
                if node != dest:
495 1f1d590c Brandon Heller
                    opts = ''
496
                    if timeout:
497
                        opts = '-W %s' % timeout
498
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
499 80a8fa62 Bob Lantz
                    sent, received = self._parsePing( result )
500 8b5062a3 Brandon Heller
                    packets += sent
501
                    if received > sent:
502 d5886525 Bob Lantz
                        error( '*** Error: received too many packets' )
503
                        error( '%s' % result )
504 80a8fa62 Bob Lantz
                        node.cmdPrint( 'route' )
505
                        exit( 1 )
506 8b5062a3 Brandon Heller
                    lost += sent - received
507 cdeaca86 Brandon Heller
                    output( ( '%s ' % dest.name ) if received else 'X ' )
508
            output( '\n' )
509 c188bee3 Brian O'Connor
        if packets > 0:
510 92a28881 lantz
            ploss = 100.0 * lost / packets
511 fec98e27 Brian O'Connor
            received = packets - lost
512
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
513
                    ( ploss, received, packets ) )
514 c188bee3 Brian O'Connor
        else:
515
            ploss = 0
516
            output( "*** Warning: No packets sent\n" )
517 8b5062a3 Brandon Heller
        return ploss
518
519 1f1d590c Brandon Heller
    @staticmethod
520
    def _parsePingFull( pingOutput ):
521
        "Parse ping output and return all data."
522 1ecc63df Brian O'Connor
        errorTuple = (1, 0, 0, 0, 0, 0)
523 1f1d590c Brandon Heller
        # Check for downed link
524 1ecc63df Brian O'Connor
        r = r'[uU]nreachable'
525
        m = re.search( r, pingOutput )
526
        if m is not None:
527
            return errorTuple
528 1f1d590c Brandon Heller
        r = r'(\d+) packets transmitted, (\d+) received'
529
        m = re.search( r, pingOutput )
530
        if m is None:
531
            error( '*** Error: could not parse ping output: %s\n' %
532
                   pingOutput )
533 1ecc63df Brian O'Connor
            return errorTuple
534 1f1d590c Brandon Heller
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
535
        r = r'rtt min/avg/max/mdev = '
536
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
537
        m = re.search( r, pingOutput )
538 1ecc63df Brian O'Connor
        if m is None:
539
            error( '*** Error: could not parse ping output: %s\n' %
540
                   pingOutput )
541
            return errorTuple
542 1f1d590c Brandon Heller
        rttmin = float( m.group( 1 ) )
543
        rttavg = float( m.group( 2 ) )
544
        rttmax = float( m.group( 3 ) )
545
        rttdev = float( m.group( 4 ) )
546
        return sent, received, rttmin, rttavg, rttmax, rttdev
547
548
    def pingFull( self, hosts=None, timeout=None ):
549
        """Ping between all specified hosts and return all data.
550
           hosts: list of hosts
551
           timeout: time to wait for a response, as string
552
           returns: all ping data; see function body."""
553
        # should we check if running?
554
        # Each value is a tuple: (src, dsd, [all ping outputs])
555
        all_outputs = []
556
        if not hosts:
557
            hosts = self.hosts
558
            output( '*** Ping: testing ping reachability\n' )
559
        for node in hosts:
560
            output( '%s -> ' % node.name )
561
            for dest in hosts:
562
                if node != dest:
563
                    opts = ''
564
                    if timeout:
565
                        opts = '-W %s' % timeout
566
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
567
                    outputs = self._parsePingFull( result )
568
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
569
                    all_outputs.append( (node, dest, outputs) )
570
                    output( ( '%s ' % dest.name ) if received else 'X ' )
571
            output( '\n' )
572
        output( "*** Results: \n" )
573
        for outputs in all_outputs:
574
            src, dest, ping_outputs = outputs
575
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
576
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
577
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
578
                    (rttmin, rttavg, rttmax, rttdev) )
579
        return all_outputs
580
581 4d1a9cdc Jon Hall
    def pingAll( self, timeout=None ):
582 80a8fa62 Bob Lantz
        """Ping between all hosts.
583
           returns: ploss packet loss percentage"""
584 4d1a9cdc Jon Hall
        return self.ping( timeout=timeout )
585 eeb9cb3c Brandon Heller
586 80a8fa62 Bob Lantz
    def pingPair( self ):
587
        """Ping between first two hosts, useful for testing.
588
           returns: ploss packet loss percentage"""
589 019bff82 Bob Lantz
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
590 80a8fa62 Bob Lantz
        return self.ping( hosts=hosts )
591 eeb9cb3c Brandon Heller
592 1f1d590c Brandon Heller
    def pingAllFull( self ):
593
        """Ping between all hosts.
594
           returns: ploss packet loss percentage"""
595
        return self.pingFull()
596
597
    def pingPairFull( self ):
598
        """Ping between first two hosts, useful for testing.
599
           returns: ploss packet loss percentage"""
600
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
601
        return self.pingFull( hosts=hosts )
602
603 eeb9cb3c Brandon Heller
    @staticmethod
604 80a8fa62 Bob Lantz
    def _parseIperf( iperfOutput ):
605
        """Parse iperf output and return bandwidth.
606
           iperfOutput: string
607
           returns: result string"""
608 eeb9cb3c Brandon Heller
        r = r'([\d\.]+ \w+/sec)'
609 ad2fda25 Bob Lantz
        m = re.findall( r, iperfOutput )
610 eeb9cb3c Brandon Heller
        if m:
611 ad2fda25 Bob Lantz
            return m[-1]
612 eeb9cb3c Brandon Heller
        else:
613 ad2fda25 Bob Lantz
            # was: raise Exception(...)
614
            error( 'could not parse iperf output: ' + iperfOutput )
615
            return ''
616 80a8fa62 Bob Lantz
617 216a4b7c Bob Lantz
    # XXX This should be cleaned up
618
619 1a40cd04 Brandon Heller
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
620 80a8fa62 Bob Lantz
        """Run iperf between two hosts.
621 019bff82 Bob Lantz
           hosts: list of hosts; if None, uses opposite hosts
622 80a8fa62 Bob Lantz
           l4Type: string, one of [ TCP, UDP ]
623
           returns: results two-element array of server and client speeds"""
624 ad2fda25 Bob Lantz
        if not quietRun( 'which telnet' ):
625
            error( 'Cannot find telnet in $PATH - required for iperf test' )
626
            return
627 eeb9cb3c Brandon Heller
        if not hosts:
628 019bff82 Bob Lantz
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
629 eeb9cb3c Brandon Heller
        else:
630 80a8fa62 Bob Lantz
            assert len( hosts ) == 2
631 ec7b211c Bob Lantz
        client, server = hosts
632 cdeaca86 Brandon Heller
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
633 ec7b211c Bob Lantz
        output( "%s and %s\n" % ( client.name, server.name ) )
634
        server.cmd( 'killall -9 iperf' )
635 80a8fa62 Bob Lantz
        iperfArgs = 'iperf '
636
        bwArgs = ''
637
        if l4Type == 'UDP':
638
            iperfArgs += '-u '
639
            bwArgs = '-b ' + udpBw + ' '
640
        elif l4Type != 'TCP':
641
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
642 ec7b211c Bob Lantz
        server.sendCmd( iperfArgs + '-s', printPid=True )
643
        servout = ''
644
        while server.lastPid is None:
645
            servout += server.monitor()
646 8856d284 Bob Lantz
        if l4Type == 'TCP':
647
            while 'Connected' not in client.cmd(
648 615ebb7a Brandon Heller
                    'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
649 8856d284 Bob Lantz
                output('waiting for iperf to start up...')
650
                sleep(.5)
651 ec7b211c Bob Lantz
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
652 edf60032 Brandon Heller
                             bwArgs )
653 ec7b211c Bob Lantz
        debug( 'Client output: %s\n' % cliout )
654
        server.sendInt()
655
        servout += server.waitOutput()
656
        debug( 'Server output: %s\n' % servout )
657
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
658 80a8fa62 Bob Lantz
        if l4Type == 'UDP':
659
            result.insert( 0, udpBw )
660 cdeaca86 Brandon Heller
        output( '*** Results: %s\n' % result )
661 eeb9cb3c Brandon Heller
        return result
662
663 fcd01592 Brandon Heller
    def runCpuLimitTest( self, cpu, duration=5 ):
664
        """run CPU limit test with 'while true' processes.
665
        cpu: desired CPU fraction of each host
666
        duration: test duration in seconds
667
        returns a single list of measured CPU fractions as floats.
668
        """
669
        pct = cpu * 100
670
        info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
671
        hosts = self.hosts
672
        for h in hosts:
673
            h.cmd( 'while true; do a=1; done &' )
674
        pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
675
        pids_str = ",".join(["%s" % pid for pid in pids])
676
        cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
677
        # It's a shame that this is what pylint prefers
678
        outputs = []
679
        for _ in range( duration ):
680
            sleep( 1 )
681
            outputs.append( quietRun( cmd ).strip() )
682
        for h in hosts:
683
            h.cmd( 'kill %1' )
684
        cpu_fractions = []
685
        for test_output in outputs:
686
            # Split by line.  Ignore first line, which looks like this:
687
            # PID %CPU COMMAND\n
688
            for line in test_output.split('\n')[1:]:
689 03c3123b Brandon Heller
                r = r'\d+\s*(\d+\.\d+)'
690 fcd01592 Brandon Heller
                m = re.search( r, line )
691
                if m is None:
692
                    error( '*** Error: could not extract CPU fraction: %s\n' %
693
                           line )
694
                    return None
695
                cpu_fractions.append( float( m.group( 1 ) ) )
696
        output( '*** Results: %s\n' % cpu_fractions )
697
        return cpu_fractions
698
699 84a91a14 Bob Lantz
    # BL: I think this can be rewritten now that we have
700
    # a real link class.
701 c70aab0a Bob Lantz
    def configLinkStatus( self, src, dst, status ):
702
        """Change status of src <-> dst links.
703
           src: node name
704
           dst: node name
705
           status: string {up, down}"""
706 8d3c2859 Brandon Heller
        if src not in self.nameToNode:
707
            error( 'src not in network: %s\n' % src )
708
        elif dst not in self.nameToNode:
709
            error( 'dst not in network: %s\n' % dst )
710
        else:
711 8856d284 Bob Lantz
            if type( src ) is str:
712
                src = self.nameToNode[ src ]
713
            if type( dst ) is str:
714
                dst = self.nameToNode[ dst ]
715
            connections = src.connectionsTo( dst )
716 fb2f6523 Bob Lantz
            if len( connections ) == 0:
717 8d3c2859 Brandon Heller
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
718 fb2f6523 Bob Lantz
            for srcIntf, dstIntf in connections:
719 8856d284 Bob Lantz
                result = srcIntf.ifconfig( status )
720 8d3c2859 Brandon Heller
                if result:
721
                    error( 'link src status change failed: %s\n' % result )
722 8856d284 Bob Lantz
                result = dstIntf.ifconfig( status )
723 8d3c2859 Brandon Heller
                if result:
724
                    error( 'link dst status change failed: %s\n' % result )
725
726 80a8fa62 Bob Lantz
    def interact( self ):
727
        "Start network and run our simple CLI."
728 eeb9cb3c Brandon Heller
        self.start()
729 496b5f9e Bob Lantz
        result = CLI( self )
730 eeb9cb3c Brandon Heller
        self.stop()
731
        return result
732 82b72072 Bob Lantz
733 8e3699ec Bob Lantz
    inited = False
734 14ff3ad3 Bob Lantz
735 8e3699ec Bob Lantz
    @classmethod
736
    def init( cls ):
737
        "Initialize Mininet"
738
        if cls.inited:
739
            return
740 bcfb3009 Brandon Heller
        ensureRoot()
741 8e3699ec Bob Lantz
        fixLimits()
742
        cls.inited = True
743
744 82b72072 Bob Lantz
745 84a91a14 Bob Lantz
class MininetWithControlNet( Mininet ):
746
747
    """Control network support:
748

749
       Create an explicit control network. Currently this is only
750
       used/usable with the user datapath.
751

752
       Notes:
753

754
       1. If the controller and switches are in the same (e.g. root)
755
          namespace, they can just use the loopback connection.
756

757
       2. If we can get unix domain sockets to work, we can use them
758
          instead of an explicit control network.
759

760
       3. Instead of routing, we could bridge or use 'in-band' control.
761

762
       4. Even if we dispense with this in general, it could still be
763
          useful for people who wish to simulate a separate control
764
          network (since real networks may need one!)
765

766
       5. Basically nobody ever used this code, so it has been moved
767 14ff3ad3 Bob Lantz
          into its own class.
768

769
       6. Ultimately we may wish to extend this to allow us to create a
770
          control network which every node's control interface is
771
          attached to."""
772 84a91a14 Bob Lantz
773
    def configureControlNetwork( self ):
774
        "Configure control network."
775
        self.configureRoutedControlNetwork()
776
777
    # We still need to figure out the right way to pass
778
    # in the control network location.
779
780
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
781 edf60032 Brandon Heller
                                       prefixLen=16 ):
782 84a91a14 Bob Lantz
        """Configure a routed control network on controller and switches.
783
           For use with the user datapath only right now."""
784
        controller = self.controllers[ 0 ]
785
        info( controller.name + ' <->' )
786
        cip = ip
787
        snum = ipParse( ip )
788
        for switch in self.switches:
789
            info( ' ' + switch.name )
790 14ff3ad3 Bob Lantz
            link = self.link( switch, controller, port1=0 )
791
            sintf, cintf = link.intf1, link.intf2
792
            switch.controlIntf = sintf
793 84a91a14 Bob Lantz
            snum += 1
794
            while snum & 0xff in [ 0, 255 ]:
795
                snum += 1
796
            sip = ipStr( snum )
797 14ff3ad3 Bob Lantz
            cintf.setIP( cip, prefixLen )
798
            sintf.setIP( sip, prefixLen )
799 84a91a14 Bob Lantz
            controller.setHostRoute( sip, cintf )
800
            switch.setHostRoute( cip, sintf )
801
        info( '\n' )
802
        info( '*** Testing control network\n' )
803 14ff3ad3 Bob Lantz
        while not cintf.isUp():
804 84a91a14 Bob Lantz
            info( '*** Waiting for', cintf, 'to come up\n' )
805
            sleep( 1 )
806
        for switch in self.switches:
807 14ff3ad3 Bob Lantz
            while not sintf.isUp():
808 84a91a14 Bob Lantz
                info( '*** Waiting for', sintf, 'to come up\n' )
809
                sleep( 1 )
810
            if self.ping( hosts=[ switch, controller ] ) != 0:
811
                error( '*** Error: control network test failed\n' )
812
                exit( 1 )
813
        info( '\n' )