Statistics
| Branch: | Tag: | Revision:

mininet / examples / consoles.py @ 8a130dea

History | View | Annotate | Download (15 KB)

1 afe0ce81 Bob Lantz
#!/usr/bin/python
2
3
"""
4
consoles.py: bring up a bunch of miniature consoles on a virtual network
5

6
This demo shows how to monitor a set of nodes by using
7 8e4d818a Bob Lantz
Node's monitor() and Tkinter's createfilehandler().
8 ab1fb093 Bob Lantz

9
We monitor nodes in a couple of ways:
10

11
- First, each individual node is monitored, and its output is added
12
  to its console window
13 82b72072 Bob Lantz

14 ab1fb093 Bob Lantz
- Second, each time a console window gets iperf output, it is parsed
15
  and accumulated. Once we have output for all consoles, a bar is
16
  added to the bandwidth graph.
17

18
The consoles also support limited interaction:
19

20
- Pressing "return" in a console will send a command to it
21

22
- Pressing the console's title button will open up an xterm
23

24
Bob Lantz, April 2010
25

26 afe0ce81 Bob Lantz
"""
27
28 3482b446 Bob Lantz
import re
29 afe0ce81 Bob Lantz
30 259d7133 Bob Lantz
from Tkinter import Frame, Button, Label, Text, Scrollbar, Canvas, Wm, READABLE
31
32
from mininet.log import setLogLevel
33 afe0ce81 Bob Lantz
from mininet.topolib import TreeNet
34 d0e53ca8 Bob Lantz
from mininet.term import makeTerms, cleanUpScreens
35 7ad48292 Bob Lantz
from mininet.util import quietRun
36 afe0ce81 Bob Lantz
37
class Console( Frame ):
38
    "A simple console on a host."
39 82b72072 Bob Lantz
40 e7ba6b9e Bob Lantz
    def __init__( self, parent, net, node, height=10, width=32, title='Node' ):
41 afe0ce81 Bob Lantz
        Frame.__init__( self, parent )
42 fce4d59d Bob Lantz
43 d0e53ca8 Bob Lantz
        self.net = net
44 afe0ce81 Bob Lantz
        self.node = node
45 d0e53ca8 Bob Lantz
        self.prompt = node.name + '# '
46 e7ba6b9e Bob Lantz
        self.height, self.width, self.title = height, width, title
47 82b72072 Bob Lantz
48 fce4d59d Bob Lantz
        # Initialize widget styles
49
        self.buttonStyle = { 'font': 'Monaco 7' }
50 82b72072 Bob Lantz
        self.textStyle = {
51 afe0ce81 Bob Lantz
            'font': 'Monaco 7',
52
            'bg': 'black',
53
            'fg': 'green',
54
            'width': self.width,
55
            'height': self.height,
56 d0e53ca8 Bob Lantz
            'relief': 'sunken',
57
            'insertbackground': 'green',
58
            'highlightcolor': 'green',
59
            'selectforeground': 'black',
60
            'selectbackground': 'green'
61 82b72072 Bob Lantz
        }
62 fce4d59d Bob Lantz
63
        # Set up widgets
64
        self.text = self.makeWidgets( )
65
        self.bindEvents()
66 3482b446 Bob Lantz
        self.sendCmd( 'export TERM=dumb' )
67 82b72072 Bob Lantz
68 ab1fb093 Bob Lantz
        self.outputHook = None
69 82b72072 Bob Lantz
70 fce4d59d Bob Lantz
    def makeWidgets( self ):
71
        "Make a label, a text area, and a scroll bar."
72
73 e7ba6b9e Bob Lantz
        def newTerm( net=self.net, node=self.node, title=self.title ):
74 d0e53ca8 Bob Lantz
            "Pop up a new terminal window for a node."
75 e7ba6b9e Bob Lantz
            net.terms += makeTerms( [ node ], title )
76 82b72072 Bob Lantz
        label = Button( self, text=self.node.name, command=newTerm,
77 fce4d59d Bob Lantz
            **self.buttonStyle )
78 afe0ce81 Bob Lantz
        label.pack( side='top', fill='x' )
79 fce4d59d Bob Lantz
        text = Text( self, wrap='word', **self.textStyle )
80 e3f6ecca Bob Lantz
        ybar = Scrollbar( self, orient='vertical', width=7,
81
            command=text.yview )
82 afe0ce81 Bob Lantz
        text.configure( yscrollcommand=ybar.set )
83
        text.pack( side='left', expand=True, fill='both' )
84
        ybar.pack( side='right', fill='y' )
85
        return text
86
87
    def bindEvents( self ):
88
        "Bind keyboard and file events."
89 e7ba6b9e Bob Lantz
        # The text widget handles regular key presses, but we
90
        # use special handlers for the following:
91 afe0ce81 Bob Lantz
        self.text.bind( '<Return>', self.handleReturn )
92
        self.text.bind( '<Control-c>', self.handleInt )
93 3482b446 Bob Lantz
        self.text.bind( '<KeyPress>', self.handleKey )
94 afe0ce81 Bob Lantz
        # This is not well-documented, but it is the correct
95
        # way to trigger a file event handler from Tk's
96
        # event loop!
97
        self.tk.createfilehandler( self.node.stdout, READABLE,
98
            self.handleReadable )
99
100 3482b446 Bob Lantz
    # We're not a terminal (yet?), so we ignore the following
101
    # control characters other than [\b\n\r]
102 e3f6ecca Bob Lantz
    ignoreChars = re.compile( r'[\x00-\x07\x09\x0b\x0c\x0e-\x1f]+' )
103 82b72072 Bob Lantz
104 afe0ce81 Bob Lantz
    def append( self, text ):
105
        "Append something to our text frame."
106 3482b446 Bob Lantz
        text = self.ignoreChars.sub( '', text )
107 afe0ce81 Bob Lantz
        self.text.insert( 'end', text )
108
        self.text.mark_set( 'insert', 'end' )
109
        self.text.see( 'insert' )
110 ab1fb093 Bob Lantz
        if self.outputHook:
111
            self.outputHook( self, text )
112
113 3482b446 Bob Lantz
    def handleKey( self, event ):
114
        "If it's an interactive command, send it to the node."
115
        char = event.char
116
        if self.node.waiting:
117
            self.node.write( char )
118 82b72072 Bob Lantz
119 afe0ce81 Bob Lantz
    def handleReturn( self, event ):
120
        "Handle a carriage return."
121 d0e53ca8 Bob Lantz
        cmd = self.text.get( 'insert linestart', 'insert lineend' )
122 3482b446 Bob Lantz
        # Send it immediately, if "interactive" command
123
        if self.node.waiting:
124
            self.node.write( event.char )
125
            return
126
        # Otherwise send the whole line to the shell
127
        pos = cmd.find( self.prompt )
128
        if pos >= 0:
129
            cmd = cmd[ pos + len( self.prompt ): ]
130 d0e53ca8 Bob Lantz
        self.sendCmd( cmd )
131 82b72072 Bob Lantz
132
    # Callback ignores event
133
    # pylint: disable-msg=W0613
134 afe0ce81 Bob Lantz
    def handleInt( self, event=None ):
135
        "Handle control-c."
136
        self.node.sendInt()
137 82b72072 Bob Lantz
    # pylint: enable-msg=W0613
138 e3f6ecca Bob Lantz
139 afe0ce81 Bob Lantz
    def sendCmd( self, cmd ):
140
        "Send a command to our node."
141 82b72072 Bob Lantz
        if not self.node.waiting:
142
            self.node.sendCmd( cmd )
143 afe0ce81 Bob Lantz
144 82b72072 Bob Lantz
    # Callback ignores fds
145
    # pylint: disable-msg=W0613
146
    def handleReadable( self, fds, timeoutms=None ):
147 7ad48292 Bob Lantz
        "Handle file readable event."
148 3482b446 Bob Lantz
        data = self.node.monitor( timeoutms )
149 afe0ce81 Bob Lantz
        self.append( data )
150 d0e53ca8 Bob Lantz
        if not self.node.waiting:
151 e7ba6b9e Bob Lantz
            # Print prompt
152 d0e53ca8 Bob Lantz
            self.append( self.prompt )
153 82b72072 Bob Lantz
    # pylint: enable-msg=W0613
154 ab1fb093 Bob Lantz
155
    def waiting( self ):
156
        "Are we waiting for output?"
157
        return self.node.waiting
158
159 7ad48292 Bob Lantz
    def waitOutput( self ):
160
        "Wait for any remaining output."
161
        while self.node.waiting:
162 3482b446 Bob Lantz
            # A bit of a trade-off here...
163
            self.handleReadable( self, timeoutms=1000)
164
            self.update()
165 afe0ce81 Bob Lantz
166 7ad48292 Bob Lantz
    def clear( self ):
167
        "Clear all of our text."
168
        self.text.delete( '1.0', 'end' )
169 ab1fb093 Bob Lantz
170 82b72072 Bob Lantz
171 ab1fb093 Bob Lantz
class Graph( Frame ):
172
173
    "Graph that we can add bars to over time."
174 82b72072 Bob Lantz
175 ab1fb093 Bob Lantz
    def __init__( self, parent=None,
176
        bg = 'white',
177
        gheight=200, gwidth=500,
178
        barwidth=10,
179
        ymax=3.5,):
180 82b72072 Bob Lantz
181 ab1fb093 Bob Lantz
        Frame.__init__( self, parent )
182
183
        self.bg = bg
184
        self.gheight = gheight
185
        self.gwidth = gwidth
186
        self.barwidth = barwidth
187
        self.ymax = float( ymax )
188
        self.xpos = 0
189
190
        # Create everything
191 82b72072 Bob Lantz
        self.title, self.scale, self.graph = self.createWidgets()
192 ab1fb093 Bob Lantz
        self.updateScrollRegions()
193
        self.yview( 'moveto', '1.0' )
194 82b72072 Bob Lantz
195
    def createScale( self ):
196 ab1fb093 Bob Lantz
        "Create a and return a new canvas with scale markers."
197
        height = float( self.gheight )
198
        width = 25
199
        ymax = self.ymax
200 e3f6ecca Bob Lantz
        scale = Canvas( self, width=width, height=height,
201 82b72072 Bob Lantz
            background=self.bg )
202
        opts = { 'fill': 'red' }
203 ab1fb093 Bob Lantz
        # Draw scale line
204 82b72072 Bob Lantz
        scale.create_line( width - 1, height, width - 1, 0, **opts )
205 ab1fb093 Bob Lantz
        # Draw ticks and numbers
206
        for y in range( 0, int( ymax + 1 ) ):
207
            ypos = height * (1 - float( y ) / ymax )
208 82b72072 Bob Lantz
            scale.create_line( width, ypos, width - 10, ypos, **opts )
209
            scale.create_text( 10, ypos, text=str( y ), **opts )
210 ab1fb093 Bob Lantz
        return scale
211 82b72072 Bob Lantz
212 ab1fb093 Bob Lantz
    def updateScrollRegions( self ):
213
        "Update graph and scale scroll regions."
214
        ofs = 20
215
        height = self.gheight + ofs
216 82b72072 Bob Lantz
        self.graph.configure( scrollregion=( 0, -ofs,
217 ab1fb093 Bob Lantz
            self.xpos * self.barwidth, height ) )
218
        self.scale.configure( scrollregion=( 0, -ofs, 0, height ) )
219 82b72072 Bob Lantz
220 ab1fb093 Bob Lantz
    def yview( self, *args ):
221 82b72072 Bob Lantz
        "Scroll both scale and graph."
222
        self.graph.yview( *args )
223
        self.scale.yview( *args )
224
225 ab1fb093 Bob Lantz
    def createWidgets( self ):
226
        "Create initial widget set."
227
228
        # Objects
229 82b72072 Bob Lantz
        title = Label( self, text='Bandwidth (Gb/s)', bg=self.bg )
230 ab1fb093 Bob Lantz
        width = self.gwidth
231
        height = self.gheight
232 82b72072 Bob Lantz
        scale = self.createScale()
233 ab1fb093 Bob Lantz
        graph = Canvas( self, width=width, height=height, background=self.bg)
234
        xbar = Scrollbar( self, orient='horizontal', command=graph.xview )
235
        ybar = Scrollbar( self, orient='vertical', command=self.yview )
236
        graph.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set,
237
            scrollregion=(0, 0, width, height ) )
238
        scale.configure( yscrollcommand=ybar.set )
239 82b72072 Bob Lantz
240 ab1fb093 Bob Lantz
        # Layout
241 259d7133 Bob Lantz
        title.grid( row=0, columnspan=3, sticky='new')
242
        scale.grid( row=1, column=0, sticky='nsew' )
243
        graph.grid( row=1, column=1, sticky='nsew' )
244
        ybar.grid( row=1, column=2, sticky='ns' )
245
        xbar.grid( row=2, column=0, columnspan=2, sticky='ew' )
246 ab1fb093 Bob Lantz
        self.rowconfigure( 1, weight=1 )
247
        self.columnconfigure( 1, weight=1 )
248 82b72072 Bob Lantz
        return title, scale, graph
249
250 ab1fb093 Bob Lantz
    def addBar( self, yval ):
251
        "Add a new bar to our graph."
252
        percent = yval / self.ymax
253
        c = self.graph
254
        x0 = self.xpos * self.barwidth
255
        x1 = x0 + self.barwidth
256
        y0 = self.gheight
257
        y1 = ( 1 - percent ) * self.gheight
258
        c.create_rectangle( x0 , y0, x1, y1, fill='green' )
259
        self.xpos += 1
260
        self.updateScrollRegions()
261
        self.graph.xview( 'moveto', '1.0' )
262
263
    def clear( self ):
264
        "Clear graph contents."
265
        self.graph.delete( 'all' )
266
        self.xpos = 0
267
268
    def test( self ):
269
        "Add a bar for testing purposes."
270
        ms = 1000
271
        if self.xpos < 10:
272 e3f6ecca Bob Lantz
            self.addBar( self.xpos / 10 * self.ymax  )
273 ab1fb093 Bob Lantz
            self.after( ms, self.test )
274
275
    def setTitle( self, text ):
276
        "Set graph title"
277
        self.title.configure( text=text, font='Helvetica 9 bold' )
278
279
280 d0e53ca8 Bob Lantz
class ConsoleApp( Frame ):
281 afe0ce81 Bob Lantz
282 82b72072 Bob Lantz
    "Simple Tk consoles for Mininet."
283 e3f6ecca Bob Lantz
284 fce4d59d Bob Lantz
    menuStyle = { 'font': 'Geneva 7 bold' }
285
286
    def __init__( self, net, parent=None, width=4 ):
287 afe0ce81 Bob Lantz
        Frame.__init__( self, parent )
288 d0e53ca8 Bob Lantz
        self.top = self.winfo_toplevel()
289
        self.top.title( 'Mininet' )
290
        self.net = net
291 fce4d59d Bob Lantz
        self.menubar = self.createMenuBar()
292
        cframe = self.cframe = Frame( self )
293
        self.consoles = {}  # consoles themselves
294 82b72072 Bob Lantz
        titles = {
295
            'hosts': 'Host',
296 e7ba6b9e Bob Lantz
            'switches': 'Switch',
297
            'controllers': 'Controller'
298
        }
299
        for name in titles:
300 fce4d59d Bob Lantz
            nodes = getattr( net, name )
301 82b72072 Bob Lantz
            frame, consoles = self.createConsoles(
302 e7ba6b9e Bob Lantz
                cframe, nodes, width, titles[ name ] )
303 fce4d59d Bob Lantz
            self.consoles[ name ] = Object( frame=frame, consoles=consoles )
304
        self.selected = None
305
        self.select( 'hosts' )
306
        self.cframe.pack( expand=True, fill='both' )
307 d0e53ca8 Bob Lantz
        cleanUpScreens()
308 7ad48292 Bob Lantz
        # Close window gracefully
309
        Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
310 82b72072 Bob Lantz
311 ab1fb093 Bob Lantz
        # Initialize graph
312
        graph = Graph( cframe )
313
        self.consoles[ 'graph' ] = Object( frame=graph, consoles=[ graph ] )
314
        self.graph = graph
315
        self.graphVisible = False
316
        self.updates = 0
317
        self.hostCount = len( self.consoles[ 'hosts' ].consoles )
318
        self.bw = 0
319
320
        self.pack( expand=True, fill='both' )
321 82b72072 Bob Lantz
322
    # Update callback doesn't use console arg
323
    # pylint: disable-msg=W0613
324 ab1fb093 Bob Lantz
    def updateGraph( self, console, output ):
325
        "Update our graph."
326
        m = re.search( r'(\d+) Mbits/sec', output )
327
        if not m:
328
            return
329
        self.updates += 1
330
        self.bw += .001 * float( m.group( 1 ) )
331
        if self.updates >= self.hostCount:
332
            self.graph.addBar( self.bw )
333
            self.bw = 0
334
            self.updates = 0
335 82b72072 Bob Lantz
    # pylint: enable-msg=W0613
336 ab1fb093 Bob Lantz
337
    def setOutputHook( self, fn=None, consoles=None ):
338 82b72072 Bob Lantz
        "Register fn as output hook [on specific consoles.]"
339 ab1fb093 Bob Lantz
        if consoles is None:
340
            consoles = self.consoles[ 'hosts' ].consoles
341
        for console in consoles:
342
            console.outputHook = fn
343 82b72072 Bob Lantz
344 e7ba6b9e Bob Lantz
    def createConsoles( self, parent, nodes, width, title ):
345 afe0ce81 Bob Lantz
        "Create a grid of consoles in a frame."
346 fce4d59d Bob Lantz
        f = Frame( parent )
347 afe0ce81 Bob Lantz
        # Create consoles
348
        consoles = []
349
        index = 0
350
        for node in nodes:
351 82b72072 Bob Lantz
            console = Console( f, self.net, node, title=title )
352 afe0ce81 Bob Lantz
            consoles.append( console )
353 fce4d59d Bob Lantz
            row = index / width
354 afe0ce81 Bob Lantz
            column = index % width
355
            console.grid( row=row, column=column, sticky='nsew' )
356
            index += 1
357
            f.rowconfigure( row, weight=1 )
358
            f.columnconfigure( column, weight=1 )
359 fce4d59d Bob Lantz
        return f, consoles
360 82b72072 Bob Lantz
361
    def select( self, groupName ):
362
        "Select a group of consoles to display."
363 fce4d59d Bob Lantz
        if self.selected is not None:
364
            self.selected.frame.pack_forget()
365 82b72072 Bob Lantz
        self.selected = self.consoles[ groupName ]
366 fce4d59d Bob Lantz
        self.selected.frame.pack( expand=True, fill='both' )
367
368
    def createMenuBar( self ):
369 afe0ce81 Bob Lantz
        "Create and return a menu (really button) bar."
370
        f = Frame( self )
371
        buttons = [
372 fce4d59d Bob Lantz
            ( 'Hosts', lambda: self.select( 'hosts' ) ),
373
            ( 'Switches', lambda: self.select( 'switches' ) ),
374 82b72072 Bob Lantz
            ( 'Controllers', lambda: self.select( 'controllers' ) ),
375
            ( 'Graph', lambda: self.select( 'graph' ) ),
376 afe0ce81 Bob Lantz
            ( 'Ping', self.ping ),
377 7ad48292 Bob Lantz
            ( 'Iperf', self.iperf ),
378 afe0ce81 Bob Lantz
            ( 'Interrupt', self.stop ),
379 7ad48292 Bob Lantz
            ( 'Clear', self.clear ),
380 afe0ce81 Bob Lantz
            ( 'Quit', self.quit )
381
        ]
382
        for name, cmd in buttons:
383 fce4d59d Bob Lantz
            b = Button( f, text=name, command=cmd, **self.menuStyle )
384 afe0ce81 Bob Lantz
            b.pack( side='left' )
385
        f.pack( padx=4, pady=4, fill='x' )
386
        return f
387 82b72072 Bob Lantz
388 7ad48292 Bob Lantz
    def clear( self ):
389 ab1fb093 Bob Lantz
        "Clear selection."
390 fce4d59d Bob Lantz
        for console in self.selected.consoles:
391 7ad48292 Bob Lantz
            console.clear()
392 82b72072 Bob Lantz
393 ab1fb093 Bob Lantz
    def waiting( self, consoles=None ):
394
        "Are any of our hosts waiting for output?"
395
        if consoles is None:
396
            consoles = self.consoles[ 'hosts' ].consoles
397
        for console in consoles:
398
            if console.waiting():
399
                return True
400
        return False
401
402 afe0ce81 Bob Lantz
    def ping( self ):
403 fce4d59d Bob Lantz
        "Tell each host to ping the next one."
404
        consoles = self.consoles[ 'hosts' ].consoles
405 ab1fb093 Bob Lantz
        if self.waiting( consoles ):
406
            return
407 afe0ce81 Bob Lantz
        count = len( consoles )
408 d0e53ca8 Bob Lantz
        i = 0
409 afe0ce81 Bob Lantz
        for console in consoles:
410 d0e53ca8 Bob Lantz
            i = ( i + 1 ) % count
411 afe0ce81 Bob Lantz
            ip = consoles[ i ].node.IP()
412
            console.sendCmd( 'ping ' + ip )
413 7ad48292 Bob Lantz
414
    def iperf( self ):
415 fce4d59d Bob Lantz
        "Tell each host to iperf to the next one."
416
        consoles = self.consoles[ 'hosts' ].consoles
417 ab1fb093 Bob Lantz
        if self.waiting( consoles ):
418
            return
419 7ad48292 Bob Lantz
        count = len( consoles )
420 ab1fb093 Bob Lantz
        self.setOutputHook( self.updateGraph )
421 7ad48292 Bob Lantz
        for console in consoles:
422 e123afea Bob Lantz
            console.node.cmd( 'iperf -sD' )
423 7ad48292 Bob Lantz
        i = 0
424
        for console in consoles:
425
            i = ( i + 1 ) % count
426
            ip = consoles[ i ].node.IP()
427 e123afea Bob Lantz
            console.sendCmd( 'iperf -t 99999 -i 1 -c ' + ip )
428 7ad48292 Bob Lantz
429 3482b446 Bob Lantz
    def stop( self, wait=True ):
430 fce4d59d Bob Lantz
        "Interrupt all hosts."
431
        consoles = self.consoles[ 'hosts' ].consoles
432
        for console in consoles:
433 afe0ce81 Bob Lantz
            console.handleInt()
434 3482b446 Bob Lantz
        if wait:
435
            for console in consoles:
436
                console.waitOutput()
437 ab1fb093 Bob Lantz
        self.setOutputHook( None )
438 7ad48292 Bob Lantz
        # Shut down any iperfs that might still be running
439
        quietRun( 'killall -9 iperf' )
440
441
    def quit( self ):
442 c79882b7 Bob Lantz
        "Stop everything and quit."
443 3482b446 Bob Lantz
        self.stop( wait=False)
444 7ad48292 Bob Lantz
        Frame.quit( self )
445 afe0ce81 Bob Lantz
446 ab1fb093 Bob Lantz
447
# Make it easier to construct and assign objects
448
449 fa20913b Bob Lantz
def assign( obj, **kwargs ):
450 ab1fb093 Bob Lantz
    "Set a bunch of fields in an object."
451 73a098a4 Bob Lantz
    obj.__dict__.update( kwargs )
452 ab1fb093 Bob Lantz
453 fce4d59d Bob Lantz
class Object( object ):
454
    "Generic object you can stuff junk into."
455
    def __init__( self, **kwargs ):
456 ab1fb093 Bob Lantz
        assign( self, **kwargs )
457
458
459 afe0ce81 Bob Lantz
if __name__ == '__main__':
460
    setLogLevel( 'info' )
461 82b72072 Bob Lantz
    network = TreeNet( depth=2, fanout=4 )
462
    network.start()
463
    app = ConsoleApp( network, width=4 )
464 afe0ce81 Bob Lantz
    app.mainloop()
465 82b72072 Bob Lantz
    network.stop()