Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ e1711f35

History | View | Annotate | Download (33.8 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 96952b92 Bob Lantz
VERSION = "2.1.0+"
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 e8146dd1 Bob Lantz
    def addLink( self, node1, node2, port1=None, port2=None,
325 5a8bb489 Bob Lantz
                 cls=None, **params ):
326 e8146dd1 Bob Lantz
        """"Add a link from node1 to node2
327
            node1: source node
328
            node2: dest node
329
            port1: source port
330
            port2: dest port
331
            returns: link object"""
332 720a846c cody burkard
        mac1 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff  | 0x020000000000 )
333
        mac2 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff  | 0x020000000000 )
334 e8146dd1 Bob Lantz
        defaults = { 'port1': port1,
335
                     'port2': port2,
336 eba13f0c cody burkard
                     'addr1': mac1,
337
                     'addr2': mac2,
338 e8146dd1 Bob Lantz
                     'intf': self.intf }
339
        defaults.update( params )
340 5a8bb489 Bob Lantz
        if not cls:
341
            cls = self.link
342 c265deed Bob Lantz
        link = cls( node1, node2, **defaults )
343
        self.links.append( link )
344
        return link
345 5a8bb489 Bob Lantz
346 80be5642 Bob Lantz
    def configHosts( self ):
347 80a8fa62 Bob Lantz
        "Configure a set of hosts."
348 019bff82 Bob Lantz
        for host in self.hosts:
349 216a4b7c Bob Lantz
            info( host.name + ' ' )
350 e1ca7196 Bob Lantz
            intf = host.defaultIntf()
351
            if intf:
352 dd21df3c Bob Lantz
                host.configDefault()
353 e1ca7196 Bob Lantz
            else:
354
                # Don't configure nonexistent intf
355
                host.configDefault( ip=None, mac=None )
356 8b5062a3 Brandon Heller
            # You're low priority, dude!
357 84a91a14 Bob Lantz
            # BL: do we want to do this here or not?
358
            # May not make sense if we have CPU lmiting...
359
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
360 a9c28885 Bob Lantz
            # This may not be the right place to do this, but
361
            # it needs to be done somewhere.
362 d5886525 Bob Lantz
        info( '\n' )
363 80a8fa62 Bob Lantz
364 84a91a14 Bob Lantz
    def buildFromTopo( self, topo=None ):
365 019bff82 Bob Lantz
        """Build mininet from a topology object
366 80a8fa62 Bob Lantz
           At the end of this function, everything should be connected
367
           and up."""
368 d44a5843 Bob Lantz
369
        # Possibly we should clean up here and/or validate
370
        # the topo
371 376bcba4 Brandon Heller
        if self.cleanup:
372 d44a5843 Bob Lantz
            pass
373
374 d5886525 Bob Lantz
        info( '*** Creating network\n' )
375 84a91a14 Bob Lantz
376 15146d90 Brian O'Connor
        if not self.controllers and self.controller:
377 84a91a14 Bob Lantz
            # Add a default controller
378
            info( '*** Adding controller\n' )
379 f58f83c0 Bob Lantz
            classes = self.controller
380
            if type( classes ) is not list:
381
                classes = [ classes ]
382
            for i, cls in enumerate( classes ):
383 b7268856 Bob Lantz
                # Allow Controller objects because nobody understands currying
384 e183e699 Bob Lantz
                if isinstance( cls, Controller ):
385 b7268856 Bob Lantz
                    self.addController( cls )
386
                else:
387
                    self.addController( 'c%d' % i, cls )
388 84a91a14 Bob Lantz
389 d5886525 Bob Lantz
        info( '*** Adding hosts:\n' )
390 5a8bb489 Bob Lantz
        for hostName in topo.hosts():
391
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
392
            info( hostName + ' ' )
393 84a91a14 Bob Lantz
394 d5886525 Bob Lantz
        info( '\n*** Adding switches:\n' )
395 5a8bb489 Bob Lantz
        for switchName in topo.switches():
396
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
397
            info( switchName + ' ' )
398 84a91a14 Bob Lantz
399 724f1144 Bob Lantz
        info( '\n*** Adding links:\n' )
400 5a8bb489 Bob Lantz
        for srcName, dstName in topo.links(sort=True):
401
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
402 e8146dd1 Bob Lantz
            params = topo.linkInfo( srcName, dstName )
403 5a8bb489 Bob Lantz
            srcPort, dstPort = topo.port( srcName, dstName )
404 e1246c37 Bob Lantz
            self.addLink( src, dst, srcPort, dstPort, **params )
405 5a8bb489 Bob Lantz
            info( '(%s, %s) ' % ( src.name, dst.name ) )
406 84a91a14 Bob Lantz
407 d5886525 Bob Lantz
        info( '\n' )
408 80a8fa62 Bob Lantz
409 84a91a14 Bob Lantz
    def configureControlNetwork( self ):
410 14ff3ad3 Bob Lantz
        "Control net config hook: override in subclass"
411
        raise Exception( 'configureControlNetwork: '
412 edf60032 Brandon Heller
                         'should be overriden in subclass', self )
413 84a91a14 Bob Lantz
414 d44a5843 Bob Lantz
    def build( self ):
415
        "Build mininet."
416
        if self.topo:
417
            self.buildFromTopo( self.topo )
418 824afb84 Rémy Léone
        if self.inNamespace:
419 d44a5843 Bob Lantz
            self.configureControlNetwork()
420 d5886525 Bob Lantz
        info( '*** Configuring hosts\n' )
421 80be5642 Bob Lantz
        self.configHosts()
422 376bcba4 Brandon Heller
        if self.xterms:
423 15b482e3 Brandon Heller
            self.startTerms()
424 80a8fa62 Bob Lantz
        if self.autoStaticArp:
425
            self.staticArp()
426 99c035d9 Bob Lantz
        self.built = True
427 80a8fa62 Bob Lantz
428 15b482e3 Brandon Heller
    def startTerms( self ):
429
        "Start a terminal for each node."
430 4316be95 Brian O'Connor
        if 'DISPLAY' not in os.environ:
431
            error( "Error starting terms: Cannot connect to display\n" )
432
            return
433 15b482e3 Brandon Heller
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
434 8a034f4f Brandon Heller
        cleanUpScreens()
435 15b482e3 Brandon Heller
        self.terms += makeTerms( self.controllers, 'controller' )
436
        self.terms += makeTerms( self.switches, 'switch' )
437
        self.terms += makeTerms( self.hosts, 'host' )
438 8a034f4f Brandon Heller
439 80a8fa62 Bob Lantz
    def stopXterms( self ):
440
        "Kill each xterm."
441 8a034f4f Brandon Heller
        for term in self.terms:
442 80a8fa62 Bob Lantz
            os.kill( term.pid, signal.SIGKILL )
443 8a034f4f Brandon Heller
        cleanUpScreens()
444 8b5062a3 Brandon Heller
445 80a8fa62 Bob Lantz
    def staticArp( self ):
446
        "Add all-pairs ARP entries to remove the need to handle broadcast."
447 019bff82 Bob Lantz
        for src in self.hosts:
448
            for dst in self.hosts:
449 376bcba4 Brandon Heller
                if src != dst:
450 1bda2d21 Bob Lantz
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
451 376bcba4 Brandon Heller
452 80a8fa62 Bob Lantz
    def start( self ):
453 99c035d9 Bob Lantz
        "Start controller and switches."
454
        if not self.built:
455
            self.build()
456 d5886525 Bob Lantz
        info( '*** Starting controller\n' )
457 019bff82 Bob Lantz
        for controller in self.controllers:
458
            controller.start()
459
        info( '*** Starting %s switches\n' % len( self.switches ) )
460
        for switch in self.switches:
461 efc9a01c Bob Lantz
            info( switch.name + ' ')
462 80a8fa62 Bob Lantz
            switch.start( self.controllers )
463 d5886525 Bob Lantz
        info( '\n' )
464 c23c992f Cody Burkard
        if self.waitConn:
465 5a9c74be Cody Burkard
            self.waitConnected()
466 80a8fa62 Bob Lantz
467
    def stop( self ):
468 d5886525 Bob Lantz
        "Stop the controller(s), switches and hosts"
469 b7a112cb Cody Burkard
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
470
        for controller in self.controllers:
471
            info( controller.name + ' ' )
472
            controller.stop()
473
        info( '\n' )
474 8a034f4f Brandon Heller
        if self.terms:
475 d5886525 Bob Lantz
            info( '*** Stopping %i terms\n' % len( self.terms ) )
476 80a8fa62 Bob Lantz
            self.stopXterms()
477 019bff82 Bob Lantz
        info( '*** Stopping %i switches\n' % len( self.switches ) )
478 876e66e5 Rich Lane
        for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
479 a0bc1002 Bob Lantz
            if hasattr( swclass, 'batchShutdown' ):
480 876e66e5 Rich Lane
                swclass.batchShutdown( switches )
481 019bff82 Bob Lantz
        for switch in self.switches:
482 84a91a14 Bob Lantz
            info( switch.name + ' ' )
483 8b5062a3 Brandon Heller
            switch.stop()
484 c265deed Bob Lantz
            switch.terminate()
485
        info( '\n' )
486
        info( '*** Stopping %i links\n' % len( self.links ) )
487
        for link in self.links:
488
            link.stop()
489 d5886525 Bob Lantz
        info( '\n' )
490 10be691b Bob Lantz
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
491
        for host in self.hosts:
492
            info( host.name + ' ' )
493
            host.terminate()
494 84a91a14 Bob Lantz
        info( '\n*** Done\n' )
495 8b5062a3 Brandon Heller
496 67516aa4 Bob Lantz
    def run( self, test, *args, **kwargs ):
497 80a8fa62 Bob Lantz
        "Perform a complete start/test/stop cycle."
498 8b5062a3 Brandon Heller
        self.start()
499 d5886525 Bob Lantz
        info( '*** Running test\n' )
500 67516aa4 Bob Lantz
        result = test( *args, **kwargs )
501 8b5062a3 Brandon Heller
        self.stop()
502
        return result
503
504 259d7133 Bob Lantz
    def monitor( self, hosts=None, timeoutms=-1 ):
505 ec7b211c Bob Lantz
        """Monitor a set of hosts (or all hosts by default),
506
           and return their output, a line at a time.
507 259d7133 Bob Lantz
           hosts: (optional) set of hosts to monitor
508
           timeoutms: (optional) timeout value in ms
509
           returns: iterator which returns host, line"""
510 ec7b211c Bob Lantz
        if hosts is None:
511
            hosts = self.hosts
512
        poller = select.poll()
513 cf6f6704 Bob Lantz
        Node = hosts[ 0 ]  # so we can call class method fdToNode
514 ec7b211c Bob Lantz
        for host in hosts:
515
            poller.register( host.stdout )
516
        while True:
517 259d7133 Bob Lantz
            ready = poller.poll( timeoutms )
518 ec7b211c Bob Lantz
            for fd, event in ready:
519
                host = Node.fdToNode( fd )
520
                if event & select.POLLIN:
521
                    line = host.readline()
522 259d7133 Bob Lantz
                    if line is not None:
523 ec7b211c Bob Lantz
                        yield host, line
524 259d7133 Bob Lantz
            # Return if non-blocking
525
            if not ready and timeoutms >= 0:
526
                yield None, None
527 ec7b211c Bob Lantz
528 84a91a14 Bob Lantz
    # XXX These test methods should be moved out of this class.
529
    # Probably we should create a tests.py for them
530
531 8b5062a3 Brandon Heller
    @staticmethod
532 80a8fa62 Bob Lantz
    def _parsePing( pingOutput ):
533
        "Parse ping output and return packets sent, received."
534 3fac5a43 Brandon Heller
        # Check for downed link
535
        if 'connect: Network is unreachable' in pingOutput:
536 824afb84 Rémy Léone
            return 1, 0
537 8b5062a3 Brandon Heller
        r = r'(\d+) packets transmitted, (\d+) received'
538 80a8fa62 Bob Lantz
        m = re.search( r, pingOutput )
539 7a506047 Brandon Heller
        if m is None:
540 d5886525 Bob Lantz
            error( '*** Error: could not parse ping output: %s\n' %
541 2e089b5e Brandon Heller
                   pingOutput )
542 824afb84 Rémy Léone
            return 1, 0
543 80a8fa62 Bob Lantz
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
544 8b5062a3 Brandon Heller
        return sent, received
545
546 1f1d590c Brandon Heller
    def ping( self, hosts=None, timeout=None ):
547 80a8fa62 Bob Lantz
        """Ping between all specified hosts.
548 019bff82 Bob Lantz
           hosts: list of hosts
549 1f1d590c Brandon Heller
           timeout: time to wait for a response, as string
550 80a8fa62 Bob Lantz
           returns: ploss packet loss percentage"""
551 d44a5843 Bob Lantz
        # should we check if running?
552 8b5062a3 Brandon Heller
        packets = 0
553
        lost = 0
554
        ploss = None
555
        if not hosts:
556 019bff82 Bob Lantz
            hosts = self.hosts
557 cdeaca86 Brandon Heller
            output( '*** Ping: testing ping reachability\n' )
558 019bff82 Bob Lantz
        for node in hosts:
559 cdeaca86 Brandon Heller
            output( '%s -> ' % node.name )
560 019bff82 Bob Lantz
            for dest in hosts:
561 8b5062a3 Brandon Heller
                if node != dest:
562 1f1d590c Brandon Heller
                    opts = ''
563
                    if timeout:
564
                        opts = '-W %s' % timeout
565
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
566 80a8fa62 Bob Lantz
                    sent, received = self._parsePing( result )
567 8b5062a3 Brandon Heller
                    packets += sent
568
                    if received > sent:
569 d5886525 Bob Lantz
                        error( '*** Error: received too many packets' )
570
                        error( '%s' % result )
571 80a8fa62 Bob Lantz
                        node.cmdPrint( 'route' )
572
                        exit( 1 )
573 8b5062a3 Brandon Heller
                    lost += sent - received
574 cdeaca86 Brandon Heller
                    output( ( '%s ' % dest.name ) if received else 'X ' )
575
            output( '\n' )
576 c188bee3 Brian O'Connor
        if packets > 0:
577 92a28881 lantz
            ploss = 100.0 * lost / packets
578 fec98e27 Brian O'Connor
            received = packets - lost
579
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
580
                    ( ploss, received, packets ) )
581 c188bee3 Brian O'Connor
        else:
582
            ploss = 0
583
            output( "*** Warning: No packets sent\n" )
584 8b5062a3 Brandon Heller
        return ploss
585
586 1f1d590c Brandon Heller
    @staticmethod
587
    def _parsePingFull( pingOutput ):
588
        "Parse ping output and return all data."
589 1ecc63df Brian O'Connor
        errorTuple = (1, 0, 0, 0, 0, 0)
590 1f1d590c Brandon Heller
        # Check for downed link
591 1ecc63df Brian O'Connor
        r = r'[uU]nreachable'
592
        m = re.search( r, pingOutput )
593
        if m is not None:
594
            return errorTuple
595 1f1d590c Brandon Heller
        r = r'(\d+) packets transmitted, (\d+) received'
596
        m = re.search( r, pingOutput )
597
        if m is None:
598
            error( '*** Error: could not parse ping output: %s\n' %
599
                   pingOutput )
600 1ecc63df Brian O'Connor
            return errorTuple
601 1f1d590c Brandon Heller
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
602
        r = r'rtt min/avg/max/mdev = '
603
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
604
        m = re.search( r, pingOutput )
605 1ecc63df Brian O'Connor
        if m is None:
606 00cbb348 cody burkard
            if received == 0:
607
                return errorTuple
608 1ecc63df Brian O'Connor
            error( '*** Error: could not parse ping output: %s\n' %
609
                   pingOutput )
610
            return errorTuple
611 1f1d590c Brandon Heller
        rttmin = float( m.group( 1 ) )
612
        rttavg = float( m.group( 2 ) )
613
        rttmax = float( m.group( 3 ) )
614
        rttdev = float( m.group( 4 ) )
615
        return sent, received, rttmin, rttavg, rttmax, rttdev
616
617
    def pingFull( self, hosts=None, timeout=None ):
618
        """Ping between all specified hosts and return all data.
619
           hosts: list of hosts
620
           timeout: time to wait for a response, as string
621
           returns: all ping data; see function body."""
622
        # should we check if running?
623
        # Each value is a tuple: (src, dsd, [all ping outputs])
624
        all_outputs = []
625
        if not hosts:
626
            hosts = self.hosts
627
            output( '*** Ping: testing ping reachability\n' )
628
        for node in hosts:
629
            output( '%s -> ' % node.name )
630
            for dest in hosts:
631
                if node != dest:
632
                    opts = ''
633
                    if timeout:
634
                        opts = '-W %s' % timeout
635
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
636
                    outputs = self._parsePingFull( result )
637
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
638
                    all_outputs.append( (node, dest, outputs) )
639
                    output( ( '%s ' % dest.name ) if received else 'X ' )
640
            output( '\n' )
641
        output( "*** Results: \n" )
642
        for outputs in all_outputs:
643
            src, dest, ping_outputs = outputs
644
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
645
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
646
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
647
                    (rttmin, rttavg, rttmax, rttdev) )
648
        return all_outputs
649
650 4d1a9cdc Jon Hall
    def pingAll( self, timeout=None ):
651 80a8fa62 Bob Lantz
        """Ping between all hosts.
652
           returns: ploss packet loss percentage"""
653 4d1a9cdc Jon Hall
        return self.ping( timeout=timeout )
654 eeb9cb3c Brandon Heller
655 80a8fa62 Bob Lantz
    def pingPair( self ):
656
        """Ping between first two hosts, useful for testing.
657
           returns: ploss packet loss percentage"""
658 019bff82 Bob Lantz
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
659 80a8fa62 Bob Lantz
        return self.ping( hosts=hosts )
660 eeb9cb3c Brandon Heller
661 1f1d590c Brandon Heller
    def pingAllFull( self ):
662
        """Ping between all hosts.
663
           returns: ploss packet loss percentage"""
664
        return self.pingFull()
665
666
    def pingPairFull( self ):
667
        """Ping between first two hosts, useful for testing.
668
           returns: ploss packet loss percentage"""
669
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
670
        return self.pingFull( hosts=hosts )
671
672 eeb9cb3c Brandon Heller
    @staticmethod
673 80a8fa62 Bob Lantz
    def _parseIperf( iperfOutput ):
674
        """Parse iperf output and return bandwidth.
675
           iperfOutput: string
676
           returns: result string"""
677 eeb9cb3c Brandon Heller
        r = r'([\d\.]+ \w+/sec)'
678 ad2fda25 Bob Lantz
        m = re.findall( r, iperfOutput )
679 eeb9cb3c Brandon Heller
        if m:
680 ad2fda25 Bob Lantz
            return m[-1]
681 eeb9cb3c Brandon Heller
        else:
682 ad2fda25 Bob Lantz
            # was: raise Exception(...)
683
            error( 'could not parse iperf output: ' + iperfOutput )
684
            return ''
685 80a8fa62 Bob Lantz
686 216a4b7c Bob Lantz
    # XXX This should be cleaned up
687
688 a1acfa89 Cody Burkard
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ):
689 80a8fa62 Bob Lantz
        """Run iperf between two hosts.
690 019bff82 Bob Lantz
           hosts: list of hosts; if None, uses opposite hosts
691 80a8fa62 Bob Lantz
           l4Type: string, one of [ TCP, UDP ]
692 e1711f35 Bob Lantz
           returns: results two-element array of [ server, client ] speeds
693
           note: send() is buffered, so client rate can be much higher than
694
           the actual transmission rate; on an unloaded system, server
695
           rate should be much closer to the actual receive rate"""
696 ad2fda25 Bob Lantz
        if not quietRun( 'which telnet' ):
697
            error( 'Cannot find telnet in $PATH - required for iperf test' )
698
            return
699 eeb9cb3c Brandon Heller
        if not hosts:
700 019bff82 Bob Lantz
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
701 eeb9cb3c Brandon Heller
        else:
702 80a8fa62 Bob Lantz
            assert len( hosts ) == 2
703 ec7b211c Bob Lantz
        client, server = hosts
704 cdeaca86 Brandon Heller
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
705 ec7b211c Bob Lantz
        output( "%s and %s\n" % ( client.name, server.name ) )
706
        server.cmd( 'killall -9 iperf' )
707 80a8fa62 Bob Lantz
        iperfArgs = 'iperf '
708
        bwArgs = ''
709
        if l4Type == 'UDP':
710
            iperfArgs += '-u '
711
            bwArgs = '-b ' + udpBw + ' '
712
        elif l4Type != 'TCP':
713
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
714 a1acfa89 Cody Burkard
        if format:
715 93ddd926 Cody Burkard
          iperfArgs += '-f %s ' %format
716 ec7b211c Bob Lantz
        server.sendCmd( iperfArgs + '-s', printPid=True )
717
        servout = ''
718
        while server.lastPid is None:
719
            servout += server.monitor()
720 8856d284 Bob Lantz
        if l4Type == 'TCP':
721
            while 'Connected' not in client.cmd(
722 615ebb7a Brandon Heller
                    'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
723 13d25b41 Bob Lantz
                info( 'Waiting for iperf to start up...' )
724 8856d284 Bob Lantz
                sleep(.5)
725 ec7b211c Bob Lantz
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
726 edf60032 Brandon Heller
                             bwArgs )
727 ec7b211c Bob Lantz
        debug( 'Client output: %s\n' % cliout )
728
        server.sendInt()
729
        servout += server.waitOutput()
730
        debug( 'Server output: %s\n' % servout )
731
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
732 80a8fa62 Bob Lantz
        if l4Type == 'UDP':
733
            result.insert( 0, udpBw )
734 cdeaca86 Brandon Heller
        output( '*** Results: %s\n' % result )
735 eeb9cb3c Brandon Heller
        return result
736
737 fcd01592 Brandon Heller
    def runCpuLimitTest( self, cpu, duration=5 ):
738
        """run CPU limit test with 'while true' processes.
739
        cpu: desired CPU fraction of each host
740
        duration: test duration in seconds
741
        returns a single list of measured CPU fractions as floats.
742
        """
743 ce781a18 cody burkard
        cores = int( quietRun( 'nproc' ) )
744 fcd01592 Brandon Heller
        pct = cpu * 100
745 ce781a18 cody burkard
        info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
746 fcd01592 Brandon Heller
        hosts = self.hosts
747 ce781a18 cody burkard
        cores = int( quietRun( 'nproc' ) )
748
        # number of processes to run a while loop on per host
749
        num_procs = int( ceil( cores * cpu ) )
750
        pids = {}
751 fcd01592 Brandon Heller
        for h in hosts:
752 ce781a18 cody burkard
            pids[ h ] = []
753
            for _core in range( num_procs ):
754
                h.cmd( 'while true; do a=1; done &' )
755
                pids[ h ].append( h.cmd( 'echo $!' ).strip() )
756
        outputs = {}
757
        time = {}
758
        # get the initial cpu time for each host
759
        for host in hosts:
760
            outputs[ host ] = []
761
            with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
762
                time[ host ] = float( f.read() )
763
        for _ in range( 5 ):
764 fcd01592 Brandon Heller
            sleep( 1 )
765 ce781a18 cody burkard
            for host in hosts:
766
                with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
767
                    readTime = float( f.read() )
768
                outputs[ host ].append( ( ( readTime - time[ host ] )
769
                                        / 1000000000 ) / cores * 100 )
770
                time[ host ] = readTime
771
        for h, pids in pids.items():
772
            for pid in pids:
773
                h.cmd( 'kill -9 %s' % pid )
774 fcd01592 Brandon Heller
        cpu_fractions = []
775 ce781a18 cody burkard
        for _host, outputs in outputs.items():
776
            for pct in outputs:
777
                cpu_fractions.append( pct )
778 fcd01592 Brandon Heller
        output( '*** Results: %s\n' % cpu_fractions )
779
        return cpu_fractions
780
781 84a91a14 Bob Lantz
    # BL: I think this can be rewritten now that we have
782
    # a real link class.
783 c70aab0a Bob Lantz
    def configLinkStatus( self, src, dst, status ):
784
        """Change status of src <-> dst links.
785
           src: node name
786
           dst: node name
787
           status: string {up, down}"""
788 8d3c2859 Brandon Heller
        if src not in self.nameToNode:
789
            error( 'src not in network: %s\n' % src )
790
        elif dst not in self.nameToNode:
791
            error( 'dst not in network: %s\n' % dst )
792
        else:
793 8856d284 Bob Lantz
            if type( src ) is str:
794
                src = self.nameToNode[ src ]
795
            if type( dst ) is str:
796
                dst = self.nameToNode[ dst ]
797
            connections = src.connectionsTo( dst )
798 fb2f6523 Bob Lantz
            if len( connections ) == 0:
799 8d3c2859 Brandon Heller
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
800 fb2f6523 Bob Lantz
            for srcIntf, dstIntf in connections:
801 8856d284 Bob Lantz
                result = srcIntf.ifconfig( status )
802 8d3c2859 Brandon Heller
                if result:
803
                    error( 'link src status change failed: %s\n' % result )
804 8856d284 Bob Lantz
                result = dstIntf.ifconfig( status )
805 8d3c2859 Brandon Heller
                if result:
806
                    error( 'link dst status change failed: %s\n' % result )
807
808 80a8fa62 Bob Lantz
    def interact( self ):
809
        "Start network and run our simple CLI."
810 eeb9cb3c Brandon Heller
        self.start()
811 496b5f9e Bob Lantz
        result = CLI( self )
812 eeb9cb3c Brandon Heller
        self.stop()
813
        return result
814 82b72072 Bob Lantz
815 8e3699ec Bob Lantz
    inited = False
816 14ff3ad3 Bob Lantz
817 8e3699ec Bob Lantz
    @classmethod
818
    def init( cls ):
819
        "Initialize Mininet"
820
        if cls.inited:
821
            return
822 bcfb3009 Brandon Heller
        ensureRoot()
823 8e3699ec Bob Lantz
        fixLimits()
824
        cls.inited = True
825
826 82b72072 Bob Lantz
827 84a91a14 Bob Lantz
class MininetWithControlNet( Mininet ):
828
829
    """Control network support:
830

831
       Create an explicit control network. Currently this is only
832
       used/usable with the user datapath.
833

834
       Notes:
835

836
       1. If the controller and switches are in the same (e.g. root)
837
          namespace, they can just use the loopback connection.
838

839
       2. If we can get unix domain sockets to work, we can use them
840
          instead of an explicit control network.
841

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

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

848
       5. Basically nobody ever used this code, so it has been moved
849 14ff3ad3 Bob Lantz
          into its own class.
850

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