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