Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 3f2355a3

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

777
       Create an explicit control network. Currently this is only
778
       used/usable with the user datapath.
779

780
       Notes:
781

782
       1. If the controller and switches are in the same (e.g. root)
783
          namespace, they can just use the loopback connection.
784

785
       2. If we can get unix domain sockets to work, we can use them
786
          instead of an explicit control network.
787

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

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

794
       5. Basically nobody ever used this code, so it has been moved
795 14ff3ad3 Bob Lantz
          into its own class.
796

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