Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 377d1b1c

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

847
       Create an explicit control network. Currently this is only
848
       used/usable with the user datapath.
849

850
       Notes:
851

852
       1. If the controller and switches are in the same (e.g. root)
853
          namespace, they can just use the loopback connection.
854

855
       2. If we can get unix domain sockets to work, we can use them
856
          instead of an explicit control network.
857

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

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

864
       5. Basically nobody ever used this code, so it has been moved
865 14ff3ad3 Bob Lantz
          into its own class.
866

867
       6. Ultimately we may wish to extend this to allow us to create a
868
          control network which every node's control interface is
869
          attached to."""
870 84a91a14 Bob Lantz
871
    def configureControlNetwork( self ):
872
        "Configure control network."
873
        self.configureRoutedControlNetwork()
874
875
    # We still need to figure out the right way to pass
876
    # in the control network location.
877
878
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
879 edf60032 Brandon Heller
                                       prefixLen=16 ):
880 84a91a14 Bob Lantz
        """Configure a routed control network on controller and switches.
881
           For use with the user datapath only right now."""
882
        controller = self.controllers[ 0 ]
883
        info( controller.name + ' <->' )
884
        cip = ip
885
        snum = ipParse( ip )
886
        for switch in self.switches:
887
            info( ' ' + switch.name )
888 14ff3ad3 Bob Lantz
            link = self.link( switch, controller, port1=0 )
889
            sintf, cintf = link.intf1, link.intf2
890
            switch.controlIntf = sintf
891 84a91a14 Bob Lantz
            snum += 1
892
            while snum & 0xff in [ 0, 255 ]:
893
                snum += 1
894
            sip = ipStr( snum )
895 14ff3ad3 Bob Lantz
            cintf.setIP( cip, prefixLen )
896
            sintf.setIP( sip, prefixLen )
897 84a91a14 Bob Lantz
            controller.setHostRoute( sip, cintf )
898
            switch.setHostRoute( cip, sintf )
899
        info( '\n' )
900
        info( '*** Testing control network\n' )
901 14ff3ad3 Bob Lantz
        while not cintf.isUp():
902 84a91a14 Bob Lantz
            info( '*** Waiting for', cintf, 'to come up\n' )
903
            sleep( 1 )
904
        for switch in self.switches:
905 14ff3ad3 Bob Lantz
            while not sintf.isUp():
906 84a91a14 Bob Lantz
                info( '*** Waiting for', sintf, 'to come up\n' )
907
                sleep( 1 )
908
            if self.ping( hosts=[ switch, controller ] ) != 0:
909
                error( '*** Error: control network test failed\n' )
910
                exit( 1 )
911
        info( '\n' )