Move Node functions into their own file

Nodes include Switch, Host, and Controller; move these to a separate
file.

This file still could use some attention to hide private functions.
Node seems like a primary class to extend, for adding stuff like Open
vSwitch, so it could benefit from a simpler interface.
This commit is contained in:
Brandon Heller
2009-12-20 14:19:32 -08:00
parent 4d2d52c3db
commit 89bf31030b
3 changed files with 343 additions and 226 deletions
+3 -225
View File
@@ -74,234 +74,12 @@ flush = sys.stdout.flush
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
from mininet.logging_mod import lg, set_loglevel
from mininet.node import Node, Host, Controller, Switch
from mininet.util import run, checkRun, quietRun, makeIntfPair, moveIntf
from mininet.util import createLink
DATAPATHS = ['user', 'kernel']
class Node( object ):
"""A virtual network node is simply a shell in a network namespace.
We communicate with it using pipes."""
inToNode = {}
outToNode = {}
def __init__( self, name, inNamespace=True ):
self.name = name
closeFds = False # speed vs. memory use
# xpg_echo is needed so we can echo our sentinel in sendCmd
cmd = [ '/bin/bash', '-O', 'xpg_echo' ]
self.inNamespace = inNamespace
if self.inNamespace: cmd = [ 'netns' ] + cmd
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
close_fds=closeFds )
self.stdin = self.shell.stdin
self.stdout = self.shell.stdout
self.pollOut = select.poll()
self.pollOut.register( self.stdout )
# Maintain mapping between file descriptors and nodes
# This could be useful for monitoring multiple nodes
# using select.poll()
self.outToNode[ self.stdout.fileno() ] = self
self.inToNode[ self.stdin.fileno() ] = self
self.pid = self.shell.pid
self.intfCount = 0
self.intfs = [] # list of interface names, as strings
self.ips = {}
self.connection = {}
self.waiting = False
self.execed = False
def fdToNode( self, f ):
node = self.outToNode.get( f )
return node or self.inToNode.get( f )
def cleanup( self ):
# Help python collect its garbage
self.shell = None
# Subshell I/O, commands and control
def read( self, max ): return os.read( self.stdout.fileno(), max )
def write( self, data ): os.write( self.stdin.fileno(), data )
def terminate( self ):
os.kill( self.pid, signal.SIGKILL )
self.cleanup()
def stop( self ): self.terminate()
def waitReadable( self ): self.pollOut.poll()
def sendCmd( self, cmd ):
"""Send a command, followed by a command to echo a sentinel,
and return without waiting for the command to complete."""
assert not self.waiting
if cmd[ -1 ] == '&':
separator = '&'
cmd = cmd[ : -1 ]
else: separator = ';'
if isinstance( cmd, list): cmd = ' '.join( cmd )
self.write( cmd + separator + " echo -n '\\0177' \n")
self.waiting = True
def monitor( self ):
"Monitor a command's output, returning (done, data)."
assert self.waiting
self.waitReadable()
data = self.read( 1024 )
if len( data ) > 0 and data[ -1 ] == chr( 0177 ):
self.waiting = False
return True, data[ : -1 ]
else:
return False, data
def sendInt( self ):
"Send ^C, hopefully interrupting a running subprocess."
self.write( chr( 3 ) )
def waitOutput( self ):
"""Wait for a command to complete (signaled by a sentinel
character, ASCII(127) appearing in the output stream) and return
the output, including trailing newline."""
assert self.waiting
output = ""
while True:
self.waitReadable()
data = self.read( 1024 )
if len(data) > 0 and data[ -1 ] == chr( 0177 ):
output += data[ : -1 ]
break
else: output += data
self.waiting = False
return output
def cmd( self, cmd ):
"Send a command, wait for output, and return it."
self.sendCmd( cmd )
return self.waitOutput()
def cmdPrint( self, cmd ):
"Call cmd, printing the command and output"
#lg.info("*** %s : %s", self.name, cmd)
result = self.cmd( cmd )
#lg.info("%s\n", result)
return result
# Interface management, configuration, and routing
def intfName( self, n):
"Construct a canonical interface name node-intf for interface N."
return self.name + '-eth' + `n`
def newIntf( self ):
"Reserve and return a new interface name for this node."
intfName = self.intfName( self.intfCount)
self.intfCount += 1
self.intfs += [ intfName ]
return intfName
def setIP( self, intf, ip, bits ):
"Set an interface's IP address."
result = self.cmd( [ 'ifconfig', intf, ip + bits, 'up' ] )
self.ips[ intf ] = ip
return result
def setHostRoute( self, ip, intf ):
"Add a route to the given IP address via intf."
return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
def setDefaultRoute( self, intf ):
"Set the default route to go through intf."
self.cmd( 'ip route flush' )
return self.cmd( 'route add default ' + intf )
def IP( self ):
"Return IP address of first interface"
if len( self.intfs ) > 0:
return self.ips.get( self.intfs[ 0 ], None )
def intfIsUp( self, intf ):
"Check if one of our interfaces is up."
return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ 0 ] )
# Other methods
def __str__( self ):
result = self.name + ":"
if self.IP():
result += " IP=" + self.IP()
result += " intfs=" + ','.join( self.intfs )
result += " waiting=" + `self.waiting`
return result
class Host( Node ):
"""A host is simply a Node."""
pass
class Controller( Node ):
"""A Controller is a Node that is running (or has execed) an
OpenFlow controller."""
def __init__( self, name, kernel=True, controller='controller',
cargs='-v ptcp:', cdir=None ):
self.controller = controller
self.cargs = cargs
self.cdir = cdir
Node.__init__( self, name, inNamespace=( not kernel ) )
def start( self ):
"Start <controller> <args> on controller, logging to /tmp/cN.log"
cout = '/tmp/' + self.name + '.log'
if self.cdir is not None:
self.cmdPrint( 'cd ' + self.cdir )
self.cmdPrint( self.controller + ' ' + self.cargs +
' 1> ' + cout + ' 2> ' + cout + ' &' )
self.execed = False # XXX Until I fix it
def stop( self, controller='controller' ):
"Stop controller cprog on controller"
self.cmd( "kill %" + controller )
self.terminate()
class Switch( Node ):
"""A Switch is a Node that is running (or has execed)
an OpenFlow switch."""
def __init__( self, name, datapath=None ):
self.dp = datapath
Node.__init__( self, name, inNamespace=( datapath == None ) )
def startUserDatapath( self, controller ):
"""Start OpenFlow reference user datapath,
logging to /tmp/sN-{ofd,ofp}.log"""
ofdlog = '/tmp/' + self.name + '-ofd.log'
ofplog = '/tmp/' + self.name + '-ofp.log'
self.cmd( 'ifconfig lo up' )
intfs = self.intfs[ 1 : ] # 0 is mgmt interface
self.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) +
' ptcp: 1> ' + ofdlog + ' 2> '+ ofdlog + ' &' )
self.cmdPrint( 'ofprotocol tcp:' + controller.IP() +
' tcp:localhost --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
def stopUserDatapath( self ):
"Stop OpenFlow reference user datapath."
self.cmd( "kill %ofdatapath" )
self.cmd( "kill %ofprotocol" )
def startKernelDatapath( self, controller):
"Start up switch using OpenFlow reference kernel datapath."
ofplog = '/tmp/' + self.name + '-ofp.log'
quietRun( 'ifconfig lo up' )
# Delete local datapath if it exists;
# then create a new one monitoring the given interfaces
quietRun( 'dpctl deldp ' + self.dp )
self.cmdPrint( 'dpctl adddp ' + self.dp )
self.cmdPrint( 'dpctl addif ' + self.dp + ' ' + ' '.join( self.intfs ) )
# Run protocol daemon
self.cmdPrint( 'ofprotocol' +
' ' + self.dp + ' tcp:127.0.0.1 ' +
' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
self.execed = False # XXX until I fix it
def stopKernelDatapath( self ):
"Terminate a switch using OpenFlow reference kernel datapath."
quietRun( 'dpctl deldp ' + self.dp )
# In theory the interfaces should go away after we shut down.
# However, this takes time, so we're better off to remove them
# explicitly so that we won't get errors if we run before they
# have been removed by the kernel. Unfortunately this is very slow.
self.cmd( 'kill %ofprotocol')
for intf in self.intfs:
quietRun( 'ip link del ' + intf )
lg.info('.')
def start( self, controller ):
if self.dp is None: self.startUserDatapath( controller )
else: self.startKernelDatapath( controller )
def stop( self ):
if self.dp is None: self.stopUserDatapath()
else: self.stopKernelDatapath()
def sendCmd( self, cmd ):
if not self.execed:
return Node.sendCmd( self, cmd )
else:
lg.error("*** Error: %s has execed and cannot accept commands" %
self.name)
def monitor( self ):
if not self.execed: return Node.monitor( self )
else: return True, ''
# Handy utilities
def dumpNodes( nodes ):
@@ -367,11 +145,11 @@ def configureRoutedControlNetwork( controller, switches, ips):
switch.setHostRoute( cip, sintf )
lg.info("\n")
lg.info("*** Testing control network\n")
while not controller.intfIsUp( controller.intfs[ 0 ] ):
while not controller.intfIsUp():
lg.info("*** Waiting for %s to come up\n", controller.intfs[ 0 ])
sleep( 1 )
for switch in switches:
while not switch.intfIsUp( switch.intfs[ 0 ] ):
while not switch.intfIsUp():
lg.info("*** Waiting for %s to come up\n" % switch.intfs[ 0 ])
sleep( 1 )
if pingTest( hosts=[ switch, controller ] ) != 0: