Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 60d9ead6

History | View | Annotate | Download (13.3 KB)

1
#!/usr/bin/env python
2
'''Node objects for Mininet.'''
3

    
4
from subprocess import Popen, PIPE, STDOUT
5
import os, signal, sys, select
6

    
7
flush = sys.stdout.flush
8

    
9
from mininet.logging_mod import lg
10
from mininet.util import quietRun, macColonHex, ipStr
11

    
12
class Node(object):
13
    '''A virtual network node is simply a shell in a network namespace.
14
       We communicate with it using pipes.'''
15
    inToNode = {}
16
    outToNode = {}
17

    
18
    def __init__(self, name, inNamespace = True):
19
        self.name = name
20
        closeFds = False # speed vs. memory use
21
        # xpg_echo is needed so we can echo our sentinel in sendCmd
22
        cmd = ['/bin/bash', '-O', 'xpg_echo']
23
        self.inNamespace = inNamespace
24
        if self.inNamespace:
25
            cmd = ['netns'] + cmd
26
        self.shell = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = STDOUT,
27
            close_fds = closeFds)
28
        self.stdin = self.shell.stdin
29
        self.stdout = self.shell.stdout
30
        self.pollOut = select.poll()
31
        self.pollOut.register(self.stdout)
32
        # Maintain mapping between file descriptors and nodes
33
        # This could be useful for monitoring multiple nodes
34
        # using select.poll()
35
        self.outToNode[self.stdout.fileno()] = self
36
        self.inToNode[self.stdin.fileno()] = self
37
        self.pid = self.shell.pid
38
        self.intfCount = 0
39
        self.intfs = [] # list of interface names, as strings
40
        self.ips = {} # dict of interfaces to ip addresses as strings
41
        self.connection = {}
42
        self.waiting = False
43
        self.execed = False
44

    
45
    def fdToNode(self, f):
46
        '''Insert docstring.
47

48
        @param f unknown
49
        @return bool unknown
50
        '''
51
        node = self.outToNode.get(f)
52
        return node or self.inToNode.get(f)
53

    
54
    def cleanup(self):
55
        '''Help python collect its garbage.'''
56
        self.shell = None
57

    
58
    # Subshell I/O, commands and control
59
    def read(self, fileno_max):
60
        '''Insert docstring.
61

62
        @param fileno_max unknown
63
        '''
64
        return os.read(self.stdout.fileno(), fileno_max)
65

    
66
    def write(self, data):
67
        '''Write data to node.
68

69
        @param data string
70
        '''
71
        os.write(self.stdin.fileno(), data)
72

    
73
    def terminate(self):
74
        '''Send kill signal to Node and cleanup after it.'''
75
        os.kill(self.pid, signal.SIGKILL)
76
        self.cleanup()
77

    
78
    def stop(self):
79
        '''Stop node.'''
80
        self.terminate()
81

    
82
    def waitReadable(self):
83
        '''Poll on node.'''
84
        self.pollOut.poll()
85

    
86
    def sendCmd(self, cmd):
87
        '''Send a command, followed by a command to echo a sentinel,
88
           and return without waiting for the command to complete.'''
89
        assert not self.waiting
90
        if cmd[-1] == '&':
91
            separator = '&'
92
            cmd = cmd[:-1]
93
        else:
94
            separator = ';'
95
        if isinstance(cmd, list):
96
            cmd = ' '.join(cmd)
97
        self.write(cmd + separator + ' echo -n "\\0177" \n')
98
        self.waiting = True
99

    
100
    def monitor(self):
101
        '''Monitor the output of a command, returning (done, data).'''
102
        assert self.waiting
103
        self.waitReadable()
104
        data = self.read(1024)
105
        if len(data) > 0 and data[-1] == chr(0177):
106
            self.waiting = False
107
            return True, data[:-1]
108
        else:
109
            return False, data
110

    
111
    def sendInt(self):
112
        '''Send ^C, hopefully interrupting a running subprocess.'''
113
        self.write(chr(3))
114

    
115
    def waitOutput(self):
116
        '''Wait for a command to complete.
117
        
118
        Completion is signaled by a sentinel character, ASCII(127) appearing in
119
        the output stream.  Wait for the sentinel and return the output,
120
        including trailing newline.
121
        '''
122
        assert self.waiting
123
        output = ''
124
        while True:
125
            self.waitReadable()
126
            data = self.read(1024)
127
            if len(data) > 0  and data[-1] == chr(0177):
128
                output += data[:-1]
129
                break
130
            else: output += data
131
        self.waiting = False
132
        return output
133

    
134
    def cmd(self, cmd):
135
        '''Send a command, wait for output, and return it.
136

137
        @param cmd string
138
        '''
139
        self.sendCmd(cmd)
140
        return self.waitOutput()
141

    
142
    def cmdPrint(self, cmd):
143
        '''Call cmd and printing its output
144
        
145
        @param cmd string
146
        '''
147
        #lg.info('*** %s : %s', self.name, cmd)
148
        result = self.cmd(cmd)
149
        #lg.info('%s\n', result)
150
        return result
151

    
152
    # Interface management, configuration, and routing
153
    def intfName(self, n):
154
        '''Construct a canonical interface name node-intf for interface N.'''
155
        return self.name + '-eth' + repr(n)
156

    
157
    def newIntf(self):
158
        '''Reserve and return a new interface name.'''
159
        intfName = self.intfName(self.intfCount)
160
        self.intfCount += 1
161
        self.intfs += [intfName]
162
        return intfName
163

    
164
    def setMAC(self, intf, mac):
165
        '''Set the MAC address for an interface.
166

167
        @param mac MAC address as unsigned int
168
        '''
169
        mac_str = macColonHex(mac)
170
        result = self.cmd(['ifconfig', intf, 'down'])
171
        result += self.cmd(['ifconfig', intf, 'hw', 'ether', mac_str])
172
        result += self.cmd(['ifconfig', intf, 'up'])
173
        return result
174

    
175
    def setARP(self, ip, mac):
176
        '''Add an ARP entry.
177

178
        @param ip IP address as unsigned int
179
        @param mac MAC address as unsigned int
180
        '''
181
        ip_str = ipStr(ip)
182
        mac_str = macColonHex(mac)
183
        result = self.cmd(['arp', '-s', ip_str, mac_str])
184
        return result
185

    
186
    def setIP(self, intf, ip, bits):
187
        '''Set the IP address for an interface.
188

189
        @param intf string, interface name
190
        @param ip IP address as a string
191
        @param bits
192
        '''
193
        result = self.cmd(['ifconfig', intf, ip + bits, 'up'])
194
        self.ips[intf] = ip
195
        return result
196

    
197
    def setHostRoute(self, ip, intf):
198
        '''Add route to host.
199

200
        @param ip IP address as dotted decimal
201
        @param intf string, interface name
202
        '''
203
        return self.cmd('route add -host ' + ip + ' dev ' + intf)
204

    
205
    def setDefaultRoute(self, intf):
206
        '''Set the default route to go through intf.
207

208
        @param intf string, interface name
209
        '''
210
        self.cmd('ip route flush')
211
        return self.cmd('route add default ' + intf)
212

    
213
    def IP(self):
214
        '''Return IP address of first interface'''
215
        if len(self.intfs) > 0:
216
            return self.ips.get(self.intfs[ 0 ], None)
217

    
218
    def intfIsUp(self):
219
        '''Check if one of our interfaces is up.'''
220
        return 'UP' in self.cmd('ifconfig ' + self.intfs[0])
221

    
222
    # Other methods
223
    def __str__(self):
224
        result = self.name + ':'
225
        if self.IP():
226
            result += ' IP=' + self.IP()
227
        result += ' intfs=' + ','.join(self.intfs)
228
        result += ' waiting=' + repr(self.waiting)
229
        return result
230

    
231

    
232
class Host(Node):
233
    '''A host is simply a Node.'''
234
    pass
235

    
236

    
237
class Switch(Node):
238
    '''A Switch is a Node that is running (or has execed)
239
       an OpenFlow switch.'''
240

    
241
    def sendCmd(self, cmd):
242
        '''Send command to Node.
243

244
        @param cmd string
245
        '''
246
        if not self.execed:
247
            return Node.sendCmd(self, cmd)
248
        else:
249
            lg.error('*** Error: %s has execed and cannot accept commands' %
250
                     self.name)
251

    
252
    def monitor(self):
253
        '''Monitor node.'''
254
        if not self.execed:
255
            return Node.monitor(self)
256
        else:
257
            return True, ''
258

    
259
class UserSwitch(Switch):
260

    
261
    def __init__(self, name):
262
        '''Init.
263

264
        @param name
265
        '''
266
        Node.__init__(self, name, inNamespace = True)
267

    
268
    def start(self, controllers):
269
        '''Start OpenFlow reference user datapath.
270

271
        Log to /tmp/sN-{ofd,ofp}.log.
272

273
        @param controllers dict of controller names to objects
274
        '''
275
        if 'c0' not in controller:
276
            raise Exception('User datapath start() requires controller c0')
277
        controller = controllers['c0']
278
        ofdlog = '/tmp/' + self.name + '-ofd.log'
279
        ofplog = '/tmp/' + self.name + '-ofp.log'
280
        self.cmd('ifconfig lo up')
281
        intfs = self.intfs[1:] # 0 is mgmt interface
282
        self.cmdPrint('ofdatapath -i ' + ','.join(intfs) +
283
                      ' ptcp: 1> ' + ofdlog + ' 2> ' + ofdlog + ' &')
284
        self.cmdPrint('ofprotocol tcp:' + controller.IP() +
285
                      ' tcp:localhost --fail=closed 1> ' + ofplog + ' 2>' +
286
                      ofplog + ' &')
287

    
288
    def stop(self):
289
        '''Stop OpenFlow reference user datapath.'''
290
        self.cmd('kill %ofdatapath')
291
        self.cmd('kill %ofprotocol')
292

    
293

    
294
class KernelSwitch(Switch):
295

    
296
    def __init__(self, name, dp = None, dpid = None):
297
        '''Init.
298

299
        @param name
300
        @param dp netlink id (0, 1, 2, ...)
301
        @param dpid datapath ID as unsigned int; random value if None
302
        '''
303
        Node.__init__(self, name, inNamespace = False)
304
        self.dp = dp
305
        self.dpid = dpid
306

    
307
    def start(self, controllers):
308
        '''Start up reference kernel datapath.'''
309
        ofplog = '/tmp/' + self.name + '-ofp.log'
310
        quietRun('ifconfig lo up')
311
        # Delete local datapath if it exists;
312
        # then create a new one monitoring the given interfaces
313
        quietRun('dpctl deldp nl:%i' % self.dp)
314
        self.cmdPrint('dpctl adddp nl:%i' % self.dp)
315
        if self.dpid:
316
            intf = 'of%i' % self.dp
317
            mac_str = macColonHex(self.dpid)
318
            self.cmd(['ifconfig', intf, 'hw', 'ether', mac_str])
319
        self.cmdPrint('dpctl addif nl:' + str(self.dp) + ' ' +
320
                      ' '.join(self.intfs))
321
        # Run protocol daemon
322
        self.cmdPrint('ofprotocol nl:' + str(self.dp) + ' tcp:' +
323
                      controllers['c0'].IP()+':'+str(controllers['c0'].port) +
324
                      ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &')
325
        self.execed = False # XXX until I fix it
326

    
327
    def stop(self):
328
        '''Terminate reference kernel datapath.'''
329
        quietRun('dpctl deldp nl:%i' % self.dp)
330
        # In theory the interfaces should go away after we shut down.
331
        # However, this takes time, so we're better off to remove them
332
        # explicitly so that we won't get errors if we run before they
333
        # have been removed by the kernel. Unfortunately this is very slow.
334
        self.cmd('kill %ofprotocol')
335
        for intf in self.intfs:
336
            quietRun('ip link del ' + intf)
337
            lg.info('.')
338

    
339

    
340
class Controller(Node):
341
    '''A Controller is a Node that is running (or has execed) an
342
      OpenFlow controller.'''
343

    
344
    def __init__(self, name, inNamespace = False, controller = 'controller',
345
                 cargs = '-v ptcp:', cdir = None, ip_address="127.0.0.1",
346
                 port = 6633):
347
        self.controller = controller
348
        self.cargs = cargs
349
        self.cdir = cdir
350
        self.ip_address = ip_address
351
        self.port = port
352
        Node.__init__(self, name, inNamespace = inNamespace)
353

    
354
    def start(self):
355
        '''Start <controller> <args> on controller.
356

357
        Log to /tmp/cN.log
358
        '''
359
        cout = '/tmp/' + self.name + '.log'
360
        if self.cdir is not None:
361
            self.cmdPrint('cd ' + self.cdir)
362
        self.cmdPrint(self.controller + ' ' + self.cargs +
363
            ' 1> ' + cout + ' 2> ' + cout + ' &')
364
        self.execed = False # XXX Until I fix it
365

    
366
    def stop(self):
367
        '''Stop controller.'''
368
        self.cmd('kill %' + self.controller)
369
        self.terminate()
370

    
371
    def IP(self):
372
        '''Return IP address of the Controller'''
373
        return self.ip_address
374

    
375

    
376
class ControllerParams(object):
377
    '''Container for controller IP parameters.'''
378
    def __init__(self, ip, subnet_size):
379
        '''Init.
380

381
        @param ip integer, controller IP
382
        @param subnet_size integer, ex 8 for slash-8, covering 17M
383
        '''
384
        self.ip = ip
385
        self.subnet_size = subnet_size
386

    
387

    
388
class NOX(Controller):
389
    '''Controller to run a NOX application.'''
390
    def __init__(self, name, inNamespace = False, nox_args = None, **kwargs):
391
        '''Init.
392

393
        @param name name to give controller
394
        @param nox_args list of args, or single arg, to pass to NOX
395
        '''
396
        if type(nox_args) != list:
397
            nox_args = [nox_args]
398
        if not nox_args:
399
            nox_args = ['packetdump']
400
        nox_core_dir = os.environ['NOX_CORE_DIR']
401
        if not nox_core_dir:
402
            raise Exception('please set NOX_CORE_DIR env var\n')
403
        Controller.__init__(self, name,
404
            controller = nox_core_dir + '/nox_core',
405
            cargs = '--libdir=/usr/local/lib -v -i ptcp: ' + \
406
                    ' '.join(nox_args),
407
            cdir = nox_core_dir, **kwargs)
408

    
409
class RemoteController(Controller):
410
    '''Controller running remotely.'''
411
    def __init__(self, name, inNamespace = False, ip_address = None, port = 6633):
412
        '''Init.
413

414
        @param name name to give controller
415
        @param ip_address the IP address where the remote controller is
416
            listening
417
        @param port the port where the remote controller is listening
418
        '''
419
        if not ip_address:
420
            raise Exception('please set ip_address\n')
421
        Controller.__init__(self, name, ip_address = ip_address, port = port)
422

    
423
    def start(self):
424
        '''Overridden to do nothing.'''
425
        return
426

    
427
    def stop(self):
428
        '''Overridden to do nothing.'''
429
        return