From 89bf31030b91b2ff19e2418d680ac46e6524681a Mon Sep 17 00:00:00 2001 From: Brandon Heller <brandonh@stanford.edu> Date: Sun, 20 Dec 2009 14:19:32 -0800 Subject: [PATCH] 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. --- examples/ripcordtest.py | 3 +- mininet/net.py | 228 +-------------------------- mininet/node.py | 338 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 343 insertions(+), 226 deletions(-) create mode 100644 mininet/node.py diff --git a/examples/ripcordtest.py b/examples/ripcordtest.py index 41853b9a..3491851c 100755 --- a/examples/ripcordtest.py +++ b/examples/ripcordtest.py @@ -13,7 +13,8 @@ from ripcord.topo import FatTreeTopo -from mininet.net import Switch, Controller, Host, init +from mininet.net import init +from mininet.node import Switch, Controller, Host from mininet.logging_mod import lg, set_loglevel from mininet.util import make_veth_pair, move_intf, retry, quietRun from mininet.util import MOVEINTF_DELAY diff --git a/mininet/net.py b/mininet/net.py index 4766a9bc..c9f525e5 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -74,234 +74,12 @@ 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: diff --git a/mininet/node.py b/mininet/node.py new file mode 100644 index 00000000..16f43bdb --- /dev/null +++ b/mininet/node.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python +'''Node objects for Mininet.''' + +from subprocess import Popen, PIPE, STDOUT +import os, signal, sys, select +flush = sys.stdout.flush + +from mininet.logging_mod import lg +from mininet.util import quietRun + +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): + '''Insert docstring. + + @param f unknown + @return bool unknown + ''' + 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, fileno_max): + '''Insert docstring. + + @param fileno_max unknown + ''' + return os.read(self.stdout.fileno(), fileno_max) + + def write(self, data): + '''Write data to node. + + @param data string + ''' + os.write(self.stdin.fileno(), data) + + def terminate(self): + '''Send kill signal to Node and cleanup after it.''' + os.kill(self.pid, signal.SIGKILL) + self.cleanup() + + def stop(self): + '''Stop node.''' + self.terminate() + + def waitReadable(self): + '''Poll on node.''' + 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 the output of a command, 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. + + Completion is signaled by a sentinel character, ASCII(127) appearing in + the output stream. Wait for the sentinel 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. + + @param cmd string + ''' + self.sendCmd(cmd) + return self.waitOutput() + + def cmdPrint(self, cmd): + '''Call cmd and printing its output + + @param cmd string + ''' + #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' + repr(n) + + def newIntf(self): + '''Reserve and return a new interface name.''' + intfName = self.intfName(self.intfCount) + self.intfCount += 1 + self.intfs += [intfName] + return intfName + + def setIP(self, intf, ip, bits): + '''Set the IP address for an interface. + + @param intf string, interface name + @param ip IP address as integer + @param bits + ''' + result = self.cmd(['ifconfig', intf, ip + bits, 'up']) + self.ips[intf] = ip + return result + + def setHostRoute(self, ip, intf): + '''Add route to host. + + @param ip IP address as dotted decimal + @param intf string, interface name + ''' + return self.cmd('route add -host ' + ip + ' dev ' + intf) + + def setDefaultRoute(self, intf): + '''Set the default route to go through intf. + + @param intf string, interface name + ''' + 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): + '''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=' + repr(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. + + Log 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): + '''Stop controller.''' + self.cmd('kill %' + self.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): + '''Init. + + @param name + @param datapath string, datapath name + ''' + self.dp = datapath + Node.__init__(self, name, inNamespace = (datapath == None)) + + def _startUserDatapath(self, controller): + '''Start OpenFlow reference user datapath. + + Log to /tmp/sN-{ofd,ofp}.log. + + @param controller Controller object. + ''' + 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): + '''Start up 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 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): + '''Start datapath. + + @param controller Controller object + ''' + if self.dp is None: + self._startUserDatapath(controller) + else: + self._startKernelDatapath() + + def stop(self): + '''Stop datapath.''' + if self.dp is None: + self._stopUserDatapath() + else: + self._stopKernelDatapath() + + def sendCmd(self, cmd): + '''Send command to Node. + + @param cmd string + ''' + 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): + '''Monitor node.''' + if not self.execed: + return Node.monitor(self) + else: + return True, '' \ No newline at end of file -- GitLab