Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 88763cfb

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

794
       Create an explicit control network. Currently this is only
795
       used/usable with the user datapath.
796

797
       Notes:
798

799
       1. If the controller and switches are in the same (e.g. root)
800
          namespace, they can just use the loopback connection.
801

802
       2. If we can get unix domain sockets to work, we can use them
803
          instead of an explicit control network.
804

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

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

811
       5. Basically nobody ever used this code, so it has been moved
812 14ff3ad3 Bob Lantz
          into its own class.
813

814
       6. Ultimately we may wish to extend this to allow us to create a
815
          control network which every node's control interface is
816
          attached to."""
817 84a91a14 Bob Lantz
818
    def configureControlNetwork( self ):
819
        "Configure control network."
820
        self.configureRoutedControlNetwork()
821
822
    # We still need to figure out the right way to pass
823
    # in the control network location.
824
825
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
826 edf60032 Brandon Heller
                                       prefixLen=16 ):
827 84a91a14 Bob Lantz
        """Configure a routed control network on controller and switches.
828
           For use with the user datapath only right now."""
829
        controller = self.controllers[ 0 ]
830
        info( controller.name + ' <->' )
831
        cip = ip
832
        snum = ipParse( ip )
833
        for switch in self.switches:
834
            info( ' ' + switch.name )
835 14ff3ad3 Bob Lantz
            link = self.link( switch, controller, port1=0 )
836
            sintf, cintf = link.intf1, link.intf2
837
            switch.controlIntf = sintf
838 84a91a14 Bob Lantz
            snum += 1
839
            while snum & 0xff in [ 0, 255 ]:
840
                snum += 1
841
            sip = ipStr( snum )
842 14ff3ad3 Bob Lantz
            cintf.setIP( cip, prefixLen )
843
            sintf.setIP( sip, prefixLen )
844 84a91a14 Bob Lantz
            controller.setHostRoute( sip, cintf )
845
            switch.setHostRoute( cip, sintf )
846
        info( '\n' )
847
        info( '*** Testing control network\n' )
848 14ff3ad3 Bob Lantz
        while not cintf.isUp():
849 84a91a14 Bob Lantz
            info( '*** Waiting for', cintf, 'to come up\n' )
850
            sleep( 1 )
851
        for switch in self.switches:
852 14ff3ad3 Bob Lantz
            while not sintf.isUp():
853 84a91a14 Bob Lantz
                info( '*** Waiting for', sintf, 'to come up\n' )
854
                sleep( 1 )
855
            if self.ping( hosts=[ switch, controller ] ) != 0:
856
                error( '*** Error: control network test failed\n' )
857
                exit( 1 )
858
        info( '\n' )