Statistics
| Branch: | Tag: | Revision:

mininet / examples / ripcordtest.py @ 89bf3103

History | View | Annotate | Download (17.2 KB)

1
#!/usr/bin/python
2

    
3
'''Run a FatTree network from the Ripcord project.
4

5
For verbose printout, set LOG_LEVEL_DEFAULT in mininet.py to logging.INFO.
6
'''
7

    
8
import os
9
import re
10
from subprocess import call
11
import sys
12
from time import sleep
13

    
14
from ripcord.topo import FatTreeTopo
15

    
16
from mininet.net import init
17
from mininet.node import Switch, Controller, Host
18
from mininet.logging_mod import lg, set_loglevel
19
from mininet.util import make_veth_pair, move_intf, retry, quietRun
20
from mininet.util import MOVEINTF_DELAY
21

    
22

    
23
class Mininet(object):
24
    '''Network emulation with hosts spawned in network namespaces.'''
25

    
26
    def __init__(self, topo, switch, host, controller, cparams,
27
                 build = True, xterms = False, cleanup = False,
28
                 in_namespace = False, switch_is_kernel = True):
29
        '''Create Mininet object.
30

31
        @param topo Topo object
32
        @param switch Switch class
33
        @param host Host class
34
        @param controller Controller class
35
        @param cparams ControllerParams object
36
        @param now build now?
37
        @param xterms if build now, spawn xterms?
38
        @param cleanup if build now, cleanup before creating?
39
        @param in_namespace spawn switches and hosts in their own namespace?
40
        '''
41
        self.topo = topo
42
        self.switch = switch
43
        self.host = host
44
        self.controller = controller
45
        self.cparams = cparams
46
        self.nodes = {} # dpid to Node{Host, Switch} objects
47
        self.controllers = {} # controller name to Controller objects
48
        self.dps = 0 # number of created kernel datapaths
49
        self.in_namespace = in_namespace
50
        self.switch_is_kernel = switch_is_kernel
51

    
52
        self.kernel = True #temporary!
53

    
54
        if build:
55
            self.build(xterms, cleanup)
56

    
57
    def _add_host(self, dpid):
58
        '''Add host.
59

60
        @param dpid DPID of host to add
61
        '''
62
        host = self.host('h_' + self.topo.name(dpid))
63
        # for now, assume one interface per host.
64
        host.intfs.append('h_' + self.topo.name(dpid) + '-eth0')
65
        self.nodes[dpid] = host
66
        #lg.info('%s ' % host.name)
67

    
68
    def _add_switch(self, dpid):
69
        '''
70
        @param dpid DPID of switch to add
71
        '''
72
        sw = None
73
        if self.switch_is_kernel:
74
            sw = self.switch('s_' + self.topo.name(dpid), 'nl:' + str(self.dps))
75
            self.dps += 1
76
        else:
77
            sw = self.switch('s_' + self.topo.name(dpid))
78
        self.nodes[dpid] = sw
79

    
80
    def _add_link(self, src, dst):
81
        '''Add link.
82

83
        @param src source DPID
84
        @param dst destination DPID
85
        '''
86
        src_port, dst_port = self.topo.port(src, dst)
87
        src_node = self.nodes[src]
88
        dst_node = self.nodes[dst]
89
        src_intf = src_node.intfName(src_port)
90
        dst_intf = dst_node.intfName(dst_port)
91
        make_veth_pair(src_intf, dst_intf)
92
        src_node.intfs.append(src_intf)
93
        dst_node.intfs.append(dst_intf)
94
        #lg.info('\n')
95
        #lg.info('added intf %s to src node %x\n' % (src_intf, src))
96
        #lg.info('added intf %s to dst node %x\n' % (dst_intf, dst))
97
        if src_node.inNamespace:
98
            #lg.info('moving src w/inNamespace set\n')
99
            retry(3, MOVEINTF_DELAY, move_intf, src_intf, src_node)
100
        if dst_node.inNamespace:
101
            #lg.info('moving dst w/inNamespace set\n')
102
            retry(3, MOVEINTF_DELAY, move_intf, dst_intf, dst_node)
103
        src_node.connection[src_intf] = (dst_node, dst_intf)
104
        dst_node.connection[dst_intf] = (src_node, src_intf)
105

    
106
    def _add_controller(self, controller):
107
        '''Add controller.
108

109
        @param controller Controller class
110
        '''
111
        controller = self.controller('c0', kernel = self.kernel)
112
        self.controllers['c0'] = controller
113

    
114
    # Control network support:
115
    #
116
    # Create an explicit control network. Currently this is only
117
    # used by the user datapath configuration.
118
    #
119
    # Notes:
120
    #
121
    # 1. If the controller and switches are in the same (e.g. root)
122
    #    namespace, they can just use the loopback connection.
123
    #    We may wish to do this for the user datapath as well as the
124
    #    kernel datapath.
125
    #
126
    # 2. If we can get unix domain sockets to work, we can use them
127
    #    instead of an explicit control network.
128
    #
129
    # 3. Instead of routing, we could bridge or use 'in-band' control.
130
    #
131
    # 4. Even if we dispense with this in general, it could still be
132
    #    useful for people who wish to simulate a separate control
133
    #    network (since real networks may need one!)
134

    
135
    def _configureControlNetwork(self):
136
        '''Configure control network.'''
137
        self._configureRoutedControlNetwork()
138

    
139
    def _configureRoutedControlNetwork(self):
140
        '''Configure a routed control network on controller and switches.
141

142
        For use with the user datapath only right now.
143

144
        @todo(brandonh) Test this code and verify that user-space works!
145
        '''
146
        # params were: controller, switches, ips
147

    
148
        controller = self.controllers['c0']
149
        lg.info('%s <-> ' % controller.name)
150
        for switch_dpid in self.topo.switches():
151
            switch = self.nodes[switch_dpid]
152
            lg.info('%s ' % switch.name)
153
            sip = self.topo.ip(switch_dpid)#ips.next()
154
            sintf = switch.intfs[0]
155
            node, cintf = switch.connection[sintf]
156
            if node != controller:
157
                lg.error('*** Error: switch %s not connected to correct'
158
                         'controller' %
159
                         switch.name)
160
                exit(1)
161
            controller.setIP(cintf, self.cparams.ip, '/' +
162
                             self.cparams.subnet_size)
163
            switch.setIP(sintf, sip, '/' + self.cparams.subnet_size)
164
            controller.setHostRoute(sip, cintf)
165
            switch.setHostRoute(self.cparams.ip, sintf)
166
        lg.info('\n')
167
        lg.info('*** Testing control network\n')
168
        while not controller.intfIsUp(controller.intfs[0]):
169
            lg.info('*** Waiting for %s to come up\n', controller.intfs[0])
170
            sleep(1)
171
        for switch_dpid in self.topo.switches():
172
            switch = self.nodes[switch_dpid]
173
            while not switch.intfIsUp(switch.intfs[0]):
174
                lg.info('*** Waiting for %s to come up\n' % switch.intfs[0])
175
                sleep(1)
176
            if self.ping_test(hosts=[switch, controller]) != 0:
177
                lg.error('*** Error: control network test failed\n')
178
                exit(1)
179
        lg.info('\n')
180

    
181
    def _config_hosts( self ):
182
        '''Configure a set of hosts.'''
183
        # params were: hosts, ips
184
        for host_dpid in self.topo.hosts():
185
            host = self.nodes[host_dpid]
186
            hintf = host.intfs[0]
187
            host.setIP(hintf, self.topo.ip(host_dpid),
188
                       '/' + str(self.cparams.subnet_size))
189
            host.setDefaultRoute(hintf)
190
            # You're low priority, dude!
191
            quietRun('renice +18 -p ' + repr(host.pid))
192
            lg.info('%s ', host.name)
193
        lg.info('\n')
194

    
195
    def build(self, xterms, cleanup):
196
        '''Build mininet.
197

198
        At the end of this function, everything should be connected and up.
199

200
        @param xterms spawn xterms on build?
201
        @param cleanup cleanup before creating?
202
        '''
203
        if cleanup:
204
            pass # cleanup
205
        # validate topo?
206
        kernel = self.kernel
207
        if kernel:
208
            lg.info('*** Using kernel datapath\n')
209
        else:
210
            lg.info('*** Using user datapath\n')
211
        lg.info('*** Adding controller\n')
212
        self._add_controller(self.controller)
213
        lg.info('*** Creating network\n')
214
        lg.info('*** Adding hosts:\n')
215
        for host in sorted(self.topo.hosts()):
216
            self._add_host(host)
217
            lg.info('0x%x ' % host)
218
        lg.info('\n*** Adding switches:\n')
219
        for switch in sorted(self.topo.switches()):
220
            self._add_switch(switch)
221
            lg.info('0x%x ' % switch)
222
        lg.info('\n*** Adding edges: ')
223
        for src, dst in sorted(self.topo.edges()):
224
            self._add_link(src, dst)
225
            lg.info('(0x%x, 0x%x) ' % (src, dst))
226
        lg.info('\n')
227

    
228
        if not kernel:
229
            lg.info('*** Configuring control network\n')
230
            self._configureControlNetwork()
231

    
232
        lg.info('*** Configuring hosts\n')
233
        self._config_hosts()
234

    
235
        if xterms:
236
            pass # build xterms
237

    
238
    def start(self):
239
        '''Start controller and switches\n'''
240
        lg.info('*** Starting controller\n')
241
        self.controllers['c0'].start()
242
        #for controller in self.controllers:
243
        #    controller.start()
244
        lg.info('*** Starting %s switches\n' % len(self.topo.switches()))
245
        for switch_dpid in self.topo.switches():
246
            switch = self.nodes[switch_dpid]
247
            #lg.info('switch = %s' % switch)
248
            lg.info('0x%x ' % switch_dpid)
249
            switch.start(self.controllers['c0'])
250
        lg.info('\n')
251

    
252
    def stop(self):
253
        '''Stop the controller(s), switches and hosts\n'''
254
        lg.info('*** Stopping %i hosts\n' % len(self.topo.hosts()))
255
        for host_dpid in self.topo.hosts():
256
            host = self.nodes[host_dpid]
257
            lg.info('%s ' % host.name)
258
            host.terminate()
259
        lg.info('\n')
260
        lg.info('*** Stopping %i switches\n' % len(self.topo.switches()))
261
        for switch_dpid in self.topo.switches():
262
            switch = self.nodes[switch_dpid]
263
            lg.info('%s' % switch.name)
264
            switch.stop()
265
        lg.info('\n')
266
        lg.info('*** Stopping controller\n')
267
        #for controller in self.controllers.iteriterms():
268
        self.controllers['c0'].stop()
269
        lg.info('*** Test complete\n')
270

    
271
    def run(self, test, **params):
272
        '''Perform a complete start/test/stop cycle.'''
273
        self.start()
274
        lg.info('*** Running test\n')
275
        result = test(self, **params)
276
        self.stop()
277
        return result
278

    
279
    @staticmethod
280
    def _parse_ping(pingOutput):
281
        '''Parse ping output and return packets sent, received.'''
282
        r = r'(\d+) packets transmitted, (\d+) received'
283
        m = re.search( r, pingOutput )
284
        if m == None:
285
            lg.error('*** Error: could not parse ping output: %s\n' %
286
                     pingOutput)
287
            exit(1)
288
        sent, received = int(m.group(1)), int(m.group(2))
289
        return sent, received
290

    
291
    def ping_test(self, hosts = None, verbose = False):
292
        '''Ping between all specified hosts.
293

294
        @param hosts list of host DPIDs
295
        @param verbose verbose printing
296
        @return ploss packet loss percentage
297
        '''
298
        #self.start()
299
        # check if running - only then, start?
300
        packets = 0
301
        lost = 0
302
        if not hosts:
303
            hosts = self.topo.hosts()
304
        for node_dpid in hosts:
305
            node = self.nodes[node_dpid]
306
            if verbose:
307
                lg.info('%s -> ' % node.name)
308
            for dest_dpid in hosts:
309
                dest = self.nodes[dest_dpid]
310
                if node != dest:
311
                    result = node.cmd('ping -c1 ' + dest.IP())
312
                    sent, received = self._parse_ping(result)
313
                    packets += sent
314
                    if received > sent:
315
                        lg.error('*** Error: received too many packets')
316
                        lg.error('%s' % result)
317
                        node.cmdPrint('route')
318
                        exit( 1 )
319
                    lost += sent - received
320
                    if verbose:
321
                        lg.info(('%s ' % dest.name) if received else 'X ')
322
            if verbose:
323
                lg.info('\n')
324
            ploss = 100 * lost/packets
325
        if verbose:
326
            lg.info('%d%% packet loss (%d/%d lost)\n' % (ploss, lost, packets))
327
            #flush()
328
        #self.stop()
329
        return ploss
330

    
331
    def interact(self):
332
        '''Start network and run our simple CLI.'''
333
        self.run(Cli)
334

    
335

    
336
class Cli(object):
337
    '''Simple command-line interface to talk to nodes.'''
338
    cmds = ['?', 'help', 'nodes', 'net', 'sh', 'ping_all', 'exit', \
339
            'ping_pair'] #'iperf'
340

    
341
    def __init__(self, mininet):
342
        self.mn = mininet
343
        self.nodemap = {} # map names to Node objects
344
        for node in self.mn.nodes.values():
345
            self.nodemap[node.name] = node
346
        self.nodemap['c0'] = self.mn.controllers['c0']
347
        self.nodelist = self.nodemap.values()
348
        self.run()
349

    
350
    # Commands
351
    def help(self, args):
352
        '''Semi-useful help for CLI.'''
353
        help_str = 'Available commands are:' + str(self.cmds) + '\n' + \
354
                   'You may also send a command to a node using:\n' + \
355
                   '  <node> command {args}\n' + \
356
                   'For example:\n' + \
357
                   '  mininet> h0 ifconfig\n' + \
358
                   '\n' + \
359
                   'The interpreter automatically substitutes IP ' + \
360
                   'addresses\n' + \
361
                   'for node names, so commands like\n' + \
362
                   '  mininet> h0 ping -c1 h1\n' + \
363
                   'should work.\n' + \
364
                   '\n\n' + \
365
                   'Interactive commands are not really supported yet,\n' + \
366
                   'so please limit commands to ones that do not\n' + \
367
                   'require user interaction and will terminate\n' + \
368
                   'after a reasonable amount of time.\n'
369
        print(help_str)
370

    
371
    def nodes(self, args):
372
        '''List all nodes.'''
373
        lg.info('available nodes are: \n%s\n',
374
                ' '.join([node.name for node in sorted(self.nodelist)]))
375

    
376
    def net(self, args):
377
        '''List network connection.'''
378
        for switch_dpid in self.mn.topo.switches():
379
            switch = self.mn.nodes[switch_dpid]
380
            lg.info('%s <->', switch.name)
381
            for intf in switch.intfs:
382
                node, remoteIntf = switch.connection[intf]
383
                lg.info(' %s' % node.name)
384
            lg.info('\n')
385

    
386
    def sh(self, args):
387
        '''Run an external shell command'''
388
        call( [ 'sh', '-c' ] + args )
389

    
390
    def ping_all(self, args):
391
        '''Ping between all hosts.'''
392
        self.mn.ping_test(verbose = True)
393

    
394
    def ping_pair(self, args):
395
        '''Ping between first two hosts, useful for testing.'''
396
        hosts_unsorted = sorted(self.mn.topo.hosts())
397
        hosts = [hosts_unsorted[0], hosts_unsorted[1]]
398
        self.mn.ping_test(hosts = hosts, verbose = True)
399

    
400
    def run(self):
401
        '''Read and execute commands.'''
402
        lg.warn('*** Starting CLI:\n')
403
        while True:
404
            lg.warn('mininet> ')
405
            input = sys.stdin.readline()
406
            if input == '':
407
                break
408
            if input[-1] == '\n':
409
                input = input[:-1]
410
            cmd = input.split(' ')
411
            first = cmd[0]
412
            rest = cmd[1:]
413
            if first in self.cmds and hasattr(self, first):
414
                getattr(self, first)(rest)
415
            elif first in self.nodemap and rest != []:
416
                node = self.nodemap[first]
417
                # Substitute IP addresses for node names in command
418
                rest = [self.nodemap[arg].IP() if arg in self.nodemap else arg
419
                        for arg in rest]
420
                rest = ' '.join(rest)
421
                # Interactive commands don't work yet, and
422
                # there are still issues with control-c
423
                lg.warn('*** %s: running %s\n' % (node.name, rest))
424
                node.sendCmd( rest )
425
                while True:
426
                    try:
427
                        done, data = node.monitor()
428
                        lg.info('%s\n' % data)
429
                        if done:
430
                            break
431
                    except KeyboardInterrupt:
432
                        node.sendInt()
433
            elif first == '':
434
                pass
435
            elif first in ['exit', 'quit']:
436
                break
437
            elif first == '?':
438
                self.help( rest )
439
            else:
440
                lg.error('CLI: unknown node or command: < %s >\n' % first)
441
            #lg.info('*** CLI: command complete\n')
442
        return 'exited by user command'
443

    
444

    
445
class NOXController(Controller):
446
    '''Controller to run a NOX application.'''
447
    def __init__(self, name, nox_args = None, **kwargs):
448
        '''Init.
449

450
        @param name name to give controller
451
        @param nox_args list of args to use with NOX
452
        '''
453
        if not nox_args:
454
            nox_args = ['packetdump']
455
        nox_core_dir = os.environ['NOX_CORE_DIR']
456
        if not nox_core_dir:
457
            raise Exception('please set NOX_CORE_DIR env var\n')
458
        Controller.__init__(self, name,
459
            controller = nox_core_dir + '/nox_core',
460
            cargs = '--libdir=/usr/local/lib -v -i ptcp: ' + \
461
                    ' '.join(nox_args),
462
            cdir = nox_core_dir, **kwargs)
463

    
464

    
465
class ControllerParams(object):
466
    '''Container for controller IP parameters.'''
467
    def __init__(self, ip, subnet_size):
468
        '''Init.
469

470
        @param ip integer, controller IP
471
        @param subnet_size integer, ex 8 for slash-8, covering 17M
472
        '''
473
        self.ip = ip
474
        self.subnet_size = subnet_size
475

    
476

    
477
if __name__ == '__main__':
478
    set_loglevel('info')
479
    init()
480
    controller_params = ControllerParams(0x0a000000, 8) # 10.0.0.0/8
481
    mn = Mininet(FatTreeTopo(4), Switch, Host, NOXController,
482
                      controller_params)
483
    mn.interact()