Revision 89bf3103

View differences:

examples/ripcordtest.py
13 13

  
14 14
from ripcord.topo import FatTreeTopo
15 15

  
16
from mininet.net import Switch, Controller, Host, init
16
from mininet.net import init
17
from mininet.node import Switch, Controller, Host
17 18
from mininet.logging_mod import lg, set_loglevel
18 19
from mininet.util import make_veth_pair, move_intf, retry, quietRun
19 20
from mininet.util import MOVEINTF_DELAY
mininet/net.py
74 74
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
75 75

  
76 76
from mininet.logging_mod import lg, set_loglevel
77
from mininet.node import Node, Host, Controller, Switch
77 78
from mininet.util import run, checkRun, quietRun, makeIntfPair, moveIntf
78 79
from mininet.util import createLink
79 80

  
80 81
DATAPATHS = ['user', 'kernel']
81 82

  
82

  
83
class Node( object ):
84
   """A virtual network node is simply a shell in a network namespace.
85
      We communicate with it using pipes."""
86
   inToNode = {}
87
   outToNode = {}
88
   def __init__( self, name, inNamespace=True ):
89
      self.name = name
90
      closeFds = False # speed vs. memory use
91
      # xpg_echo is needed so we can echo our sentinel in sendCmd
92
      cmd = [ '/bin/bash', '-O', 'xpg_echo' ]
93
      self.inNamespace = inNamespace
94
      if self.inNamespace: cmd = [ 'netns' ] + cmd
95
      self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
96
         close_fds=closeFds )
97
      self.stdin = self.shell.stdin
98
      self.stdout = self.shell.stdout
99
      self.pollOut = select.poll() 
100
      self.pollOut.register( self.stdout )
101
      # Maintain mapping between file descriptors and nodes
102
      # This could be useful for monitoring multiple nodes
103
      # using select.poll()
104
      self.outToNode[ self.stdout.fileno() ] = self
105
      self.inToNode[ self.stdin.fileno() ] = self
106
      self.pid = self.shell.pid
107
      self.intfCount = 0
108
      self.intfs = [] # list of interface names, as strings
109
      self.ips = {}
110
      self.connection = {}
111
      self.waiting = False
112
      self.execed = False
113
   def fdToNode( self, f ):
114
      node = self.outToNode.get( f )
115
      return node or self.inToNode.get( f )
116
   def cleanup( self ):
117
      # Help python collect its garbage
118
      self.shell = None
119
   # Subshell I/O, commands and control
120
   def read( self, max ): return os.read( self.stdout.fileno(), max )
121
   def write( self, data ): os.write( self.stdin.fileno(), data )
122
   def terminate( self ):
123
      os.kill( self.pid, signal.SIGKILL )
124
      self.cleanup()
125
   def stop( self ): self.terminate()
126
   def waitReadable( self ): self.pollOut.poll()
127
   def sendCmd( self, cmd ):
128
      """Send a command, followed by a command to echo a sentinel,
129
         and return without waiting for the command to complete."""
130
      assert not self.waiting
131
      if cmd[ -1 ] == '&':
132
         separator = '&'
133
         cmd = cmd[ : -1 ]
134
      else: separator = ';'
135
      if isinstance( cmd, list): cmd = ' '.join( cmd )
136
      self.write( cmd + separator + " echo -n '\\0177' \n")
137
      self.waiting = True
138
   def monitor( self ):
139
      "Monitor a command's output, returning (done, data)."
140
      assert self.waiting
141
      self.waitReadable()
142
      data = self.read( 1024 )
143
      if len( data ) > 0 and data[ -1 ] == chr( 0177 ):
144
         self.waiting = False
145
         return True, data[ : -1 ]
146
      else:
147
         return False, data
148
   def sendInt( self ):
149
      "Send ^C, hopefully interrupting a running subprocess."
150
      self.write( chr( 3 ) )
151
   def waitOutput( self ):
152
      """Wait for a command to complete (signaled by a sentinel
153
      character, ASCII(127) appearing in the output stream) and return
154
      the output, including trailing newline."""
155
      assert self.waiting
156
      output = ""
157
      while True:
158
         self.waitReadable()
159
         data = self.read( 1024 )
160
         if len(data) > 0  and data[ -1 ] == chr( 0177 ): 
161
            output += data[ : -1 ]
162
            break
163
         else: output += data
164
      self.waiting = False
165
      return output
166
   def cmd( self, cmd ):
167
      "Send a command, wait for output, and return it."
168
      self.sendCmd( cmd )
169
      return self.waitOutput()
170
   def cmdPrint( self, cmd ):
171
      "Call cmd, printing the command and output"
172
      #lg.info("*** %s : %s", self.name, cmd)
173
      result = self.cmd( cmd )
174
      #lg.info("%s\n", result)
175
      return result
176
   # Interface management, configuration, and routing
177
   def intfName( self, n):
178
      "Construct a canonical interface name node-intf for interface N."
179
      return self.name + '-eth' + `n`
180
   def newIntf( self ):
181
      "Reserve and return a new interface name for this node."
182
      intfName = self.intfName( self.intfCount)
183
      self.intfCount += 1
184
      self.intfs += [ intfName ]
185
      return intfName
186
   def setIP( self, intf, ip, bits ):
187
      "Set an interface's IP address."
188
      result = self.cmd( [ 'ifconfig', intf, ip + bits, 'up' ] )
189
      self.ips[ intf ] = ip
190
      return result
191
   def setHostRoute( self, ip, intf ):
192
      "Add a route to the given IP address via intf."
193
      return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
194
   def setDefaultRoute( self, intf ):
195
      "Set the default route to go through intf."
196
      self.cmd( 'ip route flush' )
197
      return self.cmd( 'route add default ' + intf )
198
   def IP( self ):
199
      "Return IP address of first interface"
200
      if len( self.intfs ) > 0:
201
         return self.ips.get( self.intfs[ 0 ], None )
202
   def intfIsUp( self, intf ):
203
      "Check if one of our interfaces is up."
204
      return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ 0 ] )
205
   # Other methods  
206
   def __str__( self ): 
207
      result = self.name + ":"
208
      if self.IP():
209
          result += " IP=" + self.IP()
210
      result += " intfs=" + ','.join( self.intfs )
211
      result += " waiting=" +  `self.waiting`
212
      return result
213

  
214

  
215

  
216
class Host( Node ):
217
   """A host is simply a Node."""
218
   pass
219
      
220
class Controller( Node ):
221
   """A Controller is a Node that is running (or has execed) an 
222
      OpenFlow controller."""
223
   def __init__( self, name, kernel=True, controller='controller',
224
      cargs='-v ptcp:', cdir=None ):
225
      self.controller = controller
226
      self.cargs = cargs
227
      self.cdir = cdir
228
      Node.__init__( self, name, inNamespace=( not kernel ) )
229
   def start( self ):
230
      "Start <controller> <args> on controller, logging to /tmp/cN.log"
231
      cout = '/tmp/' + self.name + '.log'
232
      if self.cdir is not None:
233
         self.cmdPrint( 'cd ' + self.cdir )
234
      self.cmdPrint( self.controller + ' ' + self.cargs + 
235
         ' 1> ' + cout + ' 2> ' + cout + ' &' )
236
      self.execed = False # XXX Until I fix it
237
   def stop( self, controller='controller' ):
238
      "Stop controller cprog on controller"
239
      self.cmd( "kill %" + controller )  
240
      self.terminate()
241
         
242
class Switch( Node ):
243
   """A Switch is a Node that is running (or has execed)
244
      an OpenFlow switch."""
245
   def __init__( self, name, datapath=None ):
246
      self.dp = datapath
247
      Node.__init__( self, name, inNamespace=( datapath == None ) )
248
   def startUserDatapath( self, controller ):
249
      """Start OpenFlow reference user datapath, 
250
         logging to /tmp/sN-{ofd,ofp}.log"""
251
      ofdlog = '/tmp/' + self.name + '-ofd.log'
252
      ofplog = '/tmp/' + self.name + '-ofp.log'
253
      self.cmd( 'ifconfig lo up' )
254
      intfs = self.intfs[ 1 : ] # 0 is mgmt interface
255
      self.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) +
256
       ' ptcp: 1> ' + ofdlog + ' 2> '+ ofdlog + ' &' )
257
      self.cmdPrint( 'ofprotocol tcp:' + controller.IP() +
258
         ' tcp:localhost --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
259
   def stopUserDatapath( self ):
260
      "Stop OpenFlow reference user datapath."
261
      self.cmd( "kill %ofdatapath" )
262
      self.cmd( "kill %ofprotocol" )
263
   def startKernelDatapath( self, controller):
264
      "Start up switch using OpenFlow reference kernel datapath."
265
      ofplog = '/tmp/' + self.name + '-ofp.log'
266
      quietRun( 'ifconfig lo up' )
267
      # Delete local datapath if it exists;
268
      # then create a new one monitoring the given interfaces
269
      quietRun( 'dpctl deldp ' + self.dp )
270
      self.cmdPrint( 'dpctl adddp ' + self.dp )
271
      self.cmdPrint( 'dpctl addif ' + self.dp + ' ' + ' '.join( self.intfs ) )
272
      # Run protocol daemon
273
      self.cmdPrint( 'ofprotocol' +
274
         ' ' + self.dp + ' tcp:127.0.0.1 ' + 
275
         ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
276
      self.execed = False # XXX until I fix it
277
   def stopKernelDatapath( self ):
278
      "Terminate a switch using OpenFlow reference kernel datapath."
279
      quietRun( 'dpctl deldp ' + self.dp )
280
      # In theory the interfaces should go away after we shut down.
281
      # However, this takes time, so we're better off to remove them
282
      # explicitly so that we won't get errors if we run before they
283
      # have been removed by the kernel. Unfortunately this is very slow.
284
      self.cmd( 'kill %ofprotocol')
285
      for intf in self.intfs:
286
         quietRun( 'ip link del ' + intf )
287
         lg.info('.')
288
   def start( self, controller ): 
289
      if self.dp is None: self.startUserDatapath( controller )
290
      else: self.startKernelDatapath( controller )
291
   def stop( self ):
292
      if self.dp is None: self.stopUserDatapath()
293
      else: self.stopKernelDatapath()
294
   def sendCmd( self, cmd ):
295
      if not self.execed:
296
          return Node.sendCmd( self, cmd )
297
      else:
298
          lg.error("*** Error: %s has execed and cannot accept commands" %
299
                 self.name)
300
   def monitor( self ):
301
      if not self.execed: return Node.monitor( self )
302
      else: return True, ''
303

  
304

  
305 83
# Handy utilities
306 84
     
307 85
def dumpNodes( nodes ):
......
367 145
      switch.setHostRoute( cip, sintf )
368 146
   lg.info("\n")
369 147
   lg.info("*** Testing control network\n")
370
   while not controller.intfIsUp( controller.intfs[ 0 ] ):
148
   while not controller.intfIsUp():
371 149
      lg.info("*** Waiting for %s to come up\n", controller.intfs[ 0 ])
372 150
      sleep( 1 )
373 151
   for switch in switches:
374
      while not switch.intfIsUp( switch.intfs[ 0 ] ):
152
      while not switch.intfIsUp():
375 153
         lg.info("*** Waiting for %s to come up\n" % switch.intfs[ 0 ])
376 154
         sleep( 1 )
377 155
      if pingTest( hosts=[ switch, controller ] ) != 0:
mininet/node.py
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
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 setIP(self, intf, ip, bits):
164
        '''Set the IP address for an interface.
165

  
166
        @param intf string, interface name
167
        @param ip IP address as integer
168
        @param bits
169
        '''
170
        result = self.cmd(['ifconfig', intf, ip + bits, 'up'])
171
        self.ips[intf] = ip
172
        return result
173

  
174
    def setHostRoute(self, ip, intf):
175
        '''Add route to host.
176

  
177
        @param ip IP address as dotted decimal
178
        @param intf string, interface name
179
        '''
180
        return self.cmd('route add -host ' + ip + ' dev ' + intf)
181

  
182
    def setDefaultRoute(self, intf):
183
        '''Set the default route to go through intf.
184

  
185
        @param intf string, interface name
186
        '''
187
        self.cmd('ip route flush')
188
        return self.cmd('route add default ' + intf)
189

  
190
    def IP(self):
191
        '''Return IP address of first interface'''
192
        if len(self.intfs) > 0:
193
            return self.ips.get(self.intfs[ 0 ], None)
194

  
195
    def intfIsUp(self):
196
        '''Check if one of our interfaces is up.'''
197
        return 'UP' in self.cmd('ifconfig ' + self.intfs[0])
198

  
199
    # Other methods
200
    def __str__(self):
201
        result = self.name + ':'
202
        if self.IP():
203
            result += ' IP=' + self.IP()
204
        result += ' intfs=' + ','.join(self.intfs)
205
        result += ' waiting=' + repr(self.waiting)
206
        return result
207

  
208

  
209
class Host(Node):
210
    '''A host is simply a Node.'''
211
    pass
212

  
213

  
214
class Controller(Node):
215
    '''A Controller is a Node that is running (or has execed) an
216
      OpenFlow controller.'''
217

  
218
    def __init__(self, name, kernel=True, controller='controller',
219
                 cargs='-v ptcp:', cdir=None):
220
        self.controller = controller
221
        self.cargs = cargs
222
        self.cdir = cdir
223
        Node.__init__(self, name, inNamespace=(not kernel))
224

  
225
    def start(self):
226
        '''Start <controller> <args> on controller.
227

  
228
        Log to /tmp/cN.log
229
        '''
230
        cout = '/tmp/' + self.name + '.log'
231
        if self.cdir is not None:
232
            self.cmdPrint('cd ' + self.cdir)
233
        self.cmdPrint(self.controller + ' ' + self.cargs +
234
            ' 1> ' + cout + ' 2> ' + cout + ' &')
235
        self.execed = False # XXX Until I fix it
236

  
237
    def stop(self):
238
        '''Stop controller.'''
239
        self.cmd('kill %' + self.controller)
240
        self.terminate()
241

  
242

  
243
class Switch(Node):
244
    '''A Switch is a Node that is running (or has execed)
245
       an OpenFlow switch.'''
246

  
247
    def __init__(self, name, datapath = None):
248
        '''Init.
249

  
250
        @param name
251
        @param datapath string, datapath name
252
        '''
253
        self.dp = datapath
254
        Node.__init__(self, name, inNamespace = (datapath == None))
255

  
256
    def _startUserDatapath(self, controller):
257
        '''Start OpenFlow reference user datapath.
258

  
259
        Log to /tmp/sN-{ofd,ofp}.log.
260

  
261
        @param controller Controller object.
262
        '''
263
        ofdlog = '/tmp/' + self.name + '-ofd.log'
264
        ofplog = '/tmp/' + self.name + '-ofp.log'
265
        self.cmd('ifconfig lo up')
266
        intfs = self.intfs[1:] # 0 is mgmt interface
267
        self.cmdPrint('ofdatapath -i ' + ','.join(intfs) +
268
                      ' ptcp: 1> ' + ofdlog + ' 2> ' + ofdlog + ' &')
269
        self.cmdPrint('ofprotocol tcp:' + controller.IP() +
270
                      ' tcp:localhost --fail=closed 1> ' + ofplog + ' 2>' +
271
                      ofplog + ' &')
272

  
273
    def _stopUserDatapath(self):
274
        '''Stop OpenFlow reference user datapath.'''
275
        self.cmd('kill %ofdatapath')
276
        self.cmd('kill %ofprotocol')
277

  
278
    def _startKernelDatapath(self):
279
        '''Start up reference kernel datapath.'''
280
        ofplog = '/tmp/' + self.name + '-ofp.log'
281
        quietRun('ifconfig lo up')
282
        # Delete local datapath if it exists;
283
        # then create a new one monitoring the given interfaces
284
        quietRun('dpctl deldp ' + self.dp)
285
        self.cmdPrint('dpctl adddp ' + self.dp)
286
        self.cmdPrint('dpctl addif ' + self.dp + ' ' + ' '.join(self.intfs))
287
        # Run protocol daemon
288
        self.cmdPrint('ofprotocol' +
289
                      ' ' + self.dp + ' tcp:127.0.0.1 ' +
290
                      ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &')
291
        self.execed = False # XXX until I fix it
292

  
293
    def _stopKernelDatapath(self):
294
        '''Terminate reference kernel datapath.'''
295
        quietRun('dpctl deldp ' + self.dp)
296
        # In theory the interfaces should go away after we shut down.
297
        # However, this takes time, so we're better off to remove them
298
        # explicitly so that we won't get errors if we run before they
299
        # have been removed by the kernel. Unfortunately this is very slow.
300
        self.cmd('kill %ofprotocol')
301
        for intf in self.intfs:
302
            quietRun('ip link del ' + intf)
303
            lg.info('.')
304

  
305
    def start(self, controller):
306
        '''Start datapath.
307

  
308
        @param controller Controller object
309
        '''
310
        if self.dp is None:
311
            self._startUserDatapath(controller)
312
        else:
313
            self._startKernelDatapath()
314

  
315
    def stop(self):
316
        '''Stop datapath.'''
317
        if self.dp is None:
318
            self._stopUserDatapath()
319
        else:
320
            self._stopKernelDatapath()
321

  
322
    def sendCmd(self, cmd):
323
        '''Send command to Node.
324

  
325
        @param cmd string
326
        '''
327
        if not self.execed:
328
            return Node.sendCmd(self, cmd)
329
        else:
330
            lg.error('*** Error: %s has execed and cannot accept commands' %
331
                     self.name)
332

  
333
    def monitor(self):
334
        '''Monitor node.'''
335
        if not self.execed:
336
            return Node.monitor(self)
337
        else:
338
            return True, ''

Also available in: Unified diff