From 82b7207295f8cbb57f1dd250f78cee355c6b592d Mon Sep 17 00:00:00 2001
From: Bob Lantz <rlantz@cs.stanford.edu>
Date: Thu, 6 May 2010 16:24:15 -0700
Subject: [PATCH] Pass pylint.

---
 Makefile               |   2 +-
 examples/consoles.py   | 135 +++++++-------
 examples/emptynet.py   |  18 +-
 examples/miniedit.py   | 402 +++++++++++++++++++++++------------------
 examples/udpbwgraph.py | 134 +++++++-------
 mininet/clean.py       |   2 +-
 mininet/cli.py         |  40 ++--
 mininet/net.py         |  52 +++---
 mininet/node.py        |  22 +--
 mininet/term.py        |   6 +-
 mininet/util.py        |  54 +++---
 11 files changed, 466 insertions(+), 401 deletions(-)

diff --git a/Makefile b/Makefile
index 8a90b2c6..84009d73 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@ EXAMPLES = examples/*.py
 BIN = bin/mn
 PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN)
 
-P8IGN = E251,E201,E302
+P8IGN = E251,E201,E302,E202
 
 codecheck: $(PYSRC)
 	pyflakes $(PYSRC)
diff --git a/examples/consoles.py b/examples/consoles.py
index 393ea84e..f131b1c7 100755
--- a/examples/consoles.py
+++ b/examples/consoles.py
@@ -10,7 +10,7 @@
 
 - First, each individual node is monitored, and its output is added
   to its console window
-  
+
 - Second, each time a console window gets iperf output, it is parsed
   and accumulated. Once we have output for all consoles, a bar is
   added to the bandwidth graph.
@@ -36,7 +36,7 @@
 
 class Console( Frame ):
     "A simple console on a host."
-       
+
     def __init__( self, parent, net, node, height=10, width=32, title='Node' ):
         Frame.__init__( self, parent )
 
@@ -44,10 +44,10 @@ def __init__( self, parent, net, node, height=10, width=32, title='Node' ):
         self.node = node
         self.prompt = node.name + '# '
         self.height, self.width, self.title = height, width, title
-                
+
         # Initialize widget styles
         self.buttonStyle = { 'font': 'Monaco 7' }
-        self.textStyle = { 
+        self.textStyle = {
             'font': 'Monaco 7',
             'bg': 'black',
             'fg': 'green',
@@ -58,22 +58,22 @@ def __init__( self, parent, net, node, height=10, width=32, title='Node' ):
             'highlightcolor': 'green',
             'selectforeground': 'black',
             'selectbackground': 'green'
-        }    
+        }
 
         # Set up widgets
         self.text = self.makeWidgets( )
         self.bindEvents()
         self.sendCmd( 'export TERM=dumb' )
-        
+
         self.outputHook = None
-        
+
     def makeWidgets( self ):
         "Make a label, a text area, and a scroll bar."
 
         def newTerm( net=self.net, node=self.node, title=self.title ):
             "Pop up a new terminal window for a node."
             net.terms += makeTerms( [ node ], title )
-        label = Button( self, text=self.node.name, command=newTerm, 
+        label = Button( self, text=self.node.name, command=newTerm,
             **self.buttonStyle )
         label.pack( side='top', fill='x' )
         text = Text( self, wrap='word', **self.textStyle )
@@ -99,7 +99,7 @@ def bindEvents( self ):
     # We're not a terminal (yet?), so we ignore the following
     # control characters other than [\b\n\r]
     ignoreChars  = re.compile( r'[\x00-\x07\x09\x0b\x0c\x0e-\x1f]+' )
-    
+
     def append( self, text ):
         "Append something to our text frame."
         text = self.ignoreChars.sub( '', text )
@@ -114,7 +114,7 @@ def handleKey( self, event ):
         char = event.char
         if self.node.waiting:
             self.node.write( char )
-            
+
     def handleReturn( self, event ):
         "Handle a carriage return."
         cmd = self.text.get( 'insert linestart', 'insert lineend' )
@@ -127,24 +127,29 @@ def handleReturn( self, event ):
         if pos >= 0:
             cmd = cmd[ pos + len( self.prompt ): ]
         self.sendCmd( cmd )
-        
+
+    # Callback ignores event
+    # pylint: disable-msg=W0613
     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."
-        text, node = self.text, self.node
-        if not node.waiting:
-            node.sendCmd( cmd )
+        if not self.node.waiting:
+            self.node.sendCmd( cmd )
 
-    def handleReadable( self, file=None, mask=None, timeoutms=None ):
+    # Callback ignores fds
+    # pylint: disable-msg=W0613
+    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?"
@@ -161,17 +166,17 @@ def clear( self ):
         "Clear all of our text."
         self.text.delete( '1.0', 'end' )
 
-            
+
 class Graph( Frame ):
 
     "Graph that we can add bars to over time."
-    
+
     def __init__( self, parent=None,
         bg = 'white',
         gheight=200, gwidth=500,
         barwidth=10,
         ymax=3.5,):
-        
+
         Frame.__init__( self, parent )
 
         self.bg = bg
@@ -182,57 +187,55 @@ def __init__( self, parent=None,
         self.xpos = 0
 
         # Create everything
-        self.title = self.graph = None
-        self.createWidgets()
+        self.title, self.scale, self.graph = self.createWidgets()
         self.updateScrollRegions()
         self.yview( 'moveto', '1.0' )
-        
-        
-    def scale( self ):
+
+    def createScale( self ):
         "Create a and return a new canvas with scale markers."
         height = float( self.gheight )
         width = 25
         ymax = self.ymax
-        scale = Canvas( self, width=width, height=height, background=self.bg )
-        fill = 'red'
+        scale = Canvas( self, width=width, height=height, 
+            background=self.bg )
+        opts = { 'fill': 'red' }
         # Draw scale line
-        scale.create_line( width - 1, height, width - 1, 0, fill=fill )
+        scale.create_line( width - 1, height, width - 1, 0, **opts )
         # Draw ticks and numbers
         for y in range( 0, int( ymax + 1 ) ):
             ypos = height * (1 - float( y ) / ymax )
-            scale.create_line( width, ypos, width - 10, ypos, fill=fill )
-            scale.create_text( 10, ypos, text=str( y ), fill=fill )
-            
+            scale.create_line( width, ypos, width - 10, ypos, **opts )
+            scale.create_text( 10, ypos, text=str( y ), **opts )
         return scale
-    
+
     def updateScrollRegions( self ):
         "Update graph and scale scroll regions."
         ofs = 20
         height = self.gheight + ofs
-        self.graph.configure( scrollregion=( 0, -ofs, 
+        self.graph.configure( scrollregion=( 0, -ofs,
             self.xpos * self.barwidth, height ) )
         self.scale.configure( scrollregion=( 0, -ofs, 0, height ) )
-        
+
     def yview( self, *args ):
-            "Scroll both scale and graph."
-            self.graph.yview( *args )
-            self.scale.yview( *args )
-                
+        "Scroll both scale and graph."
+        self.graph.yview( *args )
+        self.scale.yview( *args )
+
     def createWidgets( self ):
         "Create initial widget set."
 
         # Objects
-        title = Label( self, text="Bandwidth (Gb/s)", bg=self.bg )
+        title = Label( self, text='Bandwidth (Gb/s)', bg=self.bg )
         width = self.gwidth
         height = self.gheight
-        scale = self.scale()
+        scale = self.createScale()
         graph = Canvas( self, width=width, height=height, background=self.bg)
         xbar = Scrollbar( self, orient='horizontal', command=graph.xview )
         ybar = Scrollbar( self, orient='vertical', command=self.yview )
         graph.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set,
             scrollregion=(0, 0, width, height ) )
         scale.configure( yscrollcommand=ybar.set )
-        
+
         # Layout
         title.grid( row=0, columnspan=3, sticky='new')
         scale.grid( row=1, column=0, sticky='nsew' )
@@ -241,12 +244,8 @@ def createWidgets( self ):
         xbar.grid( row=2, column=0, columnspan=2, sticky='ew' )
         self.rowconfigure( 1, weight=1 )
         self.columnconfigure( 1, weight=1 )
-        # Save for future reference
-        self.title = title
-        self.scale = scale
-        self.graph = graph
-        return graph
-            
+        return title, scale, graph
+
     def addBar( self, yval ):
         "Add a new bar to our graph."
         percent = yval / self.ymax
@@ -279,6 +278,8 @@ def setTitle( self, text ):
 
 class ConsoleApp( Frame ):
 
+    "Simple Tk consoles for Mininet."
+    
     menuStyle = { 'font': 'Geneva 7 bold' }
 
     def __init__( self, net, parent=None, width=4 ):
@@ -289,14 +290,14 @@ def __init__( self, net, parent=None, width=4 ):
         self.menubar = self.createMenuBar()
         cframe = self.cframe = Frame( self )
         self.consoles = {}  # consoles themselves
-        titles = { 
-            'hosts': 'Host', 
+        titles = {
+            'hosts': 'Host',
             'switches': 'Switch',
             'controllers': 'Controller'
         }
         for name in titles:
             nodes = getattr( net, name )
-            frame, consoles = self.createConsoles( 
+            frame, consoles = self.createConsoles(
                 cframe, nodes, width, titles[ name ] )
             self.consoles[ name ] = Object( frame=frame, consoles=consoles )
         self.selected = None
@@ -305,7 +306,7 @@ def __init__( self, net, parent=None, width=4 ):
         cleanUpScreens()
         # Close window gracefully
         Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
-        
+
         # Initialize graph
         graph = Graph( cframe )
         self.consoles[ 'graph' ] = Object( frame=graph, consoles=[ graph ] )
@@ -316,7 +317,9 @@ def __init__( self, net, parent=None, width=4 ):
         self.bw = 0
 
         self.pack( expand=True, fill='both' )
-     
+
+    # Update callback doesn't use console arg
+    # pylint: disable-msg=W0613
     def updateGraph( self, console, output ):
         "Update our graph."
         m = re.search( r'(\d+) Mbits/sec', output )
@@ -328,13 +331,15 @@ def updateGraph( self, console, output ):
             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.]"
         if consoles is None:
             consoles = self.consoles[ 'hosts' ].consoles
         for console in consoles:
             console.outputHook = fn
-            
+
     def createConsoles( self, parent, nodes, width, title ):
         "Create a grid of consoles in a frame."
         f = Frame( parent )
@@ -342,7 +347,7 @@ def createConsoles( self, parent, nodes, width, title ):
         consoles = []
         index = 0
         for node in nodes:
-            console = Console( f, net, node, title=title )
+            console = Console( f, self.net, node, title=title )
             consoles.append( console )
             row = index / width
             column = index % width
@@ -351,12 +356,12 @@ def createConsoles( self, parent, nodes, width, title ):
             f.rowconfigure( row, weight=1 )
             f.columnconfigure( column, weight=1 )
         return f, consoles
-    
-    def select( self, set ):
-        "Select a set of consoles to display."
+
+    def select( self, groupName ):
+        "Select a group of consoles to display."
         if self.selected is not None:
             self.selected.frame.pack_forget()
-        self.selected = self.consoles[ set ]
+        self.selected = self.consoles[ groupName ]
         self.selected.frame.pack( expand=True, fill='both' )
 
     def createMenuBar( self ):
@@ -365,8 +370,8 @@ def createMenuBar( self ):
         buttons = [
             ( 'Hosts', lambda: self.select( 'hosts' ) ),
             ( 'Switches', lambda: self.select( 'switches' ) ),
-            ( 'Controllers', lambda: self.select( 'controllers' ) ),                              
-            ( 'Graph', lambda: self.select( 'graph' ) ),            
+            ( 'Controllers', lambda: self.select( 'controllers' ) ),
+            ( 'Graph', lambda: self.select( 'graph' ) ),
             ( 'Ping', self.ping ),
             ( 'Iperf', self.iperf ),
             ( 'Interrupt', self.stop ),
@@ -378,12 +383,12 @@ def createMenuBar( self ):
             b.pack( side='left' )
         f.pack( padx=4, pady=4, fill='x' )
         return f
-    
+
     def clear( self ):
         "Clear selection."
         for console in self.selected.consoles:
             console.clear()
-    
+
     def waiting( self, consoles=None ):
         "Are any of our hosts waiting for output?"
         if consoles is None:
@@ -453,8 +458,8 @@ def __init__( self, **kwargs ):
 
 if __name__ == '__main__':
     setLogLevel( 'info' )
-    net = TreeNet( depth=2, fanout=2 )
-    net.start()
-    app = ConsoleApp( net, width=4 )
+    network = TreeNet( depth=2, fanout=4 )
+    network.start()
+    app = ConsoleApp( network, width=4 )
     app.mainloop()
-    net.stop()
+    network.stop()
diff --git a/examples/emptynet.py b/examples/emptynet.py
index 4aa4f3d1..9f578551 100755
--- a/examples/emptynet.py
+++ b/examples/emptynet.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 
 """
-This example shows how to create an empty Mininet object 
+This example shows how to create an empty Mininet object
 (without a topology object) and add nodes to it manually.
 """
 
@@ -13,32 +13,32 @@
 def emptyNet():
 
     "Create an empty network and add nodes to it."
-    
+
     net = Mininet( controller=Controller )
 
     info( '*** Adding controller\n' )
     net.addController( 'c0' )
-    
+
     info( '*** Adding hosts\n' )
     h1 = net.addHost( 'h1', ip='10.0.0.1' )
     h2 = net.addHost( 'h2', ip='10.0.0.2' )
-    
+
     info( '*** Adding switch\n' )
     s3 = net.addSwitch( 's3' )
-    
+
     info( '*** Creating links\n' )
     h1.linkTo( s3 )
     h2.linkTo( s3 )
-    
+
     info( '*** Starting network\n')
     net.start()
-    
+
     info( '*** Running CLI\n' )
     CLI( net )
-    
+
     info( '*** Stopping network' )
     net.stop()
-    
+
 if __name__ == '__main__':
     setLogLevel( 'info' )
     emptyNet()
diff --git a/examples/miniedit.py b/examples/miniedit.py
index 41f7f638..1d2a9e05 100755
--- a/examples/miniedit.py
+++ b/examples/miniedit.py
@@ -24,9 +24,9 @@
 class MiniEdit( Frame ):
 
     "A simple network editor for Mininet."
-    
+
     def __init__( self, parent=None, cheight=200, cwidth=500 ):
-        
+
         Frame.__init__( self, parent )
         self.action = None
         self.appName = 'MiniEdit'
@@ -39,19 +39,19 @@ def __init__( self, parent=None, cheight=200, cwidth=500 ):
         # Title
         self.top = self.winfo_toplevel()
         self.top.title( self.appName )
-        
+
         # Menu bar
         self.createMenubar()
-        
+
         # Editing canvas
         self.cheight, self.cwidth = cheight, cwidth
         self.cframe, self.canvas = self.createCanvas()
-        
+
         # Toolbar
+        self.images = miniEditImages()
         self.buttons = {}
         self.active = None
         self.tools = ( 'Select', 'Host', 'Switch', 'Link' )
-        self.images = self.createImages()
         self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
         self.toolbar = self.createToolbar()
 
@@ -61,49 +61,53 @@ def __init__( self, parent=None, cheight=200, cwidth=500 ):
         self.columnconfigure( 1, weight=1 )
         self.rowconfigure( 0, weight=1 )
         self.pack( expand=True, fill='both' )
-    
+
         # About box
         self.aboutBox = None
-        
+
         # Initialize node data
         self.nodeBindings = self.createNodeBindings()
         self.nodePrefixes =  { 'Switch': 's', 'Host': 'h' }
         self.widgetToItem = {}
         self.itemToWidget = {}
-        
+
         # Initialize link tool
         self.link = self.linkWidget = None
 
         # Selection support
         self.selection = None
-        
+
         # Keyboard bindings
         self.bind( '<Control-q>', lambda event: self.quit() )
         self.bind( '<KeyPress-Delete>', self.deleteSelection )
         self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
         self.focus()
-        
+
+        # Event handling initalization
+        self.linkx = self.linky = self.linkItem = None
+        self.lastSelection = None
+
         # Model initialization
         self.links = {}
         self.nodeCount = 0
         self.net = None
-        
+
         # Close window gracefully
         Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
-    
+
     def quit( self ):
         "Stop our network, if any, then quit."
         self.stop()
         Frame.quit( self )
-        
+
     def createMenubar( self ):
         "Create our menu bar."
 
         font = self.font
-        
+
         mbar = Menu( self.top, font=font )
         self.top.configure( menu=mbar )
-        
+
         # Application menu
         appMenu = Menu( mbar, tearoff=False )
         mbar.add_cascade( label=self.appName, font=font, menu=appMenu )
@@ -111,20 +115,18 @@ def createMenubar( self ):
             font=font)
         appMenu.add_separator()
         appMenu.add_command( label='Quit', command=self.quit, font=font )
-        
-        """
-        fileMenu = Menu( mbar, tearoff=False )
-        mbar.add_cascade( label="File", font=font, menu=fileMenu )
-        fileMenu.add_command( label="Load...", font=font )
-        fileMenu.add_separator()
-        fileMenu.add_command( label="Save", font=font )
-        fileMenu.add_separator()
-        fileMenu.add_command( label="Print", font=font )
-        """
-        
+
+        #fileMenu = Menu( mbar, tearoff=False )
+        #mbar.add_cascade( label="File", font=font, menu=fileMenu )
+        #fileMenu.add_command( label="Load...", font=font )
+        #fileMenu.add_separator()
+        #fileMenu.add_command( label="Save", font=font )
+        #fileMenu.add_separator()
+        #fileMenu.add_command( label="Print", font=font )
+
         editMenu = Menu( mbar, tearoff=False )
         mbar.add_cascade( label="Edit", font=font, menu=editMenu )
-        editMenu.add_command( label="Cut", font=font, 
+        editMenu.add_command( label="Cut", font=font,
             command=lambda: self.deleteSelection( None ) )
 
         runMenu = Menu( mbar, tearoff=False )
@@ -135,28 +137,28 @@ def createMenubar( self ):
         runMenu.add_command( label='Xterm', font=font, command=self.xterm )
 
     # Canvas
-        
+
     def createCanvas( self ):
         "Create and return our scrolling canvas frame."
         f = Frame( self )
 
-        canvas = Canvas( f, width=self.cwidth, height=self.cheight, 
+        canvas = Canvas( f, width=self.cwidth, height=self.cheight,
             bg=self.bg )
-        
+
         # Scroll bars
         xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
         ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
         canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
-        
+
         # Resize box
         resize = Label( f, bg='white' )
-        
+
         # Layout
         canvas.grid( row=0, column=1, sticky='nsew')
         ybar.grid( row=0, column=2, sticky='ns')
         xbar.grid( row=1, column=1, sticky='ew' )
         resize.grid( row=1, column=2, sticky='nsew' )
-        
+
         # Resize behavior
         f.rowconfigure( 0, weight=1 )
         f.columnconfigure( 1, weight=1 )
@@ -167,7 +169,7 @@ def createCanvas( self ):
         canvas.bind( '<ButtonPress-1>', self.clickCanvas )
         canvas.bind( '<B1-Motion>', self.dragCanvas )
         canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
-                      
+
         return f, canvas
 
     def updateScrollRegion( self ):
@@ -175,75 +177,76 @@ def updateScrollRegion( self ):
         bbox = self.canvas.bbox( 'all' )
         if bbox is not None:
             self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], bbox[ 3 ] ) )
-        
+
     def canvasx( self, x_root ):
         "Convert root x coordinate to canvas coordinate."
         c = self.canvas
         return c.canvasx( x_root ) - c.winfo_rootx()
-        
+
     def canvasy( self, y_root ):
         "Convert root y coordinate to canvas coordinate."
         c = self.canvas
         return c.canvasy( y_root ) - c.winfo_rooty()
 
     # Toolbar
-    
+
     def activate( self, toolName ):
+        "Activate a tool and press its button."
         # Adjust button appearance
         if self.active:
             self.buttons[ self.active ].configure( relief='raised' )
         self.buttons[ toolName ].configure( relief='sunken' )
         # Activate dynamic bindings
         self.active = toolName
-        
+
     def createToolbar( self ):
         "Create and return our toolbar frame."
-        
+
         toolbar = Frame( self )
-        
+
         # Tools
         for tool in self.tools:
-            cmd = lambda t=tool: self.activate( t )
+            cmd = ( lambda t=tool: self.activate( t ) )
             b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
             if tool in self.images:
                 b.config( height=35, image=self.images[ tool ] )
                 # b.config( compound='top' )
             b.pack( fill='x' )
-            self.buttons[ tool ] = b 
+            self.buttons[ tool ] = b
         self.activate( self.tools[ 0 ] )
-        
+
         # Spacer
         Label( toolbar, text='' ).pack()
-        
+
         # Commands
         for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]:
-            def doCmd( f=getattr( self, 'do' + cmd ) ):
-                f()
-            b = Button( toolbar, text=cmd, font=self.smallFont, fg=color, command=doCmd ) 
+            doCmd = getattr( self, 'do' + cmd )
+            b = Button( toolbar, text=cmd, font=self.smallFont, 
+                fg=color, command=doCmd )
             b.pack( fill='x', side='bottom' )
-        
+
         return toolbar
-    
+
     def doRun( self ):
         "Run command."
         self.activate( 'Select' )
         for tool in self.tools:
             self.buttons[ tool ].config( state='disabled' )
         self.start()
-        
+
     def doStop( self ):
         "Stop command."
         self.stop()
         for tool in self.tools:
             self.buttons[ tool ].config( state='normal' )
-        
+
     # Generic canvas handler
     #
-    # We could have used bindtags, as in nodeIcon, but 
+    # We could have used bindtags, as in nodeIcon, but
     # the dynamic approach used here
     # may actually require less code. In any case, it's an
     # interesting introspection-based alternative to bindtags.
-    
+
     def canvasHandle( self, eventName, event ):
         "Generic canvas event handler"
         if self.active is None:
@@ -252,36 +255,36 @@ def canvasHandle( self, eventName, event ):
         handler = getattr( self, eventName + toolName, None )
         if handler is not None:
             handler( event )
-            
+
     def clickCanvas( self, event ):
         "Canvas click handler."
         self.canvasHandle( 'click', event )
-    
+
     def dragCanvas( self, event ):
         "Canvas drag handler."
         self.canvasHandle( 'drag', event )
-        
+
     def releaseCanvas( self, event ):
         "Canvas mouse up handler."
         self.canvasHandle( 'release', event )
-    
+
     # Currently the only items we can select directly are
     # links. Nodes are handled by bindings in the node icon.
-    # If we want to allow node deletion, we will 
-    
+
     def findItem( self, x, y ):
+        "Find items at a location in our canvas."
         items = self.canvas.find_overlapping( x, y, x, y )
         if len( items ) == 0:
             return None
         else:
             return items[ 0 ]
- 
+
     # Canvas bindings for Select, Host, Switch and Link tools
 
     def clickSelect( self, event ):
         "Select an item."
         self.selectItem( self.findItem( event.x, event.y ) )
-        
+
     def deleteItem( self, item ):
         "Delete an item."
         # Don't delete while network is running
@@ -293,23 +296,27 @@ def deleteItem( self, item ):
         if item in self.itemToWidget:
             self.deleteNode( item )
         # Delete from view
-        self.canvas.delete( item )                
-        
+        self.canvas.delete( item )
+
+    # Callback ignores event
+    # pylint: disable-msg=W0613
     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."
-        icon = Button( self.canvas, image=self.images[ node ], 
+        icon = Button( self.canvas, image=self.images[ node ],
             text=name, compound='top' )
         # Unfortunately bindtags wants a tuple
         bindtags = [ str( self.nodeBindings ) ]
         bindtags += list( icon.bindtags() )
         icon.bindtags( tuple( bindtags ) )
         return icon
-        
+
     def newNode( self, node, event ):
         "Add a new node to our canvas."
         c = self.canvas
@@ -317,12 +324,13 @@ def newNode( self, node, event ):
         self.nodeCount += 1
         name = self.nodePrefixes[ node ] + str( self.nodeCount )
         icon = self.nodeIcon( node, name )
-        item = self.canvas.create_window( x, y, anchor='c', window=icon, tags=node )
+        item = self.canvas.create_window( x, y, anchor='c', 
+            window=icon, tags=node )
         self.widgetToItem[ icon ] = item
         self.itemToWidget[ item ] = icon
         self.selectItem( item )
         icon.links = {}
-            
+
     def clickHost( self, event ):
         "Add a new host to our canvas."
         self.newNode( 'Host', event )
@@ -330,7 +338,7 @@ def clickHost( self, event ):
     def clickSwitch( self, event ):
         "Add a new switch to our canvas."
         self.newNode( 'Switch', event )
-    
+
     def dragLink( self, event ):
         "Drag a link's endpoint to another node."
         if self.link is None:
@@ -341,40 +349,47 @@ def dragLink( self, event ):
         c = self.canvas
         c.coords( self.link, self.linkx, self.linky, x, y )
 
+    # Callback ignores event
+    # pylint: disable-msg=W0613
     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
-        
-    # Generic node handlers 
-        
-    def createBindings( self, bindings ):
-        l = Label()
-        for event, binding in bindings.items():
-            l.bind( event, binding )
-        return l
-            
+    # pylint: enable-msg=W0613
+
+    # Generic node handlers
+
     def createNodeBindings( self ):
         "Create a set of bindings for nodes."
-        return self.createBindings( {
+        bindings = {
             '<ButtonPress-1>': self.clickNode,
             '<B1-Motion>': self.dragNode,
             '<ButtonRelease-1>': self.releaseNode,
             '<Enter>': self.enterNode,
             '<Leave>': self.leaveNode,
             '<Double-ButtonPress-1>': self.xterm
-        } )
-    
+        }
+        l = Label()  # lightweight-ish owner for bindings
+        for event, binding in bindings.items():
+            l.bind( event, binding )
+        return l
+
     def selectItem( self, item ):
+        "Select an item and remember old selection."
         self.lastSelection = self.selection
         self.selection = item
 
     def enterNode( self, event ):
+        "Select node on entry."
         self.selectNode( event )
-        
+
+    # Callback ignores event
+    # pylint: disable-msg=W0613
     def leaveNode( self, event ):
+        "Restore old selection on exit."
         self.selectItem( self.lastSelection )
+    # pylint: enable-msg=W0613
 
     def clickNode( self, event ):
         "Node click handler."
@@ -383,26 +398,26 @@ def clickNode( self, event ):
         else:
             self.selectNode( event )
         return 'break'
-                
+
     def dragNode( self, event ):
         "Node drag handler."
         if self.active is 'Link':
             self.dragLink( event )
         else:
             self.dragNodeAround( event )
-    
+
     def releaseNode( self, event ):
         "Node release handler."
         if self.active is 'Link':
             self.finishLink( event )
-    
+
     # Specific node handlers
 
     def selectNode( self, event ):
         "Select the node that was clicked on."
         item = self.widgetToItem.get( event.widget, None )
         self.selectItem( item )
-        
+
     def dragNodeAround( self, event ):
         "Drag a node around on the canvas."
         c = self.canvas
@@ -429,25 +444,31 @@ def startLink( self, event ):
         w = event.widget
         item = self.widgetToItem[ w ]
         x, y = self.canvas.coords( item )
-        self.link = self.canvas.create_line( x, y, x, y, width=4, 
+        self.link = self.canvas.create_line( x, y, x, y, width=4,
             fill='blue', tag='link' )
         self.linkx, self.linky = x, y
         self.linkWidget = w
         self.linkItem = item
         # Link bindings
         # Selection still needs a bit of work overall
+        # Callbacks ignore event
+        # pylint: disable-msg=W0613
         def select( event, link=self.link ):
+            "Select item on mouse entry."
             self.selectItem( 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 ):
+            "Unhighlight item on mouse exit."
             self.canvas.itemconfig( link, fill='blue' )
             # self.selectItem( None )
+        # pylint: disable-msg=W0613
         self.canvas.tag_bind( self.link, '<Enter>', highlight )
         self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
         self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
-        
+
     def finishLink( self, event ):
         "Finish creating a link"
         if self.link is None:
@@ -471,13 +492,11 @@ def finishLink( self, event ):
         x, y = c.coords( target )
         c.coords( self.link, self.linkx, self.linky, x, y )
         self.addLink( source, dest )
-
-
         # We're done
         self.link = self.linkWidget = None
-    
+
     # Menu handlers
-    
+
     def about( self ):
         "Display about box."
         about = self.aboutBox
@@ -494,28 +513,28 @@ def about( self ):
             line1.pack( padx=20, pady=10 )
             line2.pack(pady=10 )
             line3.pack(pady=10 )
-            hide = lambda about=about: about.withdraw()
+            hide = ( lambda about=about: about.withdraw() )
             self.aboutBox = about
             # Hide on close rather than destroying window
             Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
         # Show (existing) window
         about.deiconify()
 
-    
+
     def createToolImages( self ):
         "Create toolbar (and icon) images."
-        
+
     # Model interface
     #
     # Ultimately we will either want to use a topo or
     # mininet object here, probably.
-    
+
     def addLink( self, source, dest ):
         "Add link to model."
         source.links[ dest ] = self.link
         dest.links[ source ] = self.link
         self.links[ self.link ] = ( source, dest )
-        
+
     def deleteLink( self, link ):
         "Delete link from model."
         pair = self.links.get( link, None )
@@ -539,7 +558,7 @@ def build( self ):
         "Build network based on our topology."
 
         net = Mininet( topo=None )
-        
+
         # Make controller
         net.addController( 'c0' )
         # Make nodes
@@ -559,24 +578,26 @@ def build( self ):
             srcName, dstName  = src[ 'text' ], dst[ 'text' ]
             src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
             src.linkTo( dst )
-        
+
         # Build network (we have to do this separately at the moment )
         net.build()
-        
+
         return net
-        
+
     def start( self ):
+        "Start network."
         if self.net is None:
-            self.net = self.build()
             self.net.start()
-    
+
     def stop( self ):
+        "Stop network."
         if self.net is not None:
             self.net.stop()
         cleanUpScreens()
         self.net = None
-        
+
     def xterm( self, ignore=None ):
+        "Make an xterm when a button is pressed."
         if ( self.selection is None or
              self.net is None or
              self.selection not in self.itemToWidget ):
@@ -584,83 +605,108 @@ def xterm( self, ignore=None ):
         name = self.itemToWidget[ self.selection ][ 'text' ]
         if name not in self.net.nameToNode:
             return
-        self.net.terms.append( makeTerm( self.net.nameToNode[ name ],  'Host' ) )
-    
-    # Image data. Git will be unhappy.
+        term = makeTerm( self.net.nameToNode[ name ], 'Host' )
+        self.net.terms.append( term )
+
+
+def miniEditImages():
+    "Create and return images for MiniEdit."
+
+    # Image data. Git will be unhappy. However, the alternative
+    # is to keep track of separate binary files, which is also
+    # unappealing.
+        
+    return {
+        'Select': BitmapImage(
+            file='/usr/include/X11/bitmaps/left_ptr' ),
+
+        'Host': PhotoImage( data=r"""
+            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
+            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
+            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
+            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
+            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
+            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
+            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
+            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
+            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
+            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
+            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
+            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
+            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
+            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
+            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
+            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
+            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
+            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
+            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
+            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
+            ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw
+            BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2
+            HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO
+            p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/
+            C8cSBBAQADs=
+        """ ),
+
+        'Switch': PhotoImage( data=r"""
+            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
+            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
+            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
+            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
+            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
+            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
+            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
+            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
+            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
+            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
+            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
+            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
+            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
+            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
+            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
+            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
+            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
+            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
+            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
+            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
+            ACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGDCBMqXMiwocOH
+            ECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5
+            /AeTpsyYI1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui1
+            6saLWLNq3cq1q9evYB0GBAA7
+        """ ),
+
+        'Link': PhotoImage( data=r"""
+            R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
+            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
+            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
+            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
+            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
+            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
+            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
+            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
+            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
+            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
+            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
+            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
+            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
+            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
+            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
+            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
+            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
+            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
+            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
+            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
+            ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG
+            Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy
+            lBmxI8mSNknm1Dnx5sCAADs=
+        """ )
+    }
 
-    def createImages( self ):
-        "Initialize button/icon images."
-        images = {}
-        
-        images[ 'Select' ] = BitmapImage( file='/usr/include/X11/bitmaps/left_ptr' )
-        
-        images[ 'Host' ] = PhotoImage( data=r"""
-            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+Z
-            zP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8A
-            zP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZ
-            zMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wA
-            zMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ
-            zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kA
-            zJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZ
-            zGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YA
-            zGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZ
-            zDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA
-            zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZ
-            zACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAA
-            zAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
-            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7
-            u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGD
-            CAcKTMiw4UBwBPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2HEkQZsyC
-            M0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhOp9SWVaVOfWj1KdauTL9q5UgVbFKs
-            EjGqXVtP40NwcBnCjXtw7tx/C8cSBBAQADs=""" )
-            
-        images[ 'Switch' ] = PhotoImage( data=r"""
-            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+Z
-            zP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8A
-            zP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZ
-            zMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wA
-            zMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ
-            zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kA
-            zJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZ
-            zGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YA
-            zGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZ
-            zDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA
-            zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZ
-            zACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAA
-            zAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
-            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7
-            u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGD
-            CBMqXMiwocOHECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5/AeTpsyY
-            I1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui16saLWLNq3cq1q9evYB0GBAA7
-            """ )
-        
-        images[ 'Link' ] = PhotoImage( data=r"""
-            R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+Z
-            zP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8A
-            zP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZ
-            zMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wA
-            zMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ
-            zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kA
-            zJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZ
-            zGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YA
-            zGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZ
-            zDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA
-            zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZ
-            zACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAA
-            zAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
-            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7
-            u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGB
-            rhIeXEgwoUKGCx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEylBmxI8mS
-            Nknm1Dnx5sCAADs=
-            """ )
-            
-        return images
-        
 if __name__ == '__main__':
     setLogLevel( 'info' )
     app = MiniEdit()
     app.mainloop()
 
-        
 
-    
\ No newline at end of file
+
+
diff --git a/examples/udpbwgraph.py b/examples/udpbwgraph.py
index db6a1e59..8e4ffd8c 100755
--- a/examples/udpbwgraph.py
+++ b/examples/udpbwgraph.py
@@ -20,17 +20,17 @@
 from mininet.util import quietRun
 
 # bwtest support
-    
+
 class Graph( Frame ):
 
     "Graph that we can add bars to over time."
-    
+
     def __init__( self, master=None,
         bg = 'white',
         gheight=200, gwidth=500,
         barwidth=10,
         ymax=3.5,):
-        
+
         Frame.__init__( self, master )
 
         self.bg = bg
@@ -41,12 +41,11 @@ def __init__( self, master=None,
         self.xpos = 0
 
         # Create everything
-        self.title = self.graph = None
-        self.createWidgets()
+        self.title, self.graph, self.scale = self.createWidgets()
         self.updateScrollRegions()
         self.yview( 'moveto', '1.0' )
-        
-    def scale( self ):
+
+    def createScale( self ):
         "Create a and return a new canvas with scale markers."
         height = float( self.gheight )
         width = 25
@@ -60,22 +59,22 @@ def scale( self ):
             ypos = height * ( 1 - float( y ) / ymax )
             scale.create_line( width, ypos, width - 10, ypos, fill=fill )
             scale.create_text( 10, ypos, text=str( y ), fill=fill )
-            
+
         return scale
-    
+
     def updateScrollRegions( self ):
         "Update graph and scale scroll regions."
         ofs = 20
         height = self.gheight + ofs
-        self.graph.configure( scrollregion=( 0, -ofs, 
+        self.graph.configure( scrollregion=( 0, -ofs,
             self.xpos * self.barwidth, height ) )
         self.scale.configure( scrollregion=( 0, -ofs, 0, height ) )
-        
+
     def yview( self, *args ):
         "Scroll both scale and graph."
         self.graph.yview( *args )
         self.scale.yview( *args )
-                
+
     def createWidgets( self ):
         "Create initial widget set."
 
@@ -83,14 +82,14 @@ def createWidgets( self ):
         title = Label( self, text="Bandwidth (Mb/s)", bg=self.bg )
         width = self.gwidth
         height = self.gheight
-        scale = self.scale()
+        scale = self.createScale()
         graph = Canvas( self, width=width, height=height, background=self.bg)
         xbar = Scrollbar( self, orient='horizontal', command=graph.xview )
         ybar = Scrollbar( self, orient='vertical', command=self.yview )
         graph.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set,
             scrollregion=(0, 0, width, height ) )
         scale.configure( yscrollcommand=ybar.set )
-        
+
         # Layout
         title.grid( row=0, columnspan=3, sticky='new')
         scale.grid( row=1, column=0, sticky='nsew' )
@@ -99,13 +98,8 @@ def createWidgets( self ):
         xbar.grid( row=2, column=0, columnspan=2, sticky='ew' )
         self.rowconfigure( 1, weight=1 )
         self.columnconfigure( 1, weight=1 )
+        return title, graph, scale
 
-        # Save for future reference
-        self.title = title
-        self.scale = scale
-        self.graph = graph
-        return graph
-            
     def addBar( self, yval ):
         "Add a new bar to our graph."
         percent = yval / self.ymax
@@ -123,7 +117,7 @@ def test( self ):
         "Add a bar for testing purposes."
         ms = 1000
         if self.xpos < 10:
-            self.addBar( self.xpos/10 * self.ymax  )
+            self.addBar( self.xpos / 10 * self.ymax  )
             self.after( ms, self.test )
 
     def setTitle( self, text ):
@@ -134,52 +128,49 @@ def setTitle( self, text ):
 class Controls( Frame ):
 
     "Handy controls for configuring test."
-    
-    switches = { 
+
+    switches = {
         'Kernel Switch': KernelSwitch,
         'User Switch': UserSwitch,
         'Open vSwitch': OVSKernelSwitch
     }
-    
+
     controllers = {
         'Reference Controller': Controller,
         'NOX': NOX
     }
-    
-    def optionMenu( self, name, dict, initval, opts ):
-        "Add a new option menu."
-        var = StringVar()
-        var.set( findKey( dict, initval ) )
-        menu = OptionMenu( self, var, *dict )
-        menu.config( **opts )
-        menu.pack( fill='x' )
-        def value():
-            return dict[ var.get() ]
-        return value
 
-    def __init__( self, master, start, stop, quit ):
-    
+    def __init__( self, master, startFn, stopFn, quitFn ):
+
         Frame.__init__( self, master )
-        
+
         # Option menus
         opts = { 'font': 'Geneva 7 bold' }
-        self.switch = self.optionMenu( 'Switch', self.switches, 
+        self.switch = self.optionMenu( self.switches,
             KernelSwitch, opts )
-        self.controller = self.optionMenu( 'Controller', self.controllers, 
+        self.controller = self.optionMenu( self.controllers,
             Controller, opts)
-            
+
         # Spacer
         pk = { 'fill': 'x' }
         Label( self, **opts ).pack( **pk )
 
         # Buttons
-        self.start = Button( self, text='Start', command=start, **opts )
-        self.stop = Button( self, text='Stop', command=stop, **opts )
-        self.quit = Button( self, text='Quit', command=quit, **opts )
+        self.start = Button( self, text='Start', command=startFn, **opts )
+        self.stop = Button( self, text='Stop', command=stopFn, **opts )
+        self.quit = Button( self, text='Quit', command=quitFn, **opts )
         for button in ( self.start, self.stop, self.quit ):
             button.pack( **pk )
 
-        
+    def optionMenu( self, menuItems, initval, opts ):
+        "Add a new option menu. Returns function to get value."
+        var = StringVar()
+        var.set( findKey( menuItems, initval ) )
+        menu = OptionMenu( self, var, *menuItems )
+        menu.config( **opts )
+        menu.pack( fill='x' )
+        return lambda: menuItems[ var.get() ]
+
 def parsebwtest( line,
     r=re.compile( r'(\d+) s: in ([\d\.]+) MB/s, out ([\d\.]+) MB/s' ) ):
     "Parse udpbwtest.c output, returning seconds, inbw, outbw."
@@ -193,34 +184,41 @@ def parsebwtest( line,
 class UdpBwTest( Frame ):
     "Test and plot UDP bandwidth over time"
 
-    def __init__( self, topo, seconds=60, master=None ):
+    def __init__( self, topo, master=None ):
         "Start up and monitor udpbwtest on each of our hosts."
-        
+
         Frame.__init__( self, master )
 
         self.controls = Controls( self, self.start, self.stop, self.quit )
         self.graph = Graph( self )
-        
+
         # Layout
         self.controls.pack( side='left', expand=False, fill='y' )
         self.graph.pack( side='right', expand=True, fill='both' )
         self.pack( expand=True, fill='both' )
 
+        self.topo = topo
+        self.net = None
+        self.hosts = []
+        self.hostCount = 0
+        self.output = None
+        self.results = {}
         self.running = False
 
     def start( self ):
-        print "start"
-    
+        "Start test."
+
         if self.running:
             return
 
         switch = self.controls.switch()
-        controller = self.controls.controller() 
+        controller = self.controls.controller()
 
-        self.net = Mininet( topo, switch=switch, controller=controller )
+        self.net = Mininet( self.topo, switch=switch, 
+            controller=controller )
         self.hosts = self.net.hosts
         self.hostCount = len( self.hosts )
-        
+
         print "*** Starting network"
         self.net.start()
 
@@ -229,7 +227,7 @@ def start( self ):
         for host in hosts:
             ips = [ h.IP() for h in hosts if h != host ]
             host.cmdPrint( './udpbwtest ' + ' '.join( ips ) + ' &' )
-        
+
         print "*** Monitoring hosts"
         self.output = self.net.monitor( hosts, timeoutms=1 )
         self.results = {}
@@ -238,12 +236,12 @@ def start( self ):
 
     # Pylint isn't smart enough to understand iterator.next()
     # pylint: disable-msg=E1101
-    
+
     def updateGraph( self ):
         "Graph input bandwidth."
-        
+
         print "updateGraph"
-        
+
         if not self.running:
             return
 
@@ -269,20 +267,20 @@ def updateGraph( self ):
 
     def stop( self ):
         "Stop test."
-        
+
         print "*** Stopping udpbwtest processes"
         # We *really* don't want these things hanging around!
         quietRun( 'killall -9 udpbwtest' )
-        
+
         if not self.running:
             return
-            
+
         print "*** Stopping network"
         self.running = False
         self.net.stop()
-    
+
     def quit( self ):
-        print "*** Quitting"
+        "Quit app."
         self.stop()
         Frame.quit( self )
 
@@ -290,9 +288,9 @@ def quit( self ):
 
 # Useful utilities
 
-def findKey( dict, value ):
-    "Find some key where dict[ key ] == value."
-    return [ key for key, val in dict.items() if val == value ][ 0 ]
+def findKey( d, value ):
+    "Find some key where d[ key ] == value."
+    return [ key for key, val in d.items() if val == value ][ 0 ]
 
 def assign( obj, **kwargs):
     "Set a bunch of fields in an object."
@@ -306,7 +304,7 @@ def __init__( self, **kwargs ):
 
 if __name__ == '__main__':
     setLogLevel( 'info' )
-    topo = TreeTopo( depth=1, fanout=2 )
-    app = UdpBwTest( topo )
+    app = UdpBwTest( topo=TreeTopo( depth=2, fanout=2 ) )
     app.mainloop()
-    
\ No newline at end of file
+
+
diff --git a/mininet/clean.py b/mininet/clean.py
index d2335bfc..8dc626a4 100755
--- a/mininet/clean.py
+++ b/mininet/clean.py
@@ -23,7 +23,7 @@ def sh( cmd ):
 def cleanup():
     """Clean up junk which might be left over from old runs;
        do fast stuff before slow dp and link removal!"""
-       
+
     info("*** Removing excess controllers/ofprotocols/ofdatapaths/pings/noxes"
          "\n")
     zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core '
diff --git a/mininet/cli.py b/mininet/cli.py
index 090419d3..a2d5437d 100644
--- a/mininet/cli.py
+++ b/mininet/cli.py
@@ -29,18 +29,18 @@
 from cmd import Cmd
 from os import isatty
 from select import poll, POLLIN
-from sys import stdin
+import sys
 
 from mininet.log import info, output, error
 from mininet.term import makeTerms
 from mininet.util import quietRun, isShellBuiltin
-        
+
 class CLI( Cmd ):
     "Simple command-line interface to talk to nodes."
 
     prompt = 'mininet> '
 
-    def __init__( self, mininet, stdin=stdin ):
+    def __init__( self, mininet, stdin=sys.stdin ):
         self.mn = mininet
         self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
         self.nodemap = {}  # map names to Node objects
@@ -75,7 +75,7 @@ def emptyline( self ):
     # must have the same interface
     # pylint: disable-msg=W0613,R0201
 
-    helpStr = ( 
+    helpStr = (
         'You may also send a command to a node using:\n'
         '  <node> command {args}\n'
         'For example:\n'
@@ -110,7 +110,9 @@ def do_net( self, line ):
         for switch in self.mn.switches:
             output( switch.name, '<->' )
             for intf in switch.intfs.values():
-                node, name  = switch.connection.get( intf, ( None, 'Unknown ' ) )
+                # Ugly, but pylint wants it
+                name  = switch.connection.get( intf,
+                    ( None, 'Unknown ' ) )[ 1 ]
                 output( ' %s' % name )
             output( '\n' )
 
@@ -209,7 +211,7 @@ def do_EOF( self, line ):
     def isatty( self ):
         "Is our standard input a tty?"
         return isatty( self.stdin.fileno() )
-        
+
     def do_noecho( self, line ):
         "Run an interactive command with echoing turned off."
         if self.isatty():
@@ -236,20 +238,16 @@ def default( self, line ):
                     for arg in rest ]
             rest = ' '.join( rest )
             # Run cmd on node:
-            node.sendCmd( rest )
-            self.waitForNode( node, isShellBuiltin( first ) )
+            builtin = isShellBuiltin( first )
+            print "builtin =", builtin
+            node.sendCmd( rest, printPid=( not builtin ) )
+            self.waitForNode( node )
         else:
             error( '*** Unknown command: %s\n' % first )
 
     # pylint: enable-msg=W0613,R0201
 
-    def isReadable( self, poller ):
-        "Check whether a single polled object is readable."
-        for fd, mask in poller.poll( 0 ):
-            if mask & POLLIN:
-                return True
-
-    def waitForNode( self, node, isShellBuiltin=False ):
+    def waitForNode( self, node ):
         "Wait for a node to finish, and  print its output."
         # Pollers
         nodePoller = poll()
@@ -264,10 +262,10 @@ def waitForNode( self, node, isShellBuiltin=False ):
         while True:
             try:
                 bothPoller.poll()
-                if self.isReadable( self.inPoller ):
+                if isReadable( self.inPoller ):
                     key = self.stdin.read( 1 )
                     node.write( key )
-                if self.isReadable( nodePoller ):
+                if isReadable( nodePoller ):
                     data = node.monitor()
                     output( data )
                 if not node.waiting:
@@ -275,3 +273,11 @@ def waitForNode( self, node, isShellBuiltin=False ):
             except KeyboardInterrupt:
                 node.sendInt()
 
+# Helper functions
+
+def isReadable( poller ):
+    "Check whether a Poll object has a readable fd."
+    for fdmask in poller.poll( 0 ):
+        mask = fdmask[ 1 ]
+        if mask & POLLIN:
+            return True
diff --git a/mininet/net.py b/mininet/net.py
index f9183cf9..0e45003e 100755
--- a/mininet/net.py
+++ b/mininet/net.py
@@ -100,26 +100,6 @@
 from mininet.util import createLink, macColonHex, ipStr, ipParse
 from mininet.term import cleanUpScreens, makeTerms
 
-DATAPATHS = [ 'kernel' ]  # [ 'user', 'kernel' ]
-
-
-def init():
-    "Initialize Mininet."
-    if init.inited:
-        return
-    if os.getuid() != 0:
-        # Note: this script must be run as root
-        # Perhaps we should do so automatically!
-        print "*** Mininet must run as root."
-        exit( 1 )
-    # If which produces no output, then mnexec is not in the path.
-    # May want to loosen this to handle mnexec in the current dir.
-    if not quietRun( 'which mnexec' ):
-        raise Exception( "Could not find mnexec - check $PATH" )
-    fixLimits()
-    
-init.inited = False
-
 class Mininet( object ):
     "Network emulation with hosts spawned in network namespaces."
 
@@ -167,7 +147,7 @@ def __init__( self, topo=None, switch=KernelSwitch, host=Host,
         if topo and build:
             self.build()
 
-    
+
     def addHost( self, name, mac=None, ip=None ):
         """Add host.
            name: name of host to add
@@ -195,13 +175,13 @@ def addSwitch( self, name, mac=None, ip=None ):
         self.nameToNode[ name ] = sw
         return sw
 
-    def addController( self, controller ):
+    def addController( self, name='c0', **kwargs ):
         """Add controller.
            controller: Controller class"""
-        controller_new = self.controller( 'c0' )
+        controller_new = self.controller( name, **kwargs )
         if controller_new:  # allow controller-less setups
             self.controllers.append( controller_new )
-            self.nameToNode[ 'c0' ] = controller_new
+            self.nameToNode[ name ] = controller_new
 
     # Control network support:
     #
@@ -558,3 +538,27 @@ def interact( self ):
         result = CLI( self )
         self.stop()
         return result
+
+
+# pylint thinks inited is unused
+# pylint: disable-msg=W0612
+
+def init():
+    "Initialize Mininet."
+    if init.inited:
+        return
+    if os.getuid() != 0:
+        # Note: this script must be run as root
+        # Perhaps we should do so automatically!
+        print "*** Mininet must run as root."
+        exit( 1 )
+    # If which produces no output, then mnexec is not in the path.
+    # May want to loosen this to handle mnexec in the current dir.
+    if not quietRun( 'which mnexec' ):
+        raise Exception( "Could not find mnexec - check $PATH" )
+    fixLimits()
+    init.inited = True
+
+init.inited = False
+
+# pylint: enable-msg=W0612
diff --git a/mininet/node.py b/mininet/node.py
index 32905906..48e8b437 100644
--- a/mininet/node.py
+++ b/mininet/node.py
@@ -95,6 +95,8 @@ def __init__( self, name, inNamespace=True,
         self.lastPid = None
         self.readbuf = ''
         self.waiting = False
+        # Stash additional information as desired
+        self.args = kwargs
 
     @classmethod
     def fdToNode( cls, fd ):
@@ -186,7 +188,7 @@ def sendInt( self, sig=signal.SIGINT ):
         if self.lastPid:
             try:
                 os.kill( self.lastPid, sig )
-            except Exception:
+            except OSError:
                 pass
 
     def monitor( self, timeoutms=None ):
@@ -301,7 +303,7 @@ def linkTo( self, node2, port1=None, port2=None ):
         if port1 is None:
             port1 = node1.newPort()
         if port2 is None:
-            port2 = node2.newPort() 
+            port2 = node2.newPort()
         intf1 = node1.intfName( port1 )
         intf2 = node2.intfName( port2 )
         makeIntfPair( intf1, intf2 )
@@ -410,14 +412,6 @@ def sendCmd( self, *cmd, **kwargs ):
             error( '*** Error: %s has execed and cannot accept commands' %
                      self.name )
 
-    def monitor( self, *args, **kwargs ):
-        "Monitor a switch."
-        if not self.execed:
-            return Node.monitor( self, *args, **kwargs )
-        else:
-            return True, ''
-
-
 class UserSwitch( Switch ):
     "User-space switch."
 
@@ -458,7 +452,7 @@ def stop( self ):
 class KernelSwitch( Switch ):
     """Kernel-space switch.
        Currently only works in root namespace."""
-    
+
     def __init__( self, name, dp=None, **kwargs ):
         """Init.
            name: name for switch
@@ -496,7 +490,7 @@ def start( self, controllers ):
         # Run protocol daemon
         controller = controllers[ 0 ]
         self.cmd( 'ofprotocol ' + self.dp +
-            ' tcp:%s:%d' %  ( controller.IP(), controller.port ) + 
+            ' tcp:%s:%d' %  ( controller.IP(), controller.port ) +
             ' --fail=closed ' + self.opts +
             ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
         self.execed = False
@@ -550,8 +544,8 @@ def start( self, controllers ):
         # Run protocol daemon
         controller = controllers[ 0 ]
         self.cmd( 'ovs-openflowd ' + self.dp +
-            ' tcp:%s:%i' % ( controller.IP(),  controller.port ) + 
-            ' --fail=closed ' + self.opts + 
+            ' tcp:%s:%i' % ( controller.IP(),  controller.port ) +
+            ' --fail=closed ' + self.opts +
             ' 1>' + ofplog + ' 2>' + ofplog + '&' )
         self.execed = False
 
diff --git a/mininet/term.py b/mininet/term.py
index 4ae41187..437c4859 100644
--- a/mininet/term.py
+++ b/mininet/term.py
@@ -15,7 +15,7 @@
 def quoteArg( arg ):
     "Quote an argument if it contains spaces."
     return repr( arg ) if ' ' in arg else arg
-    
+
 def makeTerm( node, title='Node', term='xterm' ):
     """Run screen on a node, and hook up a terminal.
        node: Node object
@@ -38,9 +38,9 @@ def makeTerm( node, title='Node', term='xterm' ):
     else:
         args = [ 'sh', '-c', 'exec tail -f /tmp/' + node.name + '*.log' ]
     if term == 'gterm':
-        # Compress these for gnome-terminal, which expects one token 
+        # Compress these for gnome-terminal, which expects one token
         # to follow the -e option
-        args = [ ' '.join( [ quoteArg( arg ) for arg in args ] ) ] 
+        args = [ ' '.join( [ quoteArg( arg ) for arg in args ] ) ]
     return Popen( cmds[ term ] + args )
 
 def cleanUpScreens():
diff --git a/mininet/util.py b/mininet/util.py
index c16dfc36..75065ead 100644
--- a/mininet/util.py
+++ b/mininet/util.py
@@ -5,7 +5,9 @@
 import select
 from subprocess import call, check_call, Popen, PIPE, STDOUT
 
-from mininet.log import lg
+from mininet.log import error
+
+# Command execution support
 
 def run( cmd ):
     """Simple interface to subprocess.call()
@@ -17,13 +19,16 @@ def checkRun( cmd ):
        cmd: list of command params"""
     return check_call( cmd.split( ' ' ) )
 
+# pylint doesn't understand explicit type checking
+# pylint: disable-msg=E1103
+
 def quietRun( *cmd ):
     """Run a command, routing stderr to stdout, and return the output.
        cmd: list of command params"""
     if len( cmd ) == 1:
         cmd = cmd[ 0 ]
-    if isinstance( cmd, str ):
-        cmd = cmd.split( ' ' )
+        if isinstance( cmd, str ):
+            cmd = cmd.split( ' ' )
     popen = Popen( cmd, stdout=PIPE, stderr=STDOUT )
     # We can't use Popen.communicate() because it uses
     # select(), which can't handle
@@ -42,6 +47,22 @@ def quietRun( *cmd ):
             break
     return output
 
+# pylint: enable-msg=E1103
+# pylint: disable-msg=E1101,W0612
+
+def isShellBuiltin( cmd ):
+    "Return True if cmd is a bash builtin."
+    if isShellBuiltin.builtIns is None:
+        isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
+    space = cmd.find( ' ' )
+    if space > 0:
+        cmd = cmd[ :space]
+    return cmd in isShellBuiltin.builtIns
+
+isShellBuiltin.builtIns = None
+
+# pylint: enable-msg=E1101,W0612
+
 # Interface management
 #
 # Interfaces are managed as strings which are simply the
@@ -78,7 +99,7 @@ def retry( retries, delaySecs, fn, *args, **keywords ):
         sleep( delaySecs )
         tries += 1
     if tries >= retries:
-        lg.error( "*** gave up after %i retries\n" % tries )
+        error( "*** gave up after %i retries\n" % tries )
         exit( 1 )
 
 def moveIntfNoRetry( intf, node, printError=False ):
@@ -91,7 +112,7 @@ def moveIntfNoRetry( intf, node, printError=False ):
     links = node.cmd( 'ip link show' )
     if not ( ' %s:' % intf ) in links:
         if printError:
-            lg.error( '*** Error: moveIntf: ' + intf +
+            error( '*** Error: moveIntf: ' + intf +
                 ' not successfully moved to ' + node.name + '\n' )
         return False
     return True
@@ -112,10 +133,8 @@ def createLink( node1, node2, port1=None, port2=None ):
        returns: intf1 name, intf2 name"""
     return node1.linkTo( node2, port1, port2 )
 
-def fixLimits():
-    "Fix ridiculously small resource limits."
-    setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
-    setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
+
+# IP and Mac address formatting and parsing
 
 def _colonHex( val, bytes ):
     """Generate colon-hex string.
@@ -181,17 +200,10 @@ def makeNumeric( s ):
     else:
         return s
 
-# pylint: disable-msg=E1101,W0612
 
-def isShellBuiltin( cmd ):
-    "Return True if cmd is a bash builtin."
-    if isShellBuiltin.builtIns is None:
-        isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
-    space = cmd.find( ' ' )
-    if space > 0:
-        cmd = cmd[ :space]
-    return cmd in isShellBuiltin.builtIns
+# Other stuff we use
 
-isShellBuiltin.builtIns = None
-
-# pylint: enable-msg=E1101,W0612
+def fixLimits():
+    "Fix ridiculously small resource limits."
+    setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
+    setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
-- 
GitLab