Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 54037995

History | View | Annotate | Download (12.2 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
flush = sys.stdout.flush
7

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
230

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

    
235

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

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

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

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

    
258
class UserSwitch(Switch):
259

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

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

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

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

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

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

    
292

    
293
class KernelSwitch(Switch):
294

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

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

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

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

    
337

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

    
342
    def __init__(self, name, inNamespace = False, controller = 'controller',
343
                 cargs = '-v ptcp:', cdir = None):
344
        self.controller = controller
345
        self.cargs = cargs
346
        self.cdir = cdir
347
        Node.__init__(self, name, inNamespace = inNamespace)
348

    
349
    def start(self):
350
        '''Start <controller> <args> on controller.
351

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

    
361
    def stop(self):
362
        '''Stop controller.'''
363
        self.cmd('kill %' + self.controller)
364
        self.terminate()
365

    
366

    
367
class ControllerParams(object):
368
    '''Container for controller IP parameters.'''
369
    def __init__(self, ip, subnet_size):
370
        '''Init.
371

372
        @param ip integer, controller IP
373
        @param subnet_size integer, ex 8 for slash-8, covering 17M
374
        '''
375
        self.ip = ip
376
        self.subnet_size = subnet_size
377

    
378

    
379
class NOX(Controller):
380
    '''Controller to run a NOX application.'''
381
    def __init__(self, name, inNamespace = False, nox_args = None, **kwargs):
382
        '''Init.
383

384
        @param name name to give controller
385
        @param nox_args list of args, or single arg, to pass to NOX
386
        '''
387
        if type(nox_args) != list:
388
            nox_args = [nox_args]
389
        if not nox_args:
390
            nox_args = ['packetdump']
391
        nox_core_dir = os.environ['NOX_CORE_DIR']
392
        if not nox_core_dir:
393
            raise Exception('please set NOX_CORE_DIR env var\n')
394
        Controller.__init__(self, name,
395
            controller = nox_core_dir + '/nox_core',
396
            cargs = '--libdir=/usr/local/lib -v -i ptcp: ' + \
397
                    ' '.join(nox_args),
398
            cdir = nox_core_dir, **kwargs)