Revision 80a8fa62 mininet/net.py

View differences:

mininet/net.py
1 1
#!/usr/bin/python
2 2
"""Mininet: A simple networking testbed for OpenFlow!
3

  
4
@author Bob Lantz (rlantz@cs.stanford.edu)
5
@author Brandon Heller (brandonh@stanford.edu)
3
author: Bob Lantz ( rlantz@cs.stanford.edu )
4
author: Brandon Heller ( brandonh@stanford.edu )
6 5

  
7 6
Mininet creates scalable OpenFlow test networks by using
8 7
process-based virtualization and network namespaces.
......
12 11
top of a single Linux kernel.
13 12

  
14 13
Each host has:
15
   A virtual console (pipes to a shell)
16
   A virtual interfaces (half of a veth pair)
17
   A parent shell (and possibly some child processes) in a namespace
14
A virtual console ( pipes to a shell )
15
A virtual interfaces ( half of a veth pair )
16
A parent shell ( and possibly some child processes ) in a namespace
18 17

  
19 18
Hosts have a network interface which is configured via ifconfig/ip
20 19
link/etc.
......
25 24
In kernel datapath mode, the controller and switches are simply
26 25
processes in the root namespace.
27 26

  
28
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
27
Kernel OpenFlow datapaths are instantiated using dpctl( 8 ), and are
29 28
attached to the one side of a veth pair; the other side resides in the
30 29
host namespace. In this mode, switch processes can simply connect to the
31 30
controller via the loopback interface.
32 31

  
33 32
In user datapath mode, the controller and switches are full-service
34 33
nodes that live in their own network namespaces and have management
35
interfaces and IP addresses on a control network (e.g. 10.0.123.1,
36
currently routed although it could be bridged.)
34
interfaces and IP addresses on a control network ( e.g. 10.0.123.1,
35
currently routed although it could be bridged. )
37 36

  
38 37
In addition to a management interface, user mode switches also have
39 38
several switch interfaces, halves of veth pairs whose other halves
40 39
reside in the host nodes that the switches are connected to.
41 40

  
42 41
Naming:
43
   Host nodes are named h1-hN
44
   Switch nodes are named s0-sN
45
   Interfaces are named {nodename}-eth0 .. {nodename}-ethN,
46

  
47
"""
42
Host nodes are named h1-hN
43
Switch nodes are named s0-sN
44
Interfaces are named { nodename }-eth0 .. { nodename }-ethN,"""
48 45
import os
49 46
import re
50 47
import signal
......
55 52
from mininet.log import lg
56 53
from mininet.node import KernelSwitch, OVSKernelSwitch
57 54
from mininet.util import quietRun, fixLimits
58
from mininet.util import make_veth_pair, move_intf, retry, MOVEINTF_DELAY
55
from mininet.util import makeIntfPair, moveIntf
59 56
from mininet.xterm import cleanUpScreens, makeXterms
60 57

  
61
DATAPATHS = ['kernel'] #['user', 'kernel']
62

  
58
DATAPATHS = [ 'kernel' ] #[ 'user', 'kernel' ]
63 59

  
64 60
def init():
65 61
    "Initialize Mininet."
......
67 63
        # Note: this script must be run as root
68 64
        # Perhaps we should do so automatically!
69 65
        print "*** Mininet must run as root."
70
        exit(1)
66
        exit( 1 )
71 67
    # If which produces no output, then netns is not in the path.
72 68
    # May want to loosen this to handle netns in the current dir.
73
    if not quietRun(['which', 'netns']):
74
        raise Exception("Could not find netns; see INSTALL")
69
    if not quietRun( [ 'which', 'netns' ] ):
70
        raise Exception( "Could not find netns; see INSTALL" )
75 71
    fixLimits()
76 72

  
77

  
78
class Mininet(object):
79
    '''Network emulation with hosts spawned in network namespaces.'''
80

  
81
    def __init__(self, topo, switch, host, controller, cparams,
82
                 build = True, xterms = False, cleanup = False,
83
                 in_namespace = False,
84
                 auto_set_macs = False, auto_static_arp = False):
85
        '''Create Mininet object.
86

  
87
        @param topo Topo object
88
        @param switch Switch class
89
        @param host Host class
90
        @param controller Controller class
91
        @param cparams ControllerParams object
92
        @param now build now?
93
        @param xterms if build now, spawn xterms?
94
        @param cleanup if build now, cleanup before creating?
95
        @param in_namespace spawn switches and controller in net namespaces?
96
        @param auto_set_macs set MAC addrs to DPIDs?
97
        @param auto_static_arp set all-pairs static MAC addrs?
98
        '''
73
class Mininet( object ):
74
    "Network emulation with hosts spawned in network namespaces."
75

  
76
    def __init__( self, topo, switch, host, controller, cparams,
77
                 build=True, xterms=False, cleanup=False,
78
                 inNamespace=False,
79
                 autoSetMacs=False, autoStaticArp=False ):
80
        """Create Mininet object.
81
           topo: Topo object
82
            switch: Switch class
83
           host: Host class
84
           controller: Controller class
85
           cparams: ControllerParams object
86
           now: build now?
87
           xterms: if build now, spawn xterms?
88
           cleanup: if build now, cleanup before creating?
89
           inNamespace: spawn switches and controller in net namespaces?
90
           autoSetMacs: set MAC addrs to DPIDs?
91
           autoStaticArp: set all-pairs static MAC addrs?"""
99 92
        self.topo = topo
100 93
        self.switch = switch
101 94
        self.host = host
102 95
        self.controller = controller
103 96
        self.cparams = cparams
104
        self.nodes = {} # dpid to Node{Host, Switch} objects
97
        self.nodes = {} # dpid to Node{ Host, Switch } objects
105 98
        self.controllers = {} # controller name to Controller objects
106 99
        self.dps = 0 # number of created kernel datapaths
107
        self.in_namespace = in_namespace
100
        self.inNamespace = inNamespace
108 101
        self.xterms = xterms
109 102
        self.cleanup = cleanup
110
        self.auto_set_macs = auto_set_macs
111
        self.auto_static_arp = auto_static_arp
103
        self.autoSetMacs = autoSetMacs
104
        self.autoStaticArp = autoStaticArp
112 105

  
113 106
        self.terms = [] # list of spawned xterm processes
114 107

  
115 108
        if build:
116 109
            self.build()
117 110

  
118
    def _add_host(self, dpid):
119
        '''Add host.
120

  
121
        @param dpid DPID of host to add
122
        '''
123
        host = self.host('h_' + self.topo.name(dpid))
111
    def _addHost( self, dpid ):
112
        """Add host.
113
           dpid: DPID of host to add"""
114
        host = self.host( 'h_' + self.topo.name( dpid ) )
124 115
        # for now, assume one interface per host.
125
        host.intfs.append('h_' + self.topo.name(dpid) + '-eth0')
126
        self.nodes[dpid] = host
127
        #lg.info('%s ' % host.name)
128

  
129
    def _add_switch(self, dpid):
130
        '''Add switch.
116
        host.intfs.append( 'h_' + self.topo.name( dpid ) + '-eth0' )
117
        self.nodes[ dpid ] = host
118
        #lg.info( '%s ' % host.name )
131 119

  
132
        @param dpid DPID of switch to add
133
        '''
120
    def _addSwitch( self, dpid ):
121
        """Add switch.
122
           dpid: DPID of switch to add"""
134 123
        sw = None
135
        sw_dpid = None
136
        if self.auto_set_macs:
137
            sw_dpid = dpid
124
        swDpid = None
125
        if self.autoSetMacs:
126
            swDpid = dpid
138 127
        if self.switch is KernelSwitch or self.switch is OVSKernelSwitch:
139
            sw = self.switch('s_' + self.topo.name(dpid), dp = self.dps,
140
                             dpid = sw_dpid)
128
            sw = self.switch( 's_' + self.topo.name( dpid ), dp = self.dps,
129
                             dpid = swDpid )
141 130
            self.dps += 1
142 131
        else:
143
            sw = self.switch('s_' + self.topo.name(dpid))
144
        self.nodes[dpid] = sw
145

  
146
    def _add_link(self, src, dst):
147
        '''Add link.
148

  
149
        @param src source DPID
150
        @param dst destination DPID
151
        '''
152
        src_port, dst_port = self.topo.port(src, dst)
153
        src_node = self.nodes[src]
154
        dst_node = self.nodes[dst]
155
        src_intf = src_node.intfName(src_port)
156
        dst_intf = dst_node.intfName(dst_port)
157
        make_veth_pair(src_intf, dst_intf)
158
        src_node.intfs.append(src_intf)
159
        dst_node.intfs.append(dst_intf)
160
        src_node.ports[src_port] = src_intf
161
        dst_node.ports[dst_port] = dst_intf
162
        #lg.info('\n')
163
        #lg.info('added intf %s to src node %x\n' % (src_intf, src))
164
        #lg.info('added intf %s to dst node %x\n' % (dst_intf, dst))
165
        if src_node.inNamespace:
166
            #lg.info('moving src w/inNamespace set\n')
167
            retry(3, MOVEINTF_DELAY, move_intf, src_intf, src_node)
168
        if dst_node.inNamespace:
169
            #lg.info('moving dst w/inNamespace set\n')
170
            retry(3, MOVEINTF_DELAY, move_intf, dst_intf, dst_node)
171
        src_node.connection[src_intf] = (dst_node, dst_intf)
172
        dst_node.connection[dst_intf] = (src_node, src_intf)
173

  
174
    def _add_controller(self, controller):
175
        '''Add controller.
176

  
177
        @param controller Controller class
178
        '''
179
        controller = self.controller('c0', self.in_namespace)
132
            sw = self.switch( 's_' + self.topo.name( dpid ) )
133
        self.nodes[ dpid ] = sw
134

  
135
    def _addLink( self, src, dst ):
136
        """Add link.
137
           src: source DPID
138
           dst: destination DPID"""
139
        srcPort, dstPort = self.topo.port( src, dst )
140
        srcNode = self.nodes[ src ]
141
        dstNode = self.nodes[ dst ]
142
        srcIntf = srcNode.intfName( srcPort )
143
        dstIntf = dstNode.intfName( dstPort )
144
        makeIntfPair( srcIntf, dstIntf )
145
        srcNode.intfs.append( srcIntf )
146
        dstNode.intfs.append( dstIntf )
147
        srcNode.ports[ srcPort ] = srcIntf
148
        dstNode.ports[ dstPort ] = dstIntf
149
        #lg.info( '\n' )
150
        #lg.info( 'added intf %s to src node %x\n' % ( srcIntf, src ) )
151
        #lg.info( 'added intf %s to dst node %x\n' % ( dstIntf, dst ) )
152
        if srcNode.inNamespace:
153
            #lg.info( 'moving src w/inNamespace set\n' )
154
            moveIntf( srcIntf, srcNode )
155
        if dstNode.inNamespace:
156
            #lg.info( 'moving dst w/inNamespace set\n' )
157
            moveIntf( dstIntf, dstNode )
158
        srcNode.connection[ srcIntf ] = ( dstNode, dstIntf )
159
        dstNode.connection[ dstIntf ] = ( srcNode, srcIntf )
160

  
161
    def _addController( self, controller ):
162
        """Add controller.
163
           controller: Controller class"""
164
        controller = self.controller( 'c0', self.inNamespace )
180 165
        if controller: # allow controller-less setups
181
            self.controllers['c0'] = controller
166
            self.controllers[ 'c0' ] = controller
182 167

  
183 168
    # Control network support:
184 169
    #
......
187 172
    #
188 173
    # Notes:
189 174
    #
190
    # 1. If the controller and switches are in the same (e.g. root)
175
    # 1. If the controller and switches are in the same ( e.g. root )
191 176
    #    namespace, they can just use the loopback connection.
192 177
    #    We may wish to do this for the user datapath as well as the
193 178
    #    kernel datapath.
......
199 184
    #
200 185
    # 4. Even if we dispense with this in general, it could still be
201 186
    #    useful for people who wish to simulate a separate control
202
    #    network (since real networks may need one!)
187
    #    network ( since real networks may need one! )
203 188

  
204
    def _configureControlNetwork(self):
205
        '''Configure control network.'''
189
    def _configureControlNetwork( self ):
190
        "Configure control network."
206 191
        self._configureRoutedControlNetwork()
207 192

  
208
    def _configureRoutedControlNetwork(self):
209
        '''Configure a routed control network on controller and switches.
193
    def _configureRoutedControlNetwork( self ):
194
        """Configure a routed control network on controller and switches.
195
           For use with the user datapath only right now.
196
           TODO( brandonh ) test this code!
197
           """
210 198

  
211
        For use with the user datapath only right now.
212

  
213
        @todo(brandonh) Test this code!
214
        '''
215 199
        # params were: controller, switches, ips
216 200

  
217
        controller = self.controllers['c0']
218
        lg.info('%s <-> ' % controller.name)
219
        for switch_dpid in self.topo.switches():
220
            switch = self.nodes[switch_dpid]
221
            lg.info('%s ' % switch.name)
222
            sip = self.topo.ip(switch_dpid)#ips.next()
223
            sintf = switch.intfs[0]
224
            node, cintf = switch.connection[sintf]
201
        controller = self.controllers[ 'c0' ]
202
        lg.info( '%s <-> ' % controller.name )
203
        for switchDpid in self.topo.switches():
204
            switch = self.nodes[ switchDpid ]
205
            lg.info( '%s ' % switch.name )
206
            sip = self.topo.ip( switchDpid )#ips.next()
207
            sintf = switch.intfs[ 0 ]
208
            node, cintf = switch.connection[ sintf ]
225 209
            if node != controller:
226
                lg.error('*** Error: switch %s not connected to correct'
210
                lg.error( '*** Error: switch %s not connected to correct'
227 211
                         'controller' %
228
                         switch.name)
229
                exit(1)
230
            controller.setIP(cintf, self.cparams.ip, '/' +
231
                             self.cparams.subnet_size)
232
            switch.setIP(sintf, sip, '/' + self.cparams.subnet_size)
233
            controller.setHostRoute(sip, cintf)
234
            switch.setHostRoute(self.cparams.ip, sintf)
235
        lg.info('\n')
236
        lg.info('*** Testing control network\n')
237
        while not controller.intfIsUp(controller.intfs[0]):
238
            lg.info('*** Waiting for %s to come up\n', controller.intfs[0])
239
            sleep(1)
240
        for switch_dpid in self.topo.switches():
241
            switch = self.nodes[switch_dpid]
242
            while not switch.intfIsUp(switch.intfs[0]):
243
                lg.info('*** Waiting for %s to come up\n' % switch.intfs[0])
244
                sleep(1)
245
            if self.ping(hosts = [switch, controller]) != 0:
246
                lg.error('*** Error: control network test failed\n')
247
                exit(1)
248
        lg.info('\n')
249

  
250
    def _config_hosts(self):
251
        '''Configure a set of hosts.'''
212
                         switch.name )
213
                exit( 1 )
214
            controller.setIP( cintf, self.cparams.ip, '/' +
215
                             self.cparams.subnetSize )
216
            switch.setIP( sintf, sip, '/' + self.cparams.subnetSize )
217
            controller.setHostRoute( sip, cintf )
218
            switch.setHostRoute( self.cparams.ip, sintf )
219
        lg.info( '\n' )
220
        lg.info( '*** Testing control network\n' )
221
        while not controller.intfIsUp( controller.intfs[ 0 ] ):
222
            lg.info( '*** Waiting for %s to come up\n', controller.intfs[ 0 ] )
223
            sleep( 1 )
224
        for switchDpid in self.topo.switches():
225
            switch = self.nodes[ switchDpid ]
226
            while not switch.intfIsUp( switch.intfs[ 0 ] ):
227
                lg.info( '*** Waiting for %s to come up\n' %
228
                    switch.intfs[ 0 ] )
229
                sleep( 1 )
230
            if self.ping( hosts=[ switch, controller ] ) != 0:
231
                lg.error( '*** Error: control network test failed\n' )
232
                exit( 1 )
233
        lg.info( '\n' )
234

  
235
    def _configHosts( self ):
236
        "Configure a set of hosts."
252 237
        # params were: hosts, ips
253
        for host_dpid in self.topo.hosts():
254
            host = self.nodes[host_dpid]
255
            hintf = host.intfs[0]
256
            host.setIP(hintf, self.topo.ip(host_dpid),
257
                       '/' + str(self.cparams.subnet_size))
258
            host.setDefaultRoute(hintf)
238
        for hostDpid in self.topo.hosts():
239
            host = self.nodes[ hostDpid ]
240
            hintf = host.intfs[ 0 ]
241
            host.setIP( hintf, self.topo.ip( hostDpid ),
242
                       '/' + str( self.cparams.subnetSize ) )
243
            host.setDefaultRoute( hintf )
259 244
            # You're low priority, dude!
260
            quietRun('renice +18 -p ' + repr(host.pid))
261
            lg.info('%s ', host.name)
262
        lg.info('\n')
263

  
264
    def build(self):
265
        '''Build mininet.
266

  
267
        At the end of this function, everything should be connected and up.
268
        '''
245
            quietRun( 'renice +18 -p ' + repr( host.pid ) )
246
            lg.info( '%s ', host.name )
247
        lg.info( '\n' )
248

  
249
    def build( self ):
250
        """Build mininet.
251
           At the end of this function, everything should be connected
252
           and up."""
269 253
        if self.cleanup:
270 254
            pass # cleanup
271 255
        # validate topo?
272
        lg.info('*** Adding controller\n')
273
        self._add_controller(self.controller)
274
        lg.info('*** Creating network\n')
275
        lg.info('*** Adding hosts:\n')
276
        for host in sorted(self.topo.hosts()):
277
            self._add_host(host)
278
            lg.info('0x%x ' % host)
279
        lg.info('\n*** Adding switches:\n')
280
        for switch in sorted(self.topo.switches()):
281
            self._add_switch(switch)
282
            lg.info('0x%x ' % switch)
283
        lg.info('\n*** Adding edges:\n')
284
        for src, dst in sorted(self.topo.edges()):
285
            self._add_link(src, dst)
286
            lg.info('(0x%x, 0x%x) ' % (src, dst))
287
        lg.info('\n')
288

  
289
        if self.in_namespace:
290
            lg.info('*** Configuring control network\n')
256
        lg.info( '*** Adding controller\n' )
257
        self._addController( self.controller )
258
        lg.info( '*** Creating network\n' )
259
        lg.info( '*** Adding hosts:\n' )
260
        for host in sorted( self.topo.hosts() ):
261
            self._addHost( host )
262
            lg.info( '0x%x ' % host )
263
        lg.info( '\n*** Adding switches:\n' )
264
        for switch in sorted( self.topo.switches() ):
265
            self._addSwitch( switch )
266
            lg.info( '0x%x ' % switch )
267
        lg.info( '\n*** Adding edges:\n' )
268
        for src, dst in sorted( self.topo.edges() ):
269
            self._addLink( src, dst )
270
            lg.info( '(0x%x, 0x%x) ' % ( src, dst ) )
271
        lg.info( '\n' )
272

  
273
        if self.inNamespace:
274
            lg.info( '*** Configuring control network\n' )
291 275
            self._configureControlNetwork()
292 276

  
293
        lg.info('*** Configuring hosts\n')
294
        self._config_hosts()
277
        lg.info( '*** Configuring hosts\n' )
278
        self._configHosts()
295 279

  
296 280
        if self.xterms:
297
            self.start_xterms()
298
        if self.auto_set_macs:
299
            self.set_macs()
300
        if self.auto_static_arp:
301
            self.static_arp()
302

  
303
    def switch_nodes(self):
304
        '''Return switch nodes.'''
305
        return [self.nodes[dpid] for dpid in self.topo.switches()]
306

  
307
    def host_nodes(self):
308
        '''Return host nodes.'''
309
        return [self.nodes[dpid] for dpid in self.topo.hosts()]
310

  
311
    def start_xterms(self):
312
        '''Start an xterm for each node in the topo.'''
313
        lg.info("*** Running xterms on %s\n" % os.environ['DISPLAY'])
281
            self.startXterms()
282
        if self.autoSetMacs:
283
            self.setMacs()
284
        if self.autoStaticArp:
285
            self.staticArp()
286

  
287
    def switchNodes( self ):
288
        "Return switch nodes."
289
        return [ self.nodes[ dpid ] for dpid in self.topo.switches() ]
290

  
291
    def hostNodes( self ):
292
        "Return host nodes."
293
        return [ self.nodes[ dpid ] for dpid in self.topo.hosts() ]
294

  
295
    def startXterms( self ):
296
        "Start an xterm for each node in the topo."
297
        lg.info( "*** Running xterms on %s\n" % os.environ[ 'DISPLAY' ] )
314 298
        cleanUpScreens()
315
        self.terms += makeXterms(self.controllers.values(), 'controller')
316
        self.terms += makeXterms(self.switch_nodes(), 'switch')
317
        self.terms += makeXterms(self.host_nodes(), 'host')
299
        self.terms += makeXterms( self.controllers.values(), 'controller' )
300
        self.terms += makeXterms( self.switchNodes(), 'switch' )
301
        self.terms += makeXterms( self.hostNodes(), 'host' )
318 302

  
319
    def stop_xterms(self):
320
        '''Kill each xterm.'''
303
    def stopXterms( self ):
304
        "Kill each xterm."
321 305
        # Kill xterms
322 306
        for term in self.terms:
323
            os.kill(term.pid, signal.SIGKILL)
307
            os.kill( term.pid, signal.SIGKILL )
324 308
        cleanUpScreens()
325 309

  
326
    def set_macs(self):
327
        '''Set MAC addrs to correspond to datapath IDs on hosts.
328

  
329
        Assume that the host only has one interface.
330
        '''
310
    def setMacs( self ):
311
        """Set MAC addrs to correspond to datapath IDs on hosts.
312
           Assume that the host only has one interface."""
331 313
        for dpid in self.topo.hosts():
332
            host_node = self.nodes[dpid]
333
            host_node.setMAC(host_node.intfs[0], dpid)
314
            hostNode = self.nodes[ dpid ]
315
            hostNode.setMAC( hostNode.intfs[ 0 ], dpid )
334 316

  
335
    def static_arp(self):
336
        '''Add all-pairs ARP entries to remove the need to handle broadcast.'''
317
    def staticArp( self ):
318
        "Add all-pairs ARP entries to remove the need to handle broadcast."
337 319
        for src in self.topo.hosts():
338
            src_node = self.nodes[src]
320
            srcNode = self.nodes[ src ]
339 321
            for dst in self.topo.hosts():
340 322
                if src != dst:
341
                    src_node.setARP(dst, dst)
323
                    srcNode.setARP( dst, dst )
342 324

  
343
    def start(self):
344
        '''Start controller and switches\n'''
345
        lg.info('*** Starting controller\n')
325
    def start( self ):
326
        "Start controller and switches\n"
327
        lg.info( '*** Starting controller\n' )
346 328
        for cnode in self.controllers.values():
347 329
            cnode.start()
348
        lg.info('*** Starting %s switches\n' % len(self.topo.switches()))
349
        for switch_dpid in self.topo.switches():
350
            switch = self.nodes[switch_dpid]
351
            #lg.info('switch = %s' % switch)
352
            lg.info('0x%x ' % switch_dpid)
353
            switch.start(self.controllers)
354
        lg.info('\n')
355

  
356
    def stop(self):
357
        '''Stop the controller(s), switches and hosts\n'''
330
        lg.info( '*** Starting %s switches\n' % len( self.topo.switches() ) )
331
        for switchDpid in self.topo.switches():
332
            switch = self.nodes[ switchDpid ]
333
            #lg.info( 'switch = %s' % switch )
334
            lg.info( '0x%x ' % switchDpid )
335
            switch.start( self.controllers )
336
        lg.info( '\n' )
337

  
338
    def stop( self ):
339
        "Stop the controller(s), switches and hosts\n"
358 340
        if self.terms:
359
            lg.info('*** Stopping %i terms\n' % len(self.terms))
360
            self.stop_xterms()
361
        lg.info('*** Stopping %i hosts\n' % len(self.topo.hosts()))
362
        for host_dpid in self.topo.hosts():
363
            host = self.nodes[host_dpid]
364
            lg.info('%s ' % host.name)
341
            lg.info( '*** Stopping %i terms\n' % len( self.terms ) )
342
            self.stopXterms()
343
        lg.info( '*** Stopping %i hosts\n' % len( self.topo.hosts() ) )
344
        for hostDpid in self.topo.hosts():
345
            host = self.nodes[ hostDpid ]
346
            lg.info( '%s ' % host.name )
365 347
            host.terminate()
366
        lg.info('\n')
367
        lg.info('*** Stopping %i switches\n' % len(self.topo.switches()))
368
        for switch_dpid in self.topo.switches():
369
            switch = self.nodes[switch_dpid]
370
            lg.info('%s' % switch.name)
348
        lg.info( '\n' )
349
        lg.info( '*** Stopping %i switches\n' % len( self.topo.switches() ) )
350
        for switchDpid in self.topo.switches():
351
            switch = self.nodes[ switchDpid ]
352
            lg.info( '%s' % switch.name )
371 353
            switch.stop()
372
        lg.info('\n')
373
        lg.info('*** Stopping controller\n')
354
        lg.info( '\n' )
355
        lg.info( '*** Stopping controller\n' )
374 356
        for cnode in self.controllers.values():
375 357
            cnode.stop()
376
        lg.info('*** Test complete\n')
358
        lg.info( '*** Test complete\n' )
377 359

  
378
    def run(self, test, **params):
379
        '''Perform a complete start/test/stop cycle.'''
360
    def run( self, test, **params ):
361
        "Perform a complete start/test/stop cycle."
380 362
        self.start()
381
        lg.info('*** Running test\n')
382
        result = getattr(self, test)(**params)
363
        lg.info( '*** Running test\n' )
364
        result = getattr( self, test )( **params )
383 365
        self.stop()
384 366
        return result
385 367

  
386 368
    @staticmethod
387
    def _parse_ping(pingOutput):
388
        '''Parse ping output and return packets sent, received.'''
369
    def _parsePing( pingOutput ):
370
        "Parse ping output and return packets sent, received."
389 371
        r = r'(\d+) packets transmitted, (\d+) received'
390
        m = re.search(r, pingOutput)
372
        m = re.search( r, pingOutput )
391 373
        if m == None:
392
            lg.error('*** Error: could not parse ping output: %s\n' %
393
                     pingOutput)
394
            exit(1)
395
        sent, received = int(m.group(1)), int(m.group(2))
374
            lg.error( '*** Error: could not parse ping output: %s\n' %
375
                     pingOutput )
376
            exit( 1 )
377
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
396 378
        return sent, received
397 379

  
398
    def ping(self, hosts = None):
399
        '''Ping between all specified hosts.
400

  
401
        @param hosts list of host DPIDs
402
        @return ploss packet loss percentage
403
        '''
380
    def ping( self, hosts=None ):
381
        """Ping between all specified hosts.
382
           hosts: list of host DPIDs
383
           returns: ploss packet loss percentage"""
404 384
        #self.start()
405 385
        # check if running - only then, start?
406 386
        packets = 0
......
408 388
        ploss = None
409 389
        if not hosts:
410 390
            hosts = self.topo.hosts()
411
        lg.info('*** Ping: testing ping reachability\n')
412
        for node_dpid in hosts:
413
            node = self.nodes[node_dpid]
414
            lg.info('%s -> ' % node.name)
415
            for dest_dpid in hosts:
416
                dest = self.nodes[dest_dpid]
391
        lg.info( '*** Ping: testing ping reachability\n' )
392
        for nodeDpid in hosts:
393
            node = self.nodes[ nodeDpid ]
394
            lg.info( '%s -> ' % node.name )
395
            for destDpid in hosts:
396
                dest = self.nodes[ destDpid ]
417 397
                if node != dest:
418
                    result = node.cmd('ping -c1 ' + dest.IP())
419
                    sent, received = self._parse_ping(result)
398
                    result = node.cmd( 'ping -c1 ' + dest.IP() )
399
                    sent, received = self._parsePing( result )
420 400
                    packets += sent
421 401
                    if received > sent:
422
                        lg.error('*** Error: received too many packets')
423
                        lg.error('%s' % result)
424
                        node.cmdPrint('route')
425
                        exit(1)
402
                        lg.error( '*** Error: received too many packets' )
403
                        lg.error( '%s' % result )
404
                        node.cmdPrint( 'route' )
405
                        exit( 1 )
426 406
                    lost += sent - received
427
                    lg.info(('%s ' % dest.name) if received else 'X ')
428
            lg.info('\n')
407
                    lg.info( ( '%s ' % dest.name ) if received else 'X ' )
408
            lg.info( '\n' )
429 409
            ploss = 100 * lost / packets
430
        lg.info("*** Results: %i%% dropped (%d/%d lost)\n" %
431
                (ploss, lost, packets))
410
        lg.info( "*** Results: %i%% dropped (%d/%d lost)\n" %
411
                ( ploss, lost, packets ) )
432 412
        return ploss
433 413

  
434
    def ping_all(self):
435
        '''Ping between all hosts.
436

  
437
        @return ploss packet loss percentage
438
        '''
414
    def pingAll( self ):
415
        """Ping between all hosts.
416
           returns: ploss packet loss percentage"""
439 417
        return self.ping()
440 418

  
441
    def ping_pair(self):
442
        '''Ping between first two hosts, useful for testing.
443

  
444
        @return ploss packet loss percentage
445
        '''
446
        hosts_sorted = sorted(self.topo.hosts())
447
        hosts = [hosts_sorted[0], hosts_sorted[1]]
448
        return self.ping(hosts = hosts)
419
    def pingPair( self ):
420
        """Ping between first two hosts, useful for testing.
421
           returns: ploss packet loss percentage"""
422
        hostsSorted = sorted( self.topo.hosts() )
423
        hosts = [ hostsSorted[ 0 ], hostsSorted[ 1 ] ]
424
        return self.ping( hosts=hosts )
449 425

  
450 426
    @staticmethod
451
    def _parseIperf(iperfOutput):
452
        '''Parse iperf output and return bandwidth.
453

  
454
        @param iperfOutput string
455
        @return result string
456
        '''
427
    def _parseIperf( iperfOutput ):
428
        """Parse iperf output and return bandwidth.
429
           iperfOutput: string
430
           returns: result string"""
457 431
        r = r'([\d\.]+ \w+/sec)'
458
        m = re.search(r, iperfOutput)
432
        m = re.search( r, iperfOutput )
459 433
        if m:
460
            return m.group(1)
434
            return m.group( 1 )
461 435
        else:
462
            raise Exception('could not parse iperf output')
463

  
464
    def iperf(self, hosts = None, l4_type = 'TCP', udp_bw = '10M',
465
              verbose = False):
466
        '''Run iperf between two hosts.
467

  
468
        @param hosts list of host DPIDs; if None, uses opposite hosts
469
        @param l4_type string, one of [TCP, UDP]
470
        @param verbose verbose printing
471
        @return results two-element array of server and client speeds
472
        '''
436
            raise Exception( 'could not parse iperf output' )
437

  
438
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M',
439
              verbose=False ):
440
        """Run iperf between two hosts.
441
           hosts: list of host DPIDs; if None, uses opposite hosts
442
           l4Type: string, one of [ TCP, UDP ]
443
           verbose: verbose printing
444
           returns: results two-element array of server and client speeds"""
473 445
        if not hosts:
474
            hosts_sorted = sorted(self.topo.hosts())
475
            hosts = [hosts_sorted[0], hosts_sorted[-1]]
446
            hostsSorted = sorted( self.topo.hosts() )
447
            hosts = [ hostsSorted[ 0 ], hostsSorted[ -1 ] ]
476 448
        else:
477
            assert len(hosts) == 2
478
        host0 = self.nodes[hosts[0]]
479
        host1 = self.nodes[hosts[1]]
480
        lg.info('*** Iperf: testing ' + l4_type + ' bandwidth between ')
481
        lg.info("%s and %s\n" % (host0.name, host1.name))
482
        host0.cmd('killall -9 iperf')
483
        iperf_args = 'iperf '
484
        bw_args = ''
485
        if l4_type == 'UDP':
486
            iperf_args += '-u '
487
            bw_args = '-b ' + udp_bw + ' '
488
        elif l4_type != 'TCP':
489
            raise Exception('Unexpected l4 type: %s' % l4_type)
490
        server = host0.cmd(iperf_args + '-s &')
449
            assert len( hosts ) == 2
450
        host0 = self.nodes[ hosts[ 0 ] ]
451
        host1 = self.nodes[ hosts[ 1 ] ]
452
        lg.info( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
453
        lg.info( "%s and %s\n" % ( host0.name, host1.name ) )
454
        host0.cmd( 'killall -9 iperf' )
455
        iperfArgs = 'iperf '
456
        bwArgs = ''
457
        if l4Type == 'UDP':
458
            iperfArgs += '-u '
459
            bwArgs = '-b ' + udpBw + ' '
460
        elif l4Type != 'TCP':
461
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
462
        server = host0.cmd( iperfArgs + '-s &' )
491 463
        if verbose:
492
            lg.info('%s\n' % server)
493
        client = host1.cmd(iperf_args + '-t 5 -c ' + host0.IP() + ' ' +
494
                           bw_args)
464
            lg.info( '%s\n' % server )
465
        client = host1.cmd( iperfArgs + '-t 5 -c ' + host0.IP() + ' ' +
466
                           bwArgs )
495 467
        if verbose:
496
            lg.info('%s\n' % client)
497
        server = host0.cmd('killall -9 iperf')
468
            lg.info( '%s\n' % client )
469
        server = host0.cmd( 'killall -9 iperf' )
498 470
        if verbose:
499
            lg.info('%s\n' % server)
500
        result = [self._parseIperf(server), self._parseIperf(client)]
501
        if l4_type == 'UDP':
502
            result.insert(0, udp_bw)
503
        lg.info('*** Results: %s\n' % result)
471
            lg.info( '%s\n' % server )
472
        result = [ self._parseIperf( server ), self._parseIperf( client ) ]
473
        if l4Type == 'UDP':
474
            result.insert( 0, udpBw )
475
        lg.info( '*** Results: %s\n' % result )
504 476
        return result
505 477

  
506
    def iperf_udp(self, udp_bw = '10M'):
507
        '''Run iperf UDP test.'''
508
        return self.iperf(l4_type = 'UDP', udp_bw = udp_bw)
478
    def iperfUdp( self, udpBw='10M' ):
479
        "Run iperf UDP test."
480
        return self.iperf( l4Type='UDP', udpBw=udpBw )
509 481

  
510
    def interact(self):
511
        '''Start network and run our simple CLI.'''
482
    def interact( self ):
483
        "Start network and run our simple CLI."
512 484
        self.start()
513
        result = MininetCLI(self)
485
        result = MininetCLI( self )
514 486
        self.stop()
515 487
        return result
516 488

  
517 489

  
518
class MininetCLI(object):
519
    '''Simple command-line interface to talk to nodes.'''
520
    cmds = ['?', 'help', 'nodes', 'net', 'sh', 'ping_all', 'exit', \
521
            'ping_pair', 'iperf', 'iperf_udp', 'intfs', 'dump']
490
class MininetCLI( object ):
491
    "Simple command-line interface to talk to nodes."
492
    cmds = [ '?', 'help', 'nodes', 'net', 'sh', 'ping_all', 'exit', \
493
            'ping_pair', 'iperf', 'iperf_udp', 'intfs', 'dump' ]
522 494

  
523
    def __init__(self, mininet):
495
    def __init__( self, mininet ):
524 496
        self.mn = mininet
525 497
        self.nodemap = {} # map names to Node objects
526 498
        for node in self.mn.nodes.values():
527
            self.nodemap[node.name] = node
499
            self.nodemap[ node.name ] = node
528 500
        for cname, cnode in self.mn.controllers.iteritems():
529
            self.nodemap[cname] = cnode
501
            self.nodemap[ cname ] = cnode
530 502
        self.nodelist = self.nodemap.values()
531 503
        self.run()
532 504

  
......
535 507
    # pylint: disable-msg=W0613
536 508

  
537 509
    # Commands
538
    def help(self, args):
539
        '''Semi-useful help for CLI.'''
540
        help_str = 'Available commands are:' + str(self.cmds) + '\n' + \
541
                   'You may also send a command to a node using:\n' + \
542
                   '  <node> command {args}\n' + \
543
                   'For example:\n' + \
544
                   '  mininet> h0 ifconfig\n' + \
545
                   '\n' + \
546
                   'The interpreter automatically substitutes IP ' + \
547
                   'addresses\n' + \
548
                   'for node names, so commands like\n' + \
549
                   '  mininet> h0 ping -c1 h1\n' + \
550
                   'should work.\n' + \
551
                   '\n\n' + \
552
                   'Interactive commands are not really supported yet,\n' + \
553
                   'so please limit commands to ones that do not\n' + \
554
                   'require user interaction and will terminate\n' + \
555
                   'after a reasonable amount of time.\n'
556
        print(help_str)
557

  
558
    def nodes(self, args):
559
        '''List all nodes.'''
560
        lg.info('available nodes are: \n%s\n',
561
                ' '.join([node.name for node in sorted(self.nodelist)]))
562

  
563
    def net(self, args):
564
        '''List network connections.'''
565
        for switch_dpid in self.mn.topo.switches():
566
            switch = self.mn.nodes[switch_dpid]
567
            lg.info('%s <->', switch.name)
510
    def help( self, args ):
511
        "Semi-useful help for CLI."
512
        helpStr = ( 'Available commands are:' + str( self.cmds ) + '\n' +
513
                   'You may also send a command to a node using:\n' +
514
                   '  <node> command {args}\n' +
515
                   'For example:\n' +
516
                   '  mininet> h0 ifconfig\n' +
517
                   '\n' +
518
                   'The interpreter automatically substitutes IP ' +
519
                   'addresses\n' +
520
                   'for node names, so commands like\n' +
521
                   '  mininet> h0 ping -c1 h1\n' +
522
                   'should work.\n' +
523
                   '\n\n' +
524
                   'Interactive commands are not really supported yet,\n' +
525
                   'so please limit commands to ones that do not\n' +
526
                   'require user interaction and will terminate\n' +
527
                   'after a reasonable amount of time.\n' )
528
        print( helpStr )
529

  
530
    def nodes( self, args ):
531
        "List all nodes."
532
        nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
533
        lg.info( 'available nodes are: \n%s\n' % nodes )
534

  
535
    def net( self, args ):
536
        "List network connections."
537
        for switchDpid in self.mn.topo.switches():
538
            switch = self.mn.nodes[ switchDpid ]
539
            lg.info( '%s <->', switch.name )
568 540
            for intf in switch.intfs:
569
                node = switch.connection[intf]
570
                lg.info(' %s' % node.name)
571
            lg.info('\n')
541
                node = switch.connection[ intf ]
542
                lg.info( ' %s' % node.name )
543
            lg.info( '\n' )
572 544

  
573
    def sh(self, args):
574
        '''Run an external shell command'''
575
        call(['sh', '-c'] + args)
545
    def sh( self, args ):
546
        "Run an external shell command"
547
        call( [ 'sh', '-c' ] + args )
576 548

  
577
    def ping_all(self, args):
578
        '''Ping between all hosts.'''
579
        self.mn.ping_all()
549
    def pingAll( self, args ):
550
        "Ping between all hosts."
551
        self.mn.pingAll()
580 552

  
581
    def ping_pair(self, args):
582
        '''Ping between first two hosts, useful for testing.'''
583
        self.mn.ping_pair()
553
    def pingPair( self, args ):
554
        "Ping between first two hosts, useful for testing."
555
        self.mn.pingPair()
584 556

  
585
    def iperf(self, args):
586
        '''Simple iperf TCP test between two hosts.'''
557
    def iperf( self, args ):
558
        "Simple iperf TCP test between two hosts."
587 559
        self.mn.iperf()
588 560

  
589
    def iperf_udp(self, args):
590
        '''Simple iperf UDP test between two hosts.'''
591
        udp_bw = args[0] if len(args) else '10M'
592
        self.mn.iperf_udp(udp_bw)
561
    def iperfUdp( self, args ):
562
        "Simple iperf UDP test between two hosts."
563
        udpBw = args[ 0 ] if len( args ) else '10M'
564
        self.mn.iperfUdp( udpBw )
593 565

  
594
    def intfs(self, args):
595
        '''List interfaces.'''
566
    def intfs( self, args ):
567
        "List interfaces."
596 568
        for node in self.mn.nodes.values():
597
            lg.info('%s: %s\n' % (node.name, ' '.join(node.intfs)))
569
            lg.info( '%s: %s\n' % ( node.name, ' '.join( node.intfs ) ) )
598 570

  
599
    def dump(self, args):
600
        '''Dump node info.'''
571
    def dump( self, args ):
572
        "Dump node info."
601 573
        for node in self.mn.nodes.values():
602
            lg.info('%s\n' % node)
574
            lg.info( '%s\n' % node )
603 575

  
604 576
    # Re-enable pylint "Unused argument: 'arg's'" messages.
605 577
    # pylint: enable-msg=W0613
606 578

  
607
    def run(self):
608
        '''Read and execute commands.'''
609
        lg.warn('*** Starting CLI:\n')
579
    def run( self ):
580
        "Read and execute commands."
581
        lg.warn( '*** Starting CLI:\n' )
610 582
        while True:
611
            lg.warn('mininet> ')
612
            input_line = sys.stdin.readline()
613
            if input_line == '':
583
            lg.warn( 'mininet> ' )
584
            inputLine = sys.stdin.readline()
585
            if inputLine == '':
614 586
                break
615
            if input_line[-1] == '\n':
616
                input_line = input_line[:-1]
617
            cmd = input_line.split(' ')
618
            first = cmd[0]
619
            rest = cmd[1:]
620
            if first in self.cmds and hasattr(self, first):
621
                getattr(self, first)(rest)
587
            if inputLine[ -1 ] == '\n':
588
                inputLine = inputLine[ :-1 ]
589
            cmd = inputLine.split( ' ' )
590
            first = cmd[ 0 ]
591
            rest = cmd[ 1: ]
592
            if first in self.cmds and hasattr( self, first ):
593
                getattr( self, first )( rest )
622 594
            elif first in self.nodemap and rest != []:
623
                node = self.nodemap[first]
595
                node = self.nodemap[ first ]
624 596
                # Substitute IP addresses for node names in command
625
                rest = [self.nodemap[arg].IP() if arg in self.nodemap else arg
626
                        for arg in rest]
627
                rest = ' '.join(rest)
597
                rest = [ self.nodemap[ arg ].IP()
598
                    if arg in self.nodemap else arg
599
                    for arg in rest ]
600
                rest = ' '.join( rest )
628 601
                # Interactive commands don't work yet, and
629 602
                # there are still issues with control-c
630
                lg.warn('*** %s: running %s\n' % (node.name, rest))
631
                node.sendCmd(rest)
603
                lg.warn( '*** %s: running %s\n' % ( node.name, rest ) )
604
                node.sendCmd( rest )
632 605
                while True:
633 606
                    try:
634 607
                        done, data = node.monitor()
635
                        lg.info('%s\n' % data)
608
                        lg.info( '%s\n' % data )
636 609
                        if done:
637 610
                            break
638 611
                    except KeyboardInterrupt:
639 612
                        node.sendInt()
640 613
            elif first == '':
641 614
                pass
642
            elif first in ['exit', 'quit']:
615
            elif first in [ 'exit', 'quit' ]:
643 616
                break
644 617
            elif first == '?':
645
                self.help(rest)
618
                self.help( rest )
646 619
            else:
647
                lg.error('CLI: unknown node or command: < %s >\n' % first)
648
            #lg.info('*** CLI: command complete\n')
620
                lg.error( 'CLI: unknown node or command: < %s >\n' % first )
621
            #lg.info( '*** CLI: command complete\n' )
649 622
        return 'exited by user command'

Also available in: Unified diff