From 14ff3ad3d02dbebf65d0e9aecdbe9c531189261f Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Sat, 10 Mar 2012 20:44:34 -0800 Subject: [PATCH] Fix codecheck and MininetWithControlNet. --- bin/mn | 49 ++++---- examples/consoles.py | 16 +-- examples/limit.py | 7 +- examples/miniedit.py | 25 ++--- examples/scratchnetuser.py | 2 +- mininet/cli.py | 18 +-- mininet/link.py | 224 ++++++++++++++++++++++--------------- mininet/net.py | 40 ++++--- mininet/node.py | 120 +++++++++++--------- mininet/topo.py | 6 +- mininet/util.py | 24 ++-- 11 files changed, 292 insertions(+), 239 deletions(-) diff --git a/bin/mn b/bin/mn index 1768207..a07833a 100755 --- a/bin/mn +++ b/bin/mn @@ -19,7 +19,7 @@ import time from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info, warn -from mininet.net import Mininet +from mininet.net import Mininet, MininetWithControlNet from mininet.node import Host, CPULimitedHost, Controller, OVSController, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch from mininet.link import Intf, TCIntf @@ -32,12 +32,13 @@ def customNode( constructors, argStr ): "Return custom Node constructor based on argStr" cname, newargs, kwargs = splitArgs( argStr ) constructor = constructors.get( cname, None ) - #if args: - # raise Exception( "please specify keyword arguments for " + cname ) + if not constructor: raise Exception( "error: %s is unknown - please specify one of %s" % ( cname, constructors.keys() ) ) - def custom( name, *args, **params ): + + def customized( name, *args, **params ): + "Customized Node constructor" params.update( kwargs ) if not newargs: return constructor( name, *args, **params ) @@ -45,7 +46,8 @@ def customNode( constructors, argStr ): warn( 'warning: %s replacing %s with %s\n', constructor, args, newargs ) return constructor( name, *newargs, **params ) - return custom + + return customized # built in topologies, created only when run @@ -68,7 +70,7 @@ HOSTS = { 'proc': Host, CONTROLLERDEF = 'ref' CONTROLLERS = { 'ref': Controller, 'ovsc': OVSController, - 'nox': NOX, + 'nox': NOX, 'remote': RemoteController, 'none': lambda name: None } @@ -94,8 +96,7 @@ def splitArgs( argstr ): params = split[ 1: ] # Convert int and float args; removes the need for function # to be flexible with input arg formats. - args = [ s for s in params if '=' not in s ] - args = map( makeNumeric, args ) + args = [ makeNumeric( s ) for s in params if '=' not in s ] kwargs = {} for s in [ p for p in params if '=' in p ]: key, val = s.split( '=' ) @@ -122,7 +123,8 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ): raise Exception( 'Invalid default %s for choices dict: %s' % ( default, name ) ) if not helpStr: - helpStr = '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]' + helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + + '[,param=value...]' ) opts.add_option( '--' + name, type='string', default = default, @@ -157,11 +159,11 @@ class MininetRunner( object ): def parseCustomFile( self, fileName ): "Parse custom file and add params before parsing cmd-line options." - custom = {} + customs = {} if os.path.isfile( fileName ): - execfile( fileName, custom, custom ) - for name in custom: - self.setCustom( name, custom[ name ] ) + execfile( fileName, customs, customs ) + for name, val in customs.iteritems(): + self.setCustom( name, val ) else: raise Exception( 'could not find custom file: %s' % fileName ) @@ -171,8 +173,8 @@ class MininetRunner( object ): if '--custom' in sys.argv: index = sys.argv.index( '--custom' ) if len( sys.argv ) > index + 1: - custom = sys.argv[ index + 1 ] - self.parseCustomFile( custom ) + filename = sys.argv[ index + 1 ] + self.parseCustomFile( filename ) else: raise Exception( 'Custom file name not found' ) @@ -241,7 +243,7 @@ class MininetRunner( object ): start = time.time() topo = buildTopo( self.options.topo ) - switch = customNode( SWITCHES, self.options.switch ) + switch = customNode( SWITCHES, self.options.switch ) host = customNode( HOSTS, self.options.host ) controller = customNode( CONTROLLERS, self.options.controller ) intf = customNode( INTFS, self.options.intf ) @@ -250,6 +252,7 @@ class MininetRunner( object ): self.validate( self.options ) inNamespace = self.options.innamespace + Net = MininetWithControlNet if inNamespace else Mininet ipBase = self.options.ipbase xterms = self.options.xterms mac = self.options.mac @@ -257,13 +260,13 @@ class MininetRunner( object ): listenPort = None if not self.options.nolistenport: listenPort = self.options.listenport - mn = Mininet( topo=topo, - switch=switch, host=host, controller=controller, - intf=intf, - ipBase=ipBase, - inNamespace=inNamespace, - xterms=xterms, autoSetMacs=mac, - autoStaticArp=arp, listenPort=listenPort ) + mn = Net( topo=topo, + switch=switch, host=host, controller=controller, + intf=intf, + ipBase=ipBase, + inNamespace=inNamespace, + xterms=xterms, autoSetMacs=mac, + autoStaticArp=arp, listenPort=listenPort ) if self.options.pre: CLI( mn, script=self.options.pre ) diff --git a/examples/consoles.py b/examples/consoles.py index 5729454..2a0c195 100755 --- a/examples/consoles.py +++ b/examples/consoles.py @@ -107,7 +107,7 @@ class Console( Frame ): self.text.insert( 'end', text ) self.text.mark_set( 'insert', 'end' ) self.text.see( 'insert' ) - outputHook = lambda x,y: True # make pylint happy + outputHook = lambda x, y: True # make pylint happier if self.outputHook: outputHook = self.outputHook outputHook( self, text ) @@ -132,27 +132,22 @@ class Console( Frame ): self.sendCmd( cmd ) # Callback ignores event - # pylint: disable-msg=W0613 - def handleInt( self, event=None ): + def handleInt( self, _event=None ): "Handle control-c." self.node.sendInt() - # pylint: enable-msg=W0613 def sendCmd( self, cmd ): "Send a command to our node." if not self.node.waiting: self.node.sendCmd( cmd ) - # Callback ignores fds - # pylint: disable-msg=W0613 - def handleReadable( self, fds, timeoutms=None ): + def handleReadable( self, _fds, timeoutms=None ): "Handle file readable event." data = self.node.monitor( timeoutms ) self.append( data ) if not self.node.waiting: # Print prompt self.append( self.prompt ) - # pylint: enable-msg=W0613 def waiting( self ): "Are we waiting for output?" @@ -321,9 +316,7 @@ class ConsoleApp( Frame ): self.pack( expand=True, fill='both' ) - # Update callback doesn't use console arg - # pylint: disable-msg=W0613 - def updateGraph( self, console, output ): + def updateGraph( self, _console, output ): "Update our graph." m = re.search( r'(\d+) Mbits/sec', output ) if not m: @@ -334,7 +327,6 @@ class ConsoleApp( Frame ): self.graph.addBar( self.bw ) self.bw = 0 self.updates = 0 - # pylint: enable-msg=W0613 def setOutputHook( self, fn=None, consoles=None ): "Register fn as output hook [on specific consoles.]" diff --git a/examples/limit.py b/examples/limit.py index 801159a..4514514 100755 --- a/examples/limit.py +++ b/examples/limit.py @@ -13,10 +13,12 @@ from mininet.log import setLogLevel from time import sleep def testLinkLimit( net, bw ): + "Run bandwidth limit test" print '*** Testing network %.2f Mbps bandwidth limit' % bw net.iperf( ) def testCpuLimit( net, cpu ): + "run CPU limit test" pct = cpu * 100 print '*** Testing CPU %.0f%% bandwidth limit' % pct h1, h2 = net.hosts @@ -25,8 +27,9 @@ def testCpuLimit( net, cpu ): pid1 = h1.cmd( 'echo $!' ).strip() pid2 = h2.cmd( 'echo $!' ).strip() cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 ) - for i in range( 0, 5): - sleep( 1 ) + # It's a shame that this is what pylint prefers + for _ in range( 5 ): + sleep( 1 ) print quietRun( cmd ).strip() h1.cmd( 'kill %1') h2.cmd( 'kill %1') diff --git a/examples/miniedit.py b/examples/miniedit.py index 172a1f4..900b94a 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -299,14 +299,11 @@ class MiniEdit( Frame ): # Delete from view self.canvas.delete( item ) - # Callback ignores event - # pylint: disable-msg=W0613 - def deleteSelection( self, event ): + def deleteSelection( self, _event ): "Delete the selected item." if self.selection is not None: self.deleteItem( self.selection ) self.selectItem( None ) - # pylint: enable-msg=W0613 def nodeIcon( self, node, name ): "Create a new node icon." @@ -350,14 +347,11 @@ class MiniEdit( Frame ): c = self.canvas c.coords( self.link, self.linkx, self.linky, x, y ) - # Callback ignores event - # pylint: disable-msg=W0613 - def releaseLink( self, event ): + def releaseLink( self, _event ): "Give up on the current link." if self.link is not None: self.canvas.delete( self.link ) self.linkWidget = self.linkItem = self.link = None - # pylint: enable-msg=W0613 # Generic node handlers @@ -385,12 +379,9 @@ class MiniEdit( Frame ): "Select node on entry." self.selectNode( event ) - # Callback ignores event - # pylint: disable-msg=W0613 - def leaveNode( self, event ): + def leaveNode( self, _event ): "Restore old selection on exit." self.selectItem( self.lastSelection ) - # pylint: enable-msg=W0613 def clickNode( self, event ): "Node click handler." @@ -454,23 +445,21 @@ class MiniEdit( Frame ): # Link bindings # Selection still needs a bit of work overall # Callbacks ignore event - # pylint: disable-msg=W0613 - def select( event, link=self.link ): + def select( _event, link=self.link ): "Select item on mouse entry." self.selectItem( link ) - def highlight( event, link=self.link ): + def highlight( _event, link=self.link ): "Highlight item on mouse entry." # self.selectItem( link ) self.canvas.itemconfig( link, fill='green' ) - def unhighlight( event, link=self.link ): + def unhighlight( _event, link=self.link ): "Unhighlight item on mouse exit." self.canvas.itemconfig( link, fill='blue' ) # self.selectItem( None ) - # pylint: disable-msg=W0613 self.canvas.tag_bind( self.link, '', highlight ) self.canvas.tag_bind( self.link, '', unhighlight ) self.canvas.tag_bind( self.link, '', select ) @@ -602,7 +591,7 @@ class MiniEdit( Frame ): cleanUpScreens() self.net = None - def xterm( self, ignore=None ): + def xterm( self, _=None ): "Make an xterm when a button is pressed." if ( self.selection is None or self.net is None or diff --git a/examples/scratchnetuser.py b/examples/scratchnetuser.py index 59bc601..4b8b9fd 100755 --- a/examples/scratchnetuser.py +++ b/examples/scratchnetuser.py @@ -52,7 +52,7 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ): info( '*** Starting controller and user datapath\n' ) controller.cmd( cname + ' ' + cargs + '&' ) switch.cmd( 'ifconfig lo 127.0.0.1' ) - intfs = map( str, [ sintf1, sintf2 ] ) + intfs = [ str( i ) for i in sintf1, sintf2 ] switch.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' ptcp: &' ) switch.cmd( 'ofprotocol tcp:' + controller.IP() + ' tcp:localhost &' ) diff --git a/mininet/cli.py b/mininet/cli.py index 3fbedd1..53cd0c0 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -77,7 +77,7 @@ class CLI( Cmd ): # Disable pylint "Unused argument: 'arg's'" messages, as well as # "method could be a function" warning, since each CLI function # must have the same interface - # pylint: disable-msg=W0613,R0201 + # pylint: disable-msg=R0201 helpStr = ( 'You may also send a command to a node using:\n' @@ -104,12 +104,12 @@ class CLI( Cmd ): if line is '': output( self.helpStr ) - def do_nodes( self, line ): + def do_nodes( self, _line ): "List all nodes." nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] ) output( 'available nodes are: \n%s\n' % nodes ) - def do_net( self, line ): + def do_net( self, _line ): "List network connections." for switch in self.mn.switches: output( switch.name, '<->' ) @@ -143,11 +143,11 @@ class CLI( Cmd ): # pylint: enable-msg=W0703 - def do_pingall( self, line ): + def do_pingall( self, _line ): "Ping between all hosts." self.mn.pingAll() - def do_pingpair( self, line ): + def do_pingpair( self, _line ): "Ping between first two hosts, useful for testing." self.mn.pingPair() @@ -191,13 +191,13 @@ class CLI( Cmd ): error( 'invalid number of args: iperfudp bw src dst\n' + 'bw examples: 10M\n' ) - def do_intfs( self, line ): + def do_intfs( self, _line ): "List interfaces." for node in self.nodelist: output( '%s: %s\n' % ( node.name, ' '.join( sorted( node.intfs.values() ) ) ) ) - def do_dump( self, line ): + def do_dump( self, _line ): "Dump node info." for node in self.nodelist: output( '%s\n' % node ) @@ -229,7 +229,7 @@ class CLI( Cmd ): "Spawn gnome-terminal(s) for the given node(s)." self.do_xterm( line, term='gterm' ) - def do_exit( self, line ): + def do_exit( self, _line ): "Exit" return 'exited by user command' @@ -311,7 +311,7 @@ class CLI( Cmd ): else: error( '*** Unknown command: %s\n' % first ) - # pylint: enable-msg=W0613,R0201 + # pylint: enable-msg=R0201 def waitForNode( self, node ): "Wait for a node to finish, and print its output." diff --git a/mininet/link.py b/mininet/link.py index 7d606f9..c07b040 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -41,30 +41,35 @@ class Intf( object ): self.node = node self.name = name self.link = link - self.mac, self.ip = None, None + self.mac, self.ip, self.prefixLen = None, None, None # Add to node (and move ourselves if necessary ) node.addIntf( self ) self.config( **kwargs ) def cmd( self, *args, **kwargs ): + "Run a command in our owning node" return self.node.cmd( *args, **kwargs ) def ifconfig( self, *args ): "Configure ourselves using ifconfig" return self.cmd( 'ifconfig', self.name, *args ) - def setIP( self, ipstr ): + def setIP( self, ipstr, prefixLen=None ): """Set our IP address""" # This is a sign that we should perhaps rethink our prefix - # mechanism - self.ip, self.prefixLen = ipstr.split( '/' ) - return self.ifconfig( ipstr, 'up' ) + # mechanism and/or the way we specify IP addresses + if '/' in ipstr: + self.ip, self.prefixLen = ipstr.split( '/' ) + return self.ifconfig( ipstr, 'up' ) + else: + self.ip, self.prefixLen = ipstr, prefixLen + return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) ) def setMAC( self, macstr ): """Set the MAC address for an interface. macstr: MAC address as string""" self.mac = macstr - return ( self.ifconfig( 'down' ) + + return ( self.ifconfig( 'down' ) + self.ifconfig( 'hw', 'ether', macstr ) + self.ifconfig( 'up' ) ) @@ -78,13 +83,13 @@ class Intf( object ): self.ip = ips[ 0 ] if ips else None return self.ip - def updateMAC( self, intf ): + def updateMAC( self ): "Return updated MAC address based on ifconfig" ifconfig = self.ifconfig() macs = self._macMatchRegex.findall( ifconfig ) self.mac = macs[ 0 ] if macs else None return self.mac - + def IP( self ): "Return IP address" return self.ip @@ -93,9 +98,9 @@ class Intf( object ): "Return MAC address" return self.mac - def isUp( self, set=False ): + def isUp( self, setUp=False ): "Return whether interface is up" - if set: + if setUp: self.ifconfig( 'up' ) return "UP" in self.ifconfig() @@ -124,8 +129,8 @@ class Intf( object ): results[ name ] = result return result - def config( self, mac=None, ip=None, ifconfig=None, - defaultRoute=None, up=True, **params): + def config( self, mac=None, ip=None, ifconfig=None, + up=True, **_params ): """Configure Node according to (optional) parameters: mac: MAC address ip: IP address @@ -153,7 +158,83 @@ class Intf( object ): class TCIntf( Intf ): - "Interface customized by tc (traffic control) utility" + """Interface customized by tc (traffic control) utility + Allows specification of bandwidth limits (various methods) + as well as delay, loss and max queue length""" + + def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False, + enable_ecn=False, enable_red=False ): + "Return tc commands to set bandwidth" + + cmds, parent = [], ' root ' + + if bw and ( bw < 0 or bw > 1000 ): + error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' ) + + elif bw is not None: + # BL: this seems a bit brittle... + if ( speedup > 0 and + self.node.name[0:2] == 'sw' ): + bw = speedup + if use_hfsc: + cmds = [ '%s qdisc add dev %s root handle 1:0 hfsc default 1', + 'class add dev %s parent 1:0 classid 1:1 hfsc sc ' + + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] + elif use_tbf: + latency_us = 10 * 1500 * 8 / bw + cmds = ['%s qdisc add dev %s root handle 1: tbf ' + + 'rate %fMbit burst 15000 latency %fus' % + (bw, latency_us) ] + else: + cmds = [ '%s qdisc add dev %s root handle 1:0 htb default 1', + '%s class add dev %s parent 1:0 classid 1:1 htb ' + + 'rate %fMbit burst 15k' % bw ] + parent = ' parent 1:1 ' + + # ECN or RED + if enable_ecn: + cmds = [ '%s qdisc add dev %s' + parent + + 'handle 10: red limit 1000000 ' + + 'min 20000 max 25000 avpkt 1000 ' + + 'burst 20 ' + + 'bandwidth %fmbit probability 1 ecn' % bw ] + parent = ' parent 10: ' + elif enable_red: + cmds = [ '%s qdisc add dev %s' + parent + + 'handle 10: red limit 1000000 ' + + 'min 20000 max 25000 avpkt 1000 ' + + 'burst 20 ' + + 'bandwidth %fmbit probability 1' % bw ] + parent = ' parent 10: ' + + return cmds, parent + + @staticmethod + def delayCmds( parent, delay=None, loss=None, + max_queue_size=None ): + "Internal method: return tc commands for delay and loss" + cmds = [] + if delay and delay < 0: + error( 'Negative delay', delay, '\n' ) + elif loss and ( loss < 0 or loss > 100 ): + error( 'Bad loss percentage', loss, '%%\n' ) + else: + # Delay/loss/max queue size + netemargs = '%s%s%s' % ( + 'delay %s ' % delay if delay is not None else '', + 'loss %d ' % loss if loss is not None else '', + 'limit %d' % max_queue_size if max_queue_size is not None + else '' ) + if netemargs: + cmds = [ '%s qdisc add dev %s ' + parent + ' netem ' + + netemargs ] + return cmds + + def tc( self, cmd, tc='tc' ): + "Execute tc command for our interface" + c = cmd % (tc, self) # Add in tc command and our name + debug(" *** executing command: %s\n" % c) + return self.cmd( c ) def config( self, bw=None, delay=None, loss=None, disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False, @@ -162,106 +243,58 @@ class TCIntf( Intf ): result = Intf.config( self, **params) - # disable GRO + # Disable GRO if disable_gro: self.cmd( 'ethtool -K %s gro off' % self ) - - if ( bw is None and not delay and not loss + + # Optimization: return if nothing else to configure + # Question: what happens if we want to reset things? + if ( bw is None and not delay and not loss and max_queue_size is None ): return - if bw and ( bw < 0 or bw > 1000 ): - error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' ) - return - - if delay and delay < 0: - error( 'Negative delay', delay, '\n' ) - return + # Clear existing configuration + cmds = [ '%s qdisc del dev %s root' ] - if loss and ( loss < 0 or loss > 100 ): - error( 'Bad loss percentage', loss, '%%\n' ) - return + # Bandwidth limits via various methods + bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup, + use_hfsc=use_hfsc, use_tbf=use_tbf, + enable_ecn=enable_ecn, + enable_red=enable_red ) + cmds += bwcmds - # Ugly but functional + # Delay/loss/max_queue_size using netem + cmds += self.delayCmds( delay=delay, loss=loss, + max_queue_size=max_queue_size, + parent=parent ) + + # Ugly but functional: display configuration info stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + ( [ '%s delay' % delay ] if delay is not None else [] ) + ( ['%d%% loss' % loss ] if loss is not None else [] ) + - ( [ 'ECN' ] if enable_ecn else [ 'RED' ] if enable_red else [] ) ) + ( [ 'ECN' ] if enable_ecn else [ 'RED' ] + if enable_red else [] ) ) info( '(' + ' '.join( stuff ) + ') ' ) - cmds = [ '%s qdisc del dev %s root' ] - - tc = 'tc' # was getCmd( 'tc' ) - - # Bandwidth control algorithms - if bw is None: - parent = ' root ' - else: - parent = ' parent 1:1 ' - # BL: hmm... this seems a bit brittle - if speedup > 0 and self.node.name[0:2] == 'sw': - bw = speedup - if use_hfsc: - cmds += [ '%s qdisc add dev %s root handle 1:0 hfsc default 1', - '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' + - 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] - elif use_tbf: - latency_us = 10 * 1500 * 8 / bw - cmds += ['%s qdisc add dev %s root handle 1: tbf ' + - 'rate %fMbit burst 15000 latency %fus' % (bw, latency_us) ] - else: - cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1', - '%s class add dev %s parent 1:0 classid 1:1 htb ' + - 'rate %fMbit burst 15k' % bw ] - parent = ' parent 1:1 ' - - # ECN or RED - if enable_ecn: - cmds += [ '%s qdisc add dev %s' + parent + - 'handle 10: red limit 1000000 '+ - 'min 20000 max 25000 avpkt 1000 '+ - 'burst 20 '+ - 'bandwidth %fmbit probability 1 ecn' % bw ] - parent = ' parent 10: ' - elif enable_red: - cmds += [ '%s qdisc add dev %s' + parent + - 'handle 10: red limit 1000000 '+ - 'min 20000 max 25000 avpkt 1000 '+ - 'burst 20 '+ - 'bandwidth %fmbit probability 1' % bw ] - parent = ' parent 10: ' - - # Delay/loss/max queue size - netemargs = '%s%s%s' % ( - 'delay %s ' % delay if delay is not None else '', - 'loss %d ' % loss if loss is not None else '', - 'limit %d' % max_queue_size if max_queue_size is not None else '' ) - if netemargs: - cmds += [ '%s qdisc add dev %s ' + parent + ' netem ' + - netemargs ] - - # Execute all the commands in the container + # Execute all the commands in our node debug("at map stage w/cmds: %s\n" % cmds) - - def doConfigPort(s): - c = s % (tc, self) - debug(" *** executing command: %s\n" % c) - return self.cmd(c) - - tcoutputs = [ doConfigPort(cmd) for cmd in cmds ] + tcoutputs = [ self.tc(cmd) for cmd in cmds ] debug( "cmds:", cmds, '\n' ) debug( "outputs:", tcoutputs, '\n' ) result[ 'tcoutputs'] = tcoutputs + return result class Link( object ): - + """A basic link is just a veth pair. Other types of links could be tunnels, link emulators, etc..""" - def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None, - intf=Intf, cls1=None, cls2=None, params1={}, params2={} ): + def __init__( self, node1, node2, port1=None, port2=None, + intfName1=None, intfName2=None, + intf=Intf, cls1=None, cls2=None, params1=None, + params2=None ): """Create veth link to another node, making two new interfaces. node1: first node node2: second node @@ -284,13 +317,21 @@ class Link( object ): intfName1 = self.intfName( node1, port1 ) if not intfName2: intfName2 = self.intfName( node2, port2 ) + self.makeIntfPair( intfName1, intfName2 ) + if not cls1: cls1 = intf if not cls2: cls2 = intf + if not params1: + params1 = {} + if not params2: + params2 = {} + intf1 = cls1( name=intfName1, node=node1, link=self, **params1 ) intf2 = cls2( name=intfName2, node=node2, link=self, **params2 ) + # All we are is dust in the wind, and our two interfaces self.intf1, self.intf2 = intf1, intf2 @@ -304,7 +345,8 @@ class Link( object ): """Create pair of interfaces intf1: name of interface 1 intf2: name of interface 2 - (override this class method [and possibly delete()] to change link type)""" + (override this class method [and possibly delete()] + to change link type)""" makeIntfPair( intf1, intf2 ) def delete( self ): diff --git a/mininet/net.py b/mininet/net.py index 9e1e161..7384700 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -104,7 +104,7 @@ class Mininet( object ): "Network emulation with hosts spawned in network namespaces." def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, - controller=Controller, link=Link, intf=None, + controller=Controller, link=Link, intf=None, build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', inNamespace=False, autoSetMacs=False, autoStaticArp=False, listenPort=None ): @@ -176,7 +176,7 @@ class Mininet( object ): switch: custom switch constructor (optional) returns: added switch side effect: increments listenPort ivar .""" - defaults = { 'listenPort': self.listenPort, + defaults = { 'listenPort': self.listenPort, 'inNamespace': self.inNamespace } defaults.update( params ) if not switch: @@ -229,7 +229,7 @@ class Mininet( object ): ipBaseNum=ipBaseNum, prefixLen=prefixLen ) } if self.autoSetMacs: - defaults[ 'mac'] = macColonHex( nodeId ) + defaults[ 'mac'] = macColonHex( nodeId ) defaults.update( ni.params ) node = addMethod( name, cls=ni.cls, **defaults ) self.idToNode[ nodeId ] = node @@ -275,17 +275,16 @@ class Mininet( object ): info( '\n' ) - def configureControlNetwork( self ): - error( "configureControlNetwork: override in subclass, or use" - "MininetWithControlNet class" ) + "Control net config hook: override in subclass" + raise Exception( 'configureControlNetwork: ' + 'should be overriden in subclass', self ) def build( self ): "Build mininet." if self.topo: self.buildFromTopo( self.topo ) - if self.inNamespace: - info( '*** Configuring control network\n' ) + if ( self.inNamespace ): self.configureControlNetwork() info( '*** Configuring hosts\n' ) self.configHosts() @@ -533,7 +532,7 @@ class Mininet( object ): return result inited = False - + @classmethod def init( cls ): "Initialize Mininet" @@ -541,7 +540,8 @@ class Mininet( object ): return if os.getuid() != 0: # Note: this script must be run as root - # Perhaps we should do so automatically! + # Probably we should only sudo when we need + # to as per Big Switch's patch print "*** Mininet must run as root." exit( 1 ) fixLimits() @@ -570,7 +570,11 @@ class MininetWithControlNet( Mininet ): network (since real networks may need one!) 5. Basically nobody ever used this code, so it has been moved - into its own class.""" + into its own class. + + 6. Ultimately we may wish to extend this to allow us to create a + control network which every node's control interface is + attached to.""" def configureControlNetwork( self ): "Configure control network." @@ -589,27 +593,27 @@ class MininetWithControlNet( Mininet ): snum = ipParse( ip ) for switch in self.switches: info( ' ' + switch.name ) - sintf, cintf = self.link( switch, controller ) + link = self.link( switch, controller, port1=0 ) + sintf, cintf = link.intf1, link.intf2 + switch.controlIntf = sintf snum += 1 while snum & 0xff in [ 0, 255 ]: snum += 1 sip = ipStr( snum ) - controller.setIP( cintf, cip, prefixLen ) - switch.setIP( sintf, sip, prefixLen ) + cintf.setIP( cip, prefixLen ) + sintf.setIP( sip, prefixLen ) controller.setHostRoute( sip, cintf ) switch.setHostRoute( cip, sintf ) info( '\n' ) info( '*** Testing control network\n' ) - while not controller.intfIsUp( cintf ): + while not cintf.isUp(): info( '*** Waiting for', cintf, 'to come up\n' ) sleep( 1 ) for switch in self.switches: - while not switch.intfIsUp( sintf ): + while not sintf.isUp(): info( '*** Waiting for', sintf, 'to come up\n' ) sleep( 1 ) if self.ping( hosts=[ switch, controller ] ) != 0: error( '*** Error: control network test failed\n' ) exit( 1 ) info( '\n' ) - - diff --git a/mininet/node.py b/mininet/node.py index cbe2917..304a67a 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -81,8 +81,14 @@ class Node( object ): # replace with Port objects, eventually ? self.nameToIntf = {} # dict of interface names to Intfs + # Make pylint happy + ( self.shell, self.execed, self.pid, self.stdin, self.stdout, + self.lastPid, self.lastCmd, self.pollOut ) = ( + None, None, None, None, None, None, None, None ) + self.waiting = False + self.readbuf = '' + # Start command interpreter shell - self.shell = None self.startShell() # File descriptor to node mapping support @@ -99,28 +105,6 @@ class Node( object ): node = cls.outToNode.get( fd ) return node or cls.inToNode.get( fd ) - # Automatic class setup support - - isSetup = False; - - @classmethod - def checkSetup( cls ): - "Make sure our class and superclasses are set up" - while cls and not getattr( cls, 'isSetup', True ): - cls.setup() - cls.isSetup = True - # Make pylint happy - cls = getattr( type( cls ), '__base__', None ) - - @classmethod - def setup( cls ): - "Make sure our class dependencies are available" - pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet') - - def cleanup( self ): - "Help python collect its garbage." - self.shell = None - # Command support via shell process in namespace def startShell( self ): @@ -129,7 +113,7 @@ class Node( object ): error( "%s: shell is already running" ) return # mnexec: (c)lose descriptors, (d)etach from tty, - # (p)rint pid, and run in (n)amespace + # (p)rint pid, and run in (n)amespace opts = '-cdp' if self.inNamespace: opts += 'n' @@ -153,19 +137,23 @@ class Node( object ): self.readbuf = '' self.waiting = False - def read( self, bytes=1024 ): + def cleanup( self ): + "Help python collect its garbage." + self.shell = None + + def read( self, maxbytes=1024 ): """Buffered read from node, non-blocking. - bytes: maximum number of bytes to return""" + maxbytes: maximum number of bytes to return""" count = len( self.readbuf ) - if count < bytes: - data = os.read( self.stdout.fileno(), bytes - count ) + if count < maxbytes: + data = os.read( self.stdout.fileno(), maxbytes - count ) self.readbuf += data - if bytes >= len( self.readbuf ): + if maxbytes >= len( self.readbuf ): result = self.readbuf self.readbuf = '' else: - result = self.readbuf[ :bytes ] - self.readbuf = self.readbuf[ bytes: ] + result = self.readbuf[ :maxbytes ] + self.readbuf = self.readbuf[ maxbytes: ] return result def readline( self ): @@ -307,7 +295,7 @@ class Node( object ): self.ports[ intf ] = port self.nameToIntf[ intf.name ] = intf debug( '\n' ) - debug( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) + debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) ) if self.inNamespace: debug( 'moving', intf, 'into namespace for', self.name, '\n' ) moveIntf( intf.name, self ) @@ -363,7 +351,7 @@ class Node( object ): """Add route to host. ip: IP address as dotted decimal intf: string, interface name""" - return self.cmd( 'route add -host ' + ip + ' dev ' + intf ) + return self.cmd( 'route add -host', ip, 'dev', intf ) def setDefaultRoute( self, intf=None ): """Set the default route to go through intf. @@ -430,8 +418,8 @@ class Node( object ): results[ name ] = result return result - def config( self, mac=None, ip=None, ifconfig=None, - defaultRoute=None, **params): + def config( self, mac=None, ip=None, ifconfig=None, + defaultRoute=None, **_params ): """Configure Node according to (optional) parameters: mac: MAC address for default interface ip: IP address for default interface @@ -440,7 +428,7 @@ class Node( object ): the parent class's config(**params)""" # If we were overriding this method, we would call # the superclass config method here as follows: - # r = Parent.config( **params ) + # r = Parent.config( **_params ) r = {} self.setParam( r, 'setMAC', mac=mac ) self.setParam( r, 'setIP', ip=ip ) @@ -473,6 +461,24 @@ class Node( object ): return '%s: IP=%s intfs=%s pid=%s' % ( self.name, self.IP(), ','.join( self.intfNames() ), self.pid ) + # Automatic class setup support + + isSetup = False + + @classmethod + def checkSetup( cls ): + "Make sure our class and superclasses are set up" + while cls and not getattr( cls, 'isSetup', True ): + cls.setup() + cls.isSetup = True + # Make pylint happy + cls = getattr( type( cls ), '__base__', None ) + + @classmethod + def setup( cls ): + "Make sure our class dependencies are available" + pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet') + class Host( Node ): "A host is simply a Node" @@ -484,7 +490,7 @@ class CPULimitedHost( Host ): "CPU limited host" def __init__( self, *args, **kwargs ): - Node.__init__( self, *args, **kwargs ) + Host.__init__( self, *args, **kwargs ) # Create a cgroup and move shell into it self.cgroup = 'cpu,cpuacct:/' + self.name errFail( 'cgcreate -g ' + self.cgroup ) @@ -510,6 +516,7 @@ class CPULimitedHost( Host ): return nvalue def cgroupGet( self, param, resource='cpu' ): + "Return value of cgroup parameter" cmd = 'cgget -r %s.%s /%s' % ( resource, param, self.name ) return quietRun( cmd ).split()[ -1 ] @@ -544,7 +551,7 @@ class CPULimitedHost( Host ): return pstr, qstr, period, quota # BL comment: - # This may not be the right API, + # This may not be the right API, # since it doesn't specify CPU bandwidth in "absolute" # units the way link bandwidth is specified. # We should use MIPS or SPECINT or something instead. @@ -578,7 +585,7 @@ class CPULimitedHost( Host ): self.chrt( prio=20 ) info( '(%s %d/%dus) ' % ( sched, quota, period ) ) - def config( self, cpu=None, sched=None, **params ): + def config( self, cpu=None, **params ): """cpu: desired overall system CPU fraction params: parameters for Node.config()""" r = Node.config( self, **params ) @@ -665,8 +672,8 @@ class UserSwitch( Switch ): pathCheck( 'ofdatapath', 'ofprotocol', moduleName='the OpenFlow reference user switch (openflow.org)' ) - @staticmethod - def setup(): + @classmethod + def setup( cls ): "Ensure any dependencies are loaded; if not, try to load them." if not os.path.exists( '/dev/net/tun' ): moduleDeps( add=TUN ) @@ -684,7 +691,7 @@ class UserSwitch( Switch ): if self.inNamespace: intfs = intfs[ :-1 ] self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + - ' punix:/tmp/' + self.name + ' -d ' + self.dpid + + ' punix:/tmp/' + self.name + ' -d ' + self.dpid + ' --no-slicing ' + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) self.cmd( 'ofprotocol unix:/tmp/' + self.name + @@ -716,8 +723,8 @@ class OVSLegacyKernelSwitch( Switch ): " in the root namespace.\n" ) exit( 1 ) - @staticmethod - def setup(): + @classmethod + def setup( cls ): "Ensure any dependencies are loaded; if not, try to load them." pathCheck( 'ovs-dpctl', 'ovs-openflowd', moduleName='Open vSwitch (openvswitch.org)') @@ -741,7 +748,7 @@ class OVSLegacyKernelSwitch( Switch ): controller = controllers[ 0 ] self.cmd( 'ovs-openflowd ' + self.dp + ' tcp:%s:%d' % ( controller.IP(), controller.port ) + - ' --fail=secure ' + self.opts + + ' --fail=secure ' + self.opts + ' --datapath-id=' + self.dpid + ' 1>' + ofplog + ' 2>' + ofplog + '&' ) self.execed = False @@ -766,26 +773,34 @@ class OVSSwitch( Switch ): # dpid, which is a 64-bit numerical value used by # the openflow protocol. self.dp = name - - @staticmethod - def setup(): + if self.inNamespace: + error( "OVSSwitch currently only works" + " in the root namespace.\n" ) + exit( 1 ) + + @classmethod + def setup( cls ): "Make sure Open vSwitch is installed and working" - pathCheck( 'ovs-vsctl', + pathCheck( 'ovs-vsctl', moduleName='Open vSwitch (openvswitch.org)') moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' ) if exitcode: - error( out + err + + error( out + err + 'ovs-vsctl exited with code %d\n' % exitcode + '*** Error connecting to ovs-db with ovs-vsctl\n' 'Make sure that Open vSwitch is installed, ' 'that ovsdb-server is running, and that\n' '"ovs-vsctl show" works correctly.\n' - 'You may wish to try "service openvswitch-switch start".\n' ) + 'You may wish to try ' + '"service openvswitch-switch start".\n' ) exit( 1 ) def start( self, controllers ): "Start up a new OVS OpenFlow switch using ovs-vsctl" + if self.inNamespace: + raise Exception( + 'OVS kernel switch does not work in a namespace' ) # Annoyingly, --if-exists option seems not to work self.cmd( 'ovs-vsctl del-br ', self.dp ) self.cmd( 'ovs-vsctl add-br', self.dp ) @@ -800,7 +815,8 @@ class OVSSwitch( Switch ): self.cmd( 'ovs-vsctl add-port', self.dp, intf ) self.cmd( 'ifconfig', intf, 'up' ) # Add controllers - clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) for c in controllers ] ) + clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) + for c in controllers ] ) self.cmd( 'ovs-vsctl set-controller', self.dp, clist ) def stop( self ): diff --git a/mininet/topo.py b/mininet/topo.py index 6b4e572..3de788c 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,7 +16,7 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph -from util import netParse, ipStr +from mininet.util import netParse, ipStr class NodeID(object): '''Topo node identifier.''' @@ -116,7 +116,7 @@ class Topo(object): per-node/link classes and parameters per-topo classes per-network classes""" - + def __init__(self, node=None, switch=None, link=None ): """Create Topo object. node: default node/host class (optional) @@ -364,7 +364,7 @@ class Topo(object): # BL: may wish to rethink this or just use dicts.. return self.node_info[ dpid ] - + class SingleSwitchTopo(Topo): '''Single switch connected to k hosts.''' diff --git a/mininet/util.py b/mininet/util.py index 79b4a12..450472a 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -47,10 +47,11 @@ def oldQuietRun( *cmd ): break return out + # This is a bit complicated, but it enables us to # monitor commount output as it is happening -def errRun( *cmd, **kwargs ): +def errRun( *cmd, **kwargs ): """Run a command and return stdout, stderr and return code cmd: string or list of command and args stderr: STDOUT to merge stderr with stdout @@ -80,7 +81,10 @@ def errRun( *cmd, **kwargs ): poller.register( popen.stderr, POLLIN ) while True: readable = poller.poll() + # Tell pylint to ignore unused variable event + # pylint: disable-msg=W0612 for fd, event in readable: + # pylint: enable-msg=W0612 f = fdtofile[ fd ] data = f.read( 1024 ) if echo: @@ -91,7 +95,7 @@ def errRun( *cmd, **kwargs ): err += data returncode = popen.poll() if returncode is not None: - break + break return out, err, returncode def errFail( *cmd, **kwargs ): @@ -186,13 +190,13 @@ def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ): # IP and Mac address formatting and parsing -def _colonHex( val, bytes ): +def _colonHex( val, bytecount ): """Generate colon-hex string. val: input as unsigned int - bytes: number of bytes to convert + bytescount: number of bytes to convert returns: chStr colon-hex string""" pieces = [] - for i in range( bytes - 1, -1, -1 ): + for i in range( bytecount - 1, -1, -1 ): piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 ) pieces.append( '%02x' % piece ) chStr = ':'.join( pieces ) @@ -204,14 +208,14 @@ def macColonHex( mac ): returns: macStr MAC colon-hex string""" return _colonHex( mac, 6 ) -def ipStr( ip, defaultNet=10 ): +def ipStr( ip ): """Generate IP address string from an unsigned int. ip: unsigned int of form w << 24 | x << 16 | y << 8 | z returns: ip address string w.x.y.z, or 10.x.y.z if w==0""" w = ( ip >> 24 ) & 0xff w = 10 if w == 0 else w - x = ( ip >> 16 ) & 0xff - y = ( ip >> 8 ) & 0xff + x = ( ip >> 16 ) & 0xff + y = ( ip >> 8 ) & 0xff z = ip & 0xff return "%i.%i.%i.%i" % ( w, x, y, z ) @@ -270,6 +274,7 @@ def fixLimits(): def natural( text ): "To sort sanely/alphabetically: sorted( l, key=natural )" def num( s ): + "Convert text segment to int if necessary" return int( s ) if s.isdigit() else text return [ num( s ) for s in re.split( r'(\d+)', text ) ] @@ -286,8 +291,7 @@ def numCores(): def custom( cls, **params ): "Returns customized constructor for class cls." def customized( *args, **kwargs): + "Customized constructor" kwargs.update( params ) return cls( *args, **kwargs ) return customized - -