diff --git a/examples/scratchnet.py b/examples/scratchnet.py index 9e9ccbd5e999be2c60c14c211a36598a4fdf6040..84c74bedb3dfd900621bafa0d199f1832eb1f068 100755 --- a/examples/scratchnet.py +++ b/examples/scratchnet.py @@ -6,35 +6,49 @@ but it exposes the configuration details and allows customization. """ -from mininet.mininet import init, Node, createLink +import logging + +from mininet.net import init +from mininet.node import Node +from mininet.util import createLink +from mininet.log import info + +# print out info() messages, including cmdPrint +logging.LOGLEVELDEFAULT = logging.INFO def scratchNet( cname='controller', cargs='ptcp:'): - # Create Network - controller = Node( 'c0', inNamespace=False ) - switch = Node( 's0', inNamespace=False ) - h0 = Node( 'h0' ) - h1 = Node( 'h1' ) - createLink( h0, switch ) - createLink( h1, switch ) - # Configure hosts - h0.setIP( h0.intfs[ 0 ], '192.168.123.1', '/24' ) - h1.setIP( h1.intfs[ 0 ], '192.168.123.2', '/24' ) - # Start network using kernel datapath - controller.cmdPrint( cname + ' ' + cargs + '&' ) - switch.cmdPrint( 'dpctl deldp nl:0' ) - switch.cmdPrint( 'dpctl adddp nl:0' ) - for intf in switch.intfs: + # Create Network + print "*** creating Nodes" + controller = Node( 'c0', inNamespace=False ) + switch = Node( 's0', inNamespace=False ) + h0 = Node( 'h0' ) + h1 = Node( 'h1' ) + print "*** creating links" + createLink( node1=h0, port1=0, node2=switch, port2=0 ) + createLink( node1=h1, port1=0, node2=switch, port2=1 ) + # Configure hosts + print "*** configuring hosts" + h0.setIP( h0.intfs[ 0 ], '192.168.123.1', '/24' ) + h1.setIP( h1.intfs[ 0 ], '192.168.123.2', '/24' ) + print h0 + print h1 + # Start network using kernel datapath + controller.cmdPrint( cname + ' ' + cargs + '&' ) + switch.cmdPrint( 'dpctl deldp nl:0' ) + switch.cmdPrint( 'dpctl adddp nl:0' ) + for intf in switch.intfs.values(): switch.cmdPrint( 'dpctl addif nl:0 ' + intf ) - switch.cmdPrint( 'ofprotocol nl:0 tcp:localhost &') - # Run test - h0.cmdPrint( 'ping -c1 ' + h1.IP() ) - # Stop network - controller.cmdPrint( 'kill %' + cname) - switch.cmdPrint( 'dpctl deldp nl:0' ) - switch.cmdPrint( 'kill %ofprotocol' ) - switch.stop() - controller.stop() + switch.cmdPrint( 'ofprotocol nl:0 tcp:localhost &') + # Run test + print h0.cmd( 'ping -c1 ' + h1.IP() ) + # Stop network + controller.cmdPrint( 'kill %' + cname) + switch.cmdPrint( 'dpctl deldp nl:0' ) + switch.cmdPrint( 'kill %ofprotocol' ) + switch.stop() + controller.stop() if __name__ == '__main__': - init() - scratchNet() + info( '*** Scratch network demo\n' ) + init() + scratchNet() diff --git a/mininet/net.py b/mininet/net.py index 00abdda254c49190f65fd339a268398559e73df9..96da89971fb89d0fa61e36afcc6427e64522fca5 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -81,10 +81,10 @@ from time import sleep from mininet.cli import CLI -from mininet.log import info, error +from mininet.log import info, error, debug from mininet.node import KernelSwitch, OVSKernelSwitch from mininet.util import quietRun, fixLimits -from mininet.util import makeIntfPair, moveIntf, macColonHex +from mininet.util import createLink, macColonHex from mininet.xterm import cleanUpScreens, makeXterms DATAPATHS = [ 'kernel' ] #[ 'user', 'kernel' ] @@ -150,8 +150,6 @@ def addHost( self, name, defaultMac=None, defaultIp=None ): defaultIp: default IP address for intf 0 returns: added host""" host = self.host( name ) - # for now, assume one interface per host. - host.intfs.append( name + '-eth0' ) self.hosts.append( host ) self.nameToNode[ name ] = host # May wish to add this to actual object @@ -175,31 +173,6 @@ def addSwitch( self, name, defaultMac=None ): self.nameToNode[ name ] = sw return sw - def addLink( self, src, srcPort, dst, dstPort ): - """Add link. - src: source Node - srcPort: source port - dst: destination Node - dstPort: destination port""" - srcIntf = src.intfName( srcPort ) - dstIntf = dst.intfName( dstPort ) - makeIntfPair( srcIntf, dstIntf ) - src.intfs.append( srcIntf ) - dst.intfs.append( dstIntf ) - src.ports[ srcPort ] = srcIntf - dst.ports[ dstPort ] = dstIntf - #info( '\n' ) - #info( 'added intf %s to src node %x\n' % ( srcIntf, src ) ) - #info( 'added intf %s to dst node %x\n' % ( dstIntf, dst ) ) - if src.inNamespace: - #info( 'moving src w/inNamespace set\n' ) - moveIntf( srcIntf, src ) - if dst.inNamespace: - #info( 'moving dst w/inNamespace set\n' ) - moveIntf( dstIntf, dst ) - src.connection[ srcIntf ] = ( dst, dstIntf ) - dst.connection[ dstIntf ] = ( src, srcIntf ) - def addController( self, controller ): """Add controller. controller: Controller class""" @@ -315,7 +288,7 @@ def buildFromTopo( self, topo ): for srcId, dstId in sorted( topo.edges() ): src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] srcPort, dstPort = topo.port( srcId, dstId ) - self.addLink( src, srcPort, dst, dstPort ) + createLink( src, srcPort, dst, dstPort ) info( '(%s, %s) ' % ( src.name, dst.name ) ) info( '\n' ) @@ -368,7 +341,7 @@ def start( self ): controller.start() info( '*** Starting %s switches\n' % len( self.switches ) ) for switch in self.switches: - info( switch.name ) + info( switch.name + ' ') switch.start( self.controllers ) info( '\n' ) @@ -474,13 +447,14 @@ def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', l4Type: string, one of [ TCP, UDP ] verbose: verbose printing returns: results two-element array of server and client speeds""" + log = info if verbose else debug if not hosts: hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ] else: assert len( hosts ) == 2 host0, host1 = hosts - info( '*** Iperf: testing ' + l4Type + ' bandwidth between ' ) - info( "%s and %s\n" % ( host0.name, host1.name ) ) + log( '*** Iperf: testing ' + l4Type + ' bandwidth between ' ) + log( "%s and %s\n" % ( host0.name, host1.name ) ) host0.cmd( 'killall -9 iperf' ) iperfArgs = 'iperf ' bwArgs = '' @@ -490,19 +464,16 @@ def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', elif l4Type != 'TCP': raise Exception( 'Unexpected l4 type: %s' % l4Type ) server = host0.cmd( iperfArgs + '-s &' ) - if verbose: - info( '%s\n' % server ) + log( '%s\n' % server ) client = host1.cmd( iperfArgs + '-t 5 -c ' + host0.IP() + ' ' + bwArgs ) - if verbose: - info( '%s\n' % client ) + log( '%s\n' % client ) server = host0.cmd( 'killall -9 iperf' ) - if verbose: - info( '%s\n' % server ) + log( '%s\n' % server ) result = [ self._parseIperf( server ), self._parseIperf( client ) ] if l4Type == 'UDP': result.insert( 0, udpBw ) - info( '*** Results: %s\n' % result ) + log( '*** Results: %s\n' % result ) return result def iperfUdp( self, udpBw='10M' ): diff --git a/mininet/node.py b/mininet/node.py index 8153e84fc1d239061c3e5c2bc9323e3e35a4b18c..eb60b85a0086fd1dae12dc795774b27859de5a09 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -40,8 +40,8 @@ import signal import select -from mininet.log import info, error -from mininet.util import quietRun +from mininet.log import info, error, debug +from mininet.util import quietRun, moveIntf class Node( object ): @@ -72,13 +72,13 @@ def __init__( self, name, inNamespace=True ): self.inToNode[ self.stdin.fileno() ] = self self.pid = self.shell.pid self.intfCount = 0 - self.intfs = [] # list of interface names, as strings + self.intfs = {} # dict of port numbers to interface names + self.ports = {} # dict of interface names to port numbers + # replace with Port objects, eventually ? self.ips = {} # dict of interfaces to ip addresses as strings self.connection = {} # remote node connected to each interface self.waiting = False self.execed = False - self.ports = {} # dict of ints to interface strings - # replace with Port object, eventually @classmethod def fdToNode( cls, fd ): @@ -145,11 +145,13 @@ def sendInt( self ): "Send ^C, hopefully interrupting an interactive subprocess." self.write( chr( 3 ) ) - def waitOutput( self ): + def waitOutput( self, verbose=False ): """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.""" + the output, including trailing newline. + verbose: print output interactively""" + log = info if verbose else debug assert self.waiting output = '' while True: @@ -157,38 +159,60 @@ def waitOutput( self ): data = self.read( 1024 ) if len( data ) > 0 and data[ -1 ] == chr( 0177 ): output += data[ :-1 ] + log( output ) break else: output += data self.waiting = False return output - def cmd( self, cmd ): + def cmd( self, cmd, verbose=False ): """Send a command, wait for output, and return it. cmd: string""" + log = info if verbose else debug + log( '*** %s : %s', self.name, cmd ) self.sendCmd( cmd ) - return self.waitOutput() + return self.waitOutput( verbose ) def cmdPrint( self, cmd ): """Call cmd and printing its output cmd: string""" - #info( '*** %s : %s', self.name, cmd ) - result = self.cmd( cmd ) - #info( '%s\n', result ) - return result + return self.cmd( cmd, verbose=True ) # Interface management, configuration, and routing + + # BL notes: This might be a bit redundant or over-complicated. + # However, it does allow a bit of specialization, including + # changing the canonical interface names. It's also tricky since + # the real interfaces are created as veth pairs, so we can't + # make a single interface at a time. + def intfName( self, n ): - "Construct a canonical interface name node-intf for interface N." + "Construct a canonical interface name node-ethN 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 addIntf( self, intf, port ): + """Add an interface. + intf: interface name (nodeN-ethM) + port: port number (typically OpenFlow port number)""" + self.intfs[ port ] = intf + self.ports[ intf ] = port + #info( '\n' ) + #info( 'added intf %s to node %x\n' % ( srcIntf, src ) ) + if self.inNamespace: + #info( 'moving w/inNamespace set\n' ) + moveIntf( intf, self ) + + def connect( self, intf, dstNode, dstIntf ): + "Register connection of intf to dstIntf on dstNode." + self.connection[ intf ] = ( dstNode, dstIntf ) + def setMAC( self, intf, mac ): """Set the MAC address for an interface. mac: MAC address as string""" @@ -206,9 +230,9 @@ def setARP( self, ip, mac ): def setIP( self, intf, ip, bits ): """Set the IP address for an interface. - intf: string, interface name + intf: interface name ip: IP address as a string - bits:""" + bits: prefix length of form /24""" result = self.cmd( [ 'ifconfig', intf, ip + bits, 'up' ] ) self.ips[ intf ] = ip return result @@ -226,20 +250,19 @@ def setDefaultRoute( self, intf ): 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 ) + "Return IP address of interface 0" + return self.ips.get( self.intfs.get( 0 , None ), None ) - def intfIsUp( self ): - "Check if one of our interfaces is up." - return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ 0 ] ) + def intfIsUp( self, port ): + """Check if interface for a given port number is up. + port: port number""" + return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ port ] ) # Other methods def __str__( self ): result = self.name + ':' - if self.IP(): - result += ' IP=' + self.IP() - result += ' intfs=' + ','.join( self.intfs ) + result += ' IP=' + repr( self.IP() ) + result += ' intfs=' + ','.join( sorted( self.intfs.values() ) ) result += ' waiting=' + repr( self.waiting ) return result @@ -282,17 +305,17 @@ def __init__( self, name ): def start( self, controllers ): """Start OpenFlow reference user datapath. Log to /tmp/sN-{ofd,ofp}.log. - controllers: dict of controller names to objects""" + controllers: list of controller objects""" controller = controllers[ 0 ] ofdlog = '/tmp/' + self.name + '-ofd.log' ofplog = '/tmp/' + self.name + '-ofp.log' self.cmd( 'ifconfig lo up' ) - intfs = self.intfs + intfs = sorted( self.intfs.values() ) - self.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) + + self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' punix:/tmp/' + self.name + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) - self.cmdPrint( 'ofprotocol unix:/tmp/' + self.name + + self.cmd( 'ofprotocol unix:/tmp/' + self.name + ' tcp:' + controller.IP() + ' --fail=closed' + ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) @@ -322,20 +345,20 @@ def start( self, controllers ): # Delete local datapath if it exists; # then create a new one monitoring the given interfaces quietRun( 'dpctl deldp nl:%i' % self.dp ) - self.cmdPrint( 'dpctl adddp nl:%i' % self.dp ) + self.cmd( 'dpctl adddp nl:%i' % self.dp ) if self.defaultMac: intf = 'of%i' % self.dp self.cmd( [ 'ifconfig', intf, 'hw', 'ether', self.defaultMac ] ) - if len( self.ports ) != max( self.ports.keys() ) + 1: + if len( self.intfs ) != max( self.intfs ) + 1: raise Exception( 'only contiguous, zero-indexed port ranges' - 'supported: %s' % self.ports ) - intfs = [ self.ports[ port ] for port in self.ports.keys() ] - self.cmdPrint( 'dpctl addif nl:' + str( self.dp ) + ' ' + + 'supported: %s' % self.intfs ) + intfs = [ self.intfs[ port ] for port in sorted( self.intfs.keys() ) ] + self.cmd( 'dpctl addif nl:' + str( self.dp ) + ' ' + ' '.join( intfs ) ) # Run protocol daemon controller = controllers[ 0 ] - self.cmdPrint( 'ofprotocol nl:' + str( self.dp ) + ' tcp:' + + self.cmd( 'ofprotocol nl:' + str( self.dp ) + ' tcp:' + controller.IP() + ':' + str( controller.port ) + ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' ) @@ -345,11 +368,11 @@ def stop( self ): "Terminate kernel datapath." quietRun( 'dpctl deldp nl:%i' % 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 + # However, this takes time, so we're better off removing 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: + for intf in self.intfs.values(): quietRun( 'ip link del ' + intf ) info( '.' ) @@ -374,21 +397,21 @@ def start( self, controllers ): # Delete local datapath if it exists; # then create a new one monitoring the given interfaces quietRun( 'ovs-dpctl del-dp dp%i' % self.dp ) - self.cmdPrint( 'ovs-dpctl add-dp dp%i' % self.dp ) + self.cmd( 'ovs-dpctl add-dp dp%i' % self.dp ) if self.defaultMac: intf = 'dp' % self.dp mac = self.defaultMac self.cmd( [ 'ifconfig', intf, 'hw', 'ether', mac ] ) - if len( self.ports ) != max( self.ports.keys() ) + 1: + if len( self.intfs ) != max( self.intfs ) + 1: raise Exception( 'only contiguous, zero-indexed port ranges' - 'supported: %s' % self.ports ) - intfs = [ self.ports[ port ] for port in self.ports.keys() ] - self.cmdPrint( 'ovs-dpctl add-if dp' + str( self.dp ) + ' ' + + 'supported: %s' % self.intfs ) + intfs = [ self.intfs[ port ] for port in sorted( self.intfs.keys() ) ] + self.cmd( 'ovs-dpctl add-if dp' + str( self.dp ) + ' ' + ' '.join( intfs ) ) # Run protocol daemon controller = controllers[ 0 ] - self.cmdPrint( 'ovs-openflowd dp' + str( self.dp ) + ' tcp:' + + self.cmd( 'ovs-openflowd dp' + str( self.dp ) + ' tcp:' + controller.IP() + ':' + ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' ) self.execed = False @@ -397,11 +420,11 @@ def stop( self ): "Terminate kernel datapath." quietRun( 'ovs-dpctl del-dp dp%i' % 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 + # However, this takes time, so we're better off removing 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 %ovs-openflowd' ) - for intf in self.intfs: + for intf in self.intfs.values(): quietRun( 'ip link del ' + intf ) info( '.' ) @@ -425,8 +448,8 @@ def start( self ): 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 + + self.cmd( 'cd ' + self.cdir ) + self.cmd( self.controller + ' ' + self.cargs + ' 1> ' + cout + ' 2> ' + cout + ' &' ) self.execed = False diff --git a/mininet/util.py b/mininet/util.py index 221c85e4db5cf47be186cc7326ee0edd5b9cb387..3bec7f59616c11bd4caecef0be4810935ea0c34d 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -15,7 +15,7 @@ def run( cmd ): def checkRun( cmd ): """Simple interface to subprocess.check_call() cmd: list of command params""" - check_call( cmd.split( ' ' ) ) + return check_call( cmd.split( ' ' ) ) def quietRun( cmd ): """Run a command, routing stderr to stdout, and return the output. @@ -63,7 +63,7 @@ def makeIntfPair( intf1, intf2 ): quietRun( 'ip link del ' + intf2 ) # Create new pair cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2 - return checkRun( cmd ) + return quietRun( cmd ) def retry( retries, delaySecs, fn, *args, **keywords ): """Try something several times before giving up. @@ -101,19 +101,20 @@ def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ): printError: if true, print error""" retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError ) -def createLink( node1, node2, retries=10, delaySecs=0.001 ): +def createLink( node1, port1, node2, port2 ): """Create a link between nodes, making an interface for each. node1: Node object - node2: Node object""" - intf1 = node1.newIntf() - intf2 = node2.newIntf() + port1: node1 port number + node2: Node object + port2: node2 port number + returns: intf1 name, intf2 name""" + intf1 = node1.intfName( port1 ) + intf2 = node2.intfName( port2 ) makeIntfPair( intf1, intf2 ) - if node1.inNamespace: - retry( retries, delaySecs, moveIntf, intf1, node1 ) - if node2.inNamespace: - retry( retries, delaySecs, moveIntf, intf2, node2 ) - node1.connection[ intf1 ] = ( node2, intf2 ) - node2.connection[ intf2 ] = ( node1, intf1 ) + node1.addIntf( intf1, port1 ) + node2.addIntf( intf2, port2 ) + node1.connect( intf1, node2, intf2 ) + node2.connect( intf2, node1, intf1 ) return intf1, intf2 def fixLimits(): @@ -141,7 +142,8 @@ def macColonHex( mac ): def ipStr( ip ): """Generate IP address string - returns: ip addr string""" + ip: unsigned int of form x << 16 | y << 8 | z + returns: ip address string 10.x.y.z """ hi = ( ip & 0xff0000 ) >> 16 mid = ( ip & 0xff00 ) >> 8 lo = ip & 0xff