Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ ab8c4e91

History | View | Annotate | Download (15.4 KB)

1 496b5f9e Bob Lantz
"""
2
A simple command-line interface for Mininet.
3

4
The Mininet CLI provides a simple control console which
5
makes it easy to talk to nodes. For example, the command
6

7
mininet> h27 ifconfig
8

9
runs 'ifconfig' on host h27.
10

11
Having a single console rather than, for example, an xterm for each
12
node is particularly convenient for networks of any reasonable
13
size.
14

15
The CLI automatically substitutes IP addresses for node names,
16
so commands like
17

18 bcacfc05 Bob Lantz
mininet> h2 ping h3
19 496b5f9e Bob Lantz

20 bcacfc05 Bob Lantz
should work correctly and allow host h2 to ping host h3
21 496b5f9e Bob Lantz

22
Several useful commands are provided, including the ability to
23
list all nodes ('nodes'), to print out the network topology
24 64c451e0 Bob Lantz
('net') and to check connectivity ('pingall', 'pingpair')
25 496b5f9e Bob Lantz
and bandwidth ('iperf'.)
26
"""
27
28
from subprocess import call
29 114dcd56 Brandon Heller
from cmd import Cmd
30 f800e512 Bob Lantz
from os import isatty
31 259d7133 Bob Lantz
from select import poll, POLLIN
32 82b72072 Bob Lantz
import sys
33 f509ae28 Brandon Heller
import time
34 a905be22 Rich Lane
import os
35
import atexit
36 496b5f9e Bob Lantz
37 8d3c2859 Brandon Heller
from mininet.log import info, output, error
38 e3d07bc1 Bob Lantz
from mininet.term import makeTerms, runX11
39 50774e40 Bob Lantz
from mininet.util import ( quietRun, dumpNodeConnections,
40 7a3159c9 Bob Lantz
                           dumpPorts )
41 82b72072 Bob Lantz
42 114dcd56 Brandon Heller
class CLI( Cmd ):
43 496b5f9e Bob Lantz
    "Simple command-line interface to talk to nodes."
44
45 114dcd56 Brandon Heller
    prompt = 'mininet> '
46 496b5f9e Bob Lantz
47 e3a1fbb0 Bob Lantz
    def __init__( self, mininet, stdin=sys.stdin, script=None ):
48 613fac4b Bob Lantz
        """Start and run interactive or batch mode CLI
49
           mininet: Mininet network object
50
           stdin: standard input for CLI
51
           script: script to run in batch mode"""
52 496b5f9e Bob Lantz
        self.mn = mininet
53 98cb3359 Brian O'Connor
        # Local variable bindings for py command
54 9281719d Brian O'Connor
        self.locals = { 'net': mininet }
55 f800e512 Bob Lantz
        # Attempt to handle input
56
        self.stdin = stdin
57
        self.inPoller = poll()
58
        self.inPoller.register( stdin )
59 e3a1fbb0 Bob Lantz
        self.inputFile = script
60 114dcd56 Brandon Heller
        Cmd.__init__( self )
61 4e69ae83 Brandon Heller
        info( '*** Starting CLI:\n' )
62 a905be22 Rich Lane
63 e3a1fbb0 Bob Lantz
        if self.inputFile:
64
            self.do_source( self.inputFile )
65
            return
66 e0cd11ab Rich Lane
67 613fac4b Bob Lantz
        self.initReadline()
68
        self.run()
69 e0cd11ab Rich Lane
70 613fac4b Bob Lantz
    readlineInited = False
71 c5779dee Bob Lantz
72 e0cd11ab Rich Lane
    @classmethod
73 613fac4b Bob Lantz
    def initReadline( cls ):
74 e0cd11ab Rich Lane
        "Set up history if readline is available"
75
        # Only set up readline once to prevent multiplying the history file
76 613fac4b Bob Lantz
        if cls.readlineInited:
77 e0cd11ab Rich Lane
            return
78 613fac4b Bob Lantz
        cls.readlineInited = True
79 e0cd11ab Rich Lane
        try:
80 f49e2539 Bob Lantz
            from readline import read_history_file, write_history_file
81 e0cd11ab Rich Lane
        except ImportError:
82
            pass
83
        else:
84 f49e2539 Bob Lantz
            history_path = os.path.expanduser( '~/.mininet_history' )
85
            if os.path.isfile( history_path ):
86
                read_history_file( history_path )
87
            atexit.register( lambda: write_history_file( history_path ) )
88 e0cd11ab Rich Lane
89 613fac4b Bob Lantz
    def run( self ):
90
        "Run our cmdloop(), catching KeyboardInterrupt"
91
        while True:
92
            try:
93
                # Make sure no nodes are still waiting
94
                for node in self.mn.values():
95
                    while node.waiting:
96
                        info( 'stopping', node, '\n' )
97
                        node.sendInt()
98
                        node.waitOutput()
99
                if self.isatty():
100
                    quietRun( 'stty echo sane intr ^C' )
101
                self.cmdloop()
102
                break
103
            except KeyboardInterrupt:
104
                # Output a message - unless it's also interrupted
105 c5779dee Bob Lantz
                # pylint: disable=broad-except
106 613fac4b Bob Lantz
                try:
107
                    output( '\nInterrupt\n' )
108 c5779dee Bob Lantz
                except Exception:
109 613fac4b Bob Lantz
                    pass
110 c5779dee Bob Lantz
                # pylint: enable=broad-except
111 613fac4b Bob Lantz
112 bcacfc05 Bob Lantz
    def emptyline( self ):
113
        "Don't repeat last command when you hit return."
114
        pass
115 496b5f9e Bob Lantz
116 9281719d Brian O'Connor
    def getLocals( self ):
117 8e04a9f8 Brian O'Connor
        "Local variable bindings for py command"
118 9281719d Brian O'Connor
        self.locals.update( self.mn )
119
        return self.locals
120 8e04a9f8 Brian O'Connor
121 82b72072 Bob Lantz
    helpStr = (
122 caf024bc Bob Lantz
        'You may also send a command to a node using:\n'
123
        '  <node> command {args}\n'
124
        'For example:\n'
125
        '  mininet> h1 ifconfig\n'
126
        '\n'
127
        'The interpreter automatically substitutes IP addresses\n'
128
        'for node names when a node is the first arg, so commands\n'
129
        'like\n'
130
        '  mininet> h2 ping h3\n'
131
        'should work.\n'
132
        '\n'
133
        'Some character-oriented interactive commands require\n'
134
        'noecho:\n'
135
        '  mininet> noecho h2 vi foo.py\n'
136
        'However, starting up an xterm/gterm is generally better:\n'
137
        '  mininet> xterm h2\n\n'
138
    )
139
140 2a554ae3 Bob Lantz
    def do_help( self, line ):
141 114dcd56 Brandon Heller
        "Describe available CLI commands."
142 2a554ae3 Bob Lantz
        Cmd.do_help( self, line )
143
        if line is '':
144 caf024bc Bob Lantz
            output( self.helpStr )
145 496b5f9e Bob Lantz
146 14ff3ad3 Bob Lantz
    def do_nodes( self, _line ):
147 496b5f9e Bob Lantz
        "List all nodes."
148 9281719d Brian O'Connor
        nodes = ' '.join( sorted( self.mn ) )
149 cdeaca86 Brandon Heller
        output( 'available nodes are: \n%s\n' % nodes )
150 496b5f9e Bob Lantz
151 b1ec912d Bob Lantz
    def do_ports( self, _line ):
152 08643fe6 cody burkard
        "display ports and interfaces for each switch"
153
        dumpPorts( self.mn.switches )
154
155 14ff3ad3 Bob Lantz
    def do_net( self, _line ):
156 496b5f9e Bob Lantz
        "List network connections."
157 9281719d Brian O'Connor
        dumpNodeConnections( self.mn.values() )
158 496b5f9e Bob Lantz
159 2a554ae3 Bob Lantz
    def do_sh( self, line ):
160 cac98f5f cody burkard
        """Run an external shell command
161
           Usage: sh [cmd args]"""
162 061598f0 Bob Lantz
        assert self  # satisfy pylint and allow override
163 2a554ae3 Bob Lantz
        call( line, shell=True )
164 496b5f9e Bob Lantz
165 23c70f60 Bob Lantz
    # do_py() and do_px() need to catch any exception during eval()/exec()
166 061598f0 Bob Lantz
    # pylint: disable=broad-except
167 1fdcd676 Bob Lantz
168 2a554ae3 Bob Lantz
    def do_py( self, line ):
169 1fdcd676 Bob Lantz
        """Evaluate a Python expression.
170 23c70f60 Bob Lantz
           Node names may be used, e.g.: py h1.cmd('ls')"""
171 1fdcd676 Bob Lantz
        try:
172 9281719d Brian O'Connor
            result = eval( line, globals(), self.getLocals() )
173 1fdcd676 Bob Lantz
            if not result:
174
                return
175
            elif isinstance( result, str ):
176 f800e512 Bob Lantz
                output( result + '\n' )
177 1fdcd676 Bob Lantz
            else:
178 f800e512 Bob Lantz
                output( repr( result ) + '\n' )
179 1fdcd676 Bob Lantz
        except Exception, e:
180 f800e512 Bob Lantz
            output( str( e ) + '\n' )
181 1fdcd676 Bob Lantz
182 23c70f60 Bob Lantz
    # We are in fact using the exec() pseudo-function
183 061598f0 Bob Lantz
    # pylint: disable=exec-used
184 23c70f60 Bob Lantz
185
    def do_px( self, line ):
186
        """Execute a Python statement.
187
            Node names may be used, e.g.: px print h1.cmd('ls')"""
188
        try:
189 9281719d Brian O'Connor
            exec( line, globals(), self.getLocals() )
190 23c70f60 Bob Lantz
        except Exception, e:
191
            output( str( e ) + '\n' )
192
193 061598f0 Bob Lantz
    # pylint: enable=broad-except,exec-used
194 1fdcd676 Bob Lantz
195 4d1a9cdc Jon Hall
    def do_pingall( self, line ):
196 496b5f9e Bob Lantz
        "Ping between all hosts."
197 4d1a9cdc Jon Hall
        self.mn.pingAll( line )
198 496b5f9e Bob Lantz
199 14ff3ad3 Bob Lantz
    def do_pingpair( self, _line ):
200 496b5f9e Bob Lantz
        "Ping between first two hosts, useful for testing."
201
        self.mn.pingPair()
202
203 1f1d590c Brandon Heller
    def do_pingallfull( self, _line ):
204 24fe68d9 Baohua Yang
        "Ping between all hosts, returns all ping results."
205 1f1d590c Brandon Heller
        self.mn.pingAllFull()
206
207
    def do_pingpairfull( self, _line ):
208
        "Ping between first two hosts, returns all ping results."
209
        self.mn.pingPairFull()
210
211 2a554ae3 Bob Lantz
    def do_iperf( self, line ):
212 cac98f5f cody burkard
        """Simple iperf TCP test between two (optionally specified) hosts.
213
           Usage: iperf node1 node2"""
214 60a39a72 Brandon Heller
        args = line.split()
215
        if not args:
216
            self.mn.iperf()
217
        elif len(args) == 2:
218
            hosts = []
219
            err = False
220
            for arg in args:
221 8e04a9f8 Brian O'Connor
                if arg not in self.mn:
222 60a39a72 Brandon Heller
                    err = True
223
                    error( "node '%s' not in network\n" % arg )
224
                else:
225 8e04a9f8 Brian O'Connor
                    hosts.append( self.mn[ arg ] )
226 60a39a72 Brandon Heller
            if not err:
227
                self.mn.iperf( hosts )
228
        else:
229
            error( 'invalid number of args: iperf src dst\n' )
230 496b5f9e Bob Lantz
231 2a554ae3 Bob Lantz
    def do_iperfudp( self, line ):
232 cac98f5f cody burkard
        """Simple iperf UDP test between two (optionally specified) hosts.
233
           Usage: iperfudp bw node1 node2"""
234 2a554ae3 Bob Lantz
        args = line.split()
235 57aae3e1 Brandon Heller
        if not args:
236
            self.mn.iperf( l4Type='UDP' )
237
        elif len(args) == 3:
238
            udpBw = args[ 0 ]
239
            hosts = []
240
            err = False
241
            for arg in args[ 1:3 ]:
242 8e04a9f8 Brian O'Connor
                if arg not in self.mn:
243 57aae3e1 Brandon Heller
                    err = True
244
                    error( "node '%s' not in network\n" % arg )
245
                else:
246 8e04a9f8 Brian O'Connor
                    hosts.append( self.mn[ arg ] )
247 57aae3e1 Brandon Heller
            if not err:
248
                self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
249
        else:
250
            error( 'invalid number of args: iperfudp bw src dst\n' +
251
                   'bw examples: 10M\n' )
252 496b5f9e Bob Lantz
253 14ff3ad3 Bob Lantz
    def do_intfs( self, _line ):
254 496b5f9e Bob Lantz
        "List interfaces."
255 9281719d Brian O'Connor
        for node in self.mn.values():
256 cdeaca86 Brandon Heller
            output( '%s: %s\n' %
257 edf60032 Brandon Heller
                    ( node.name, ','.join( node.intfNames() ) ) )
258 496b5f9e Bob Lantz
259 14ff3ad3 Bob Lantz
    def do_dump( self, _line ):
260 496b5f9e Bob Lantz
        "Dump node info."
261 9281719d Brian O'Connor
        for node in self.mn.values():
262 8856d284 Bob Lantz
            output( '%s\n' % repr( node ) )
263 496b5f9e Bob Lantz
264 2a554ae3 Bob Lantz
    def do_link( self, line ):
265 cac98f5f cody burkard
        """Bring link(s) between two nodes up or down.
266
           Usage: link node1 node2 [up/down]"""
267 2a554ae3 Bob Lantz
        args = line.split()
268 8d3c2859 Brandon Heller
        if len(args) != 3:
269 c70aab0a Bob Lantz
            error( 'invalid number of args: link end1 end2 [up down]\n' )
270
        elif args[ 2 ] not in [ 'up', 'down' ]:
271
            error( 'invalid type: link end1 end2 [up down]\n' )
272 8d3c2859 Brandon Heller
        else:
273 c70aab0a Bob Lantz
            self.mn.configLinkStatus( *args )
274 8d3c2859 Brandon Heller
275 2a554ae3 Bob Lantz
    def do_xterm( self, line, term='xterm' ):
276 cac98f5f cody burkard
        """Spawn xterm(s) for the given node(s).
277
           Usage: xterm node1 node2 ..."""
278 2a554ae3 Bob Lantz
        args = line.split()
279 ce9cd5be Brandon Heller
        if not args:
280 f800e512 Bob Lantz
            error( 'usage: %s node1 node2 ...\n' % term )
281 ce9cd5be Brandon Heller
        else:
282
            for arg in args:
283 8e04a9f8 Brian O'Connor
                if arg not in self.mn:
284 68f97b74 Bob Lantz
                    error( "node '%s' not in network\n" % arg )
285 ce9cd5be Brandon Heller
                else:
286 8e04a9f8 Brian O'Connor
                    node = self.mn[ arg ]
287 15b482e3 Brandon Heller
                    self.mn.terms += makeTerms( [ node ], term = term )
288 ce9cd5be Brandon Heller
289 e3d07bc1 Bob Lantz
    def do_x( self, line ):
290
        """Create an X11 tunnel to the given node,
291 cac98f5f cody burkard
           optionally starting a client.
292
           Usage: x node [cmd args]"""
293 e3d07bc1 Bob Lantz
        args = line.split()
294
        if not args:
295
            error( 'usage: x node [cmd args]...\n' )
296
        else:
297
            node = self.mn[ args[ 0 ] ]
298
            cmd = args[ 1: ]
299
            self.mn.terms += runX11( node, cmd )
300
301 2a554ae3 Bob Lantz
    def do_gterm( self, line ):
302 cac98f5f cody burkard
        """Spawn gnome-terminal(s) for the given node(s).
303
           Usage: gterm node1 node2 ..."""
304 2a554ae3 Bob Lantz
        self.do_xterm( line, term='gterm' )
305 68f97b74 Bob Lantz
306 14ff3ad3 Bob Lantz
    def do_exit( self, _line ):
307 114dcd56 Brandon Heller
        "Exit"
308 061598f0 Bob Lantz
        assert self  # satisfy pylint and allow override
309 114dcd56 Brandon Heller
        return 'exited by user command'
310 496b5f9e Bob Lantz
311 2a554ae3 Bob Lantz
    def do_quit( self, line ):
312 114dcd56 Brandon Heller
        "Exit"
313 2a554ae3 Bob Lantz
        return self.do_exit( line )
314 64c451e0 Bob Lantz
315 2a554ae3 Bob Lantz
    def do_EOF( self, line ):
316 64c451e0 Bob Lantz
        "Exit"
317 f800e512 Bob Lantz
        output( '\n' )
318 2a554ae3 Bob Lantz
        return self.do_exit( line )
319 114dcd56 Brandon Heller
320 f800e512 Bob Lantz
    def isatty( self ):
321
        "Is our standard input a tty?"
322
        return isatty( self.stdin.fileno() )
323 82b72072 Bob Lantz
324 f800e512 Bob Lantz
    def do_noecho( self, line ):
325 cac98f5f cody burkard
        """Run an interactive command with echoing turned off.
326
           Usage: noecho [cmd args]"""
327 f800e512 Bob Lantz
        if self.isatty():
328
            quietRun( 'stty -echo' )
329
        self.default( line )
330 9d0dbe48 Bob Lantz
        if self.isatty():
331
            quietRun( 'stty echo' )
332
333
    def do_source( self, line ):
334 cac98f5f cody burkard
        """Read commands from an input file.
335
           Usage: source <file>"""
336 9d0dbe48 Bob Lantz
        args = line.split()
337
        if len(args) != 1:
338
            error( 'usage: source <file>\n' )
339
            return
340
        try:
341
            self.inputFile = open( args[ 0 ] )
342
            while True:
343
                line = self.inputFile.readline()
344
                if len( line ) > 0:
345
                    self.onecmd( line )
346
                else:
347
                    break
348
        except IOError:
349 dde9c91d Bob Lantz
            error( 'error reading file %s\n' % args[ 0 ] )
350 3780d9cd Baohua Yang
        self.inputFile.close()
351 9d0dbe48 Bob Lantz
        self.inputFile = None
352 f800e512 Bob Lantz
353 9da63d4e Brandon Heller
    def do_dpctl( self, line ):
354 cac98f5f cody burkard
        """Run dpctl (or ovs-ofctl) command on all switches.
355
           Usage: dpctl command [arg1] [arg2] ..."""
356 9da63d4e Brandon Heller
        args = line.split()
357 8856d284 Bob Lantz
        if len(args) < 1:
358 9da63d4e Brandon Heller
            error( 'usage: dpctl command [arg1] [arg2] ...\n' )
359
            return
360
        for sw in self.mn.switches:
361
            output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
362 8856d284 Bob Lantz
            output( sw.dpctl( *args ) )
363 9da63d4e Brandon Heller
364 f509ae28 Brandon Heller
    def do_time( self, line ):
365
        "Measure time taken for any command in Mininet."
366
        start = time.time()
367
        self.onecmd(line)
368
        elapsed = time.time() - start
369
        self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
370
371 b1ec912d Bob Lantz
    def do_links( self, _line ):
372 c265deed Bob Lantz
        "Report on links"
373
        for link in self.mn.links:
374
            print link, link.status()
375
376 b57e5d93 Brian O'Connor
    def do_switch( self, line ):
377
        "Starts or stops a switch"
378
        args = line.split()
379
        if len(args) != 2:
380 b1ec912d Bob Lantz
            error( 'invalid number of args: switch <switch name>'
381
                   '{start, stop}\n' )
382 b57e5d93 Brian O'Connor
            return
383
        sw = args[ 0 ]
384
        command = args[ 1 ]
385 7a3159c9 Bob Lantz
        if sw not in self.mn or self.mn.get( sw ) not in self.mn.switches:
386 b57e5d93 Brian O'Connor
            error( 'invalid switch: %s\n' % args[ 1 ] )
387
        else:
388
            sw = args[ 0 ]
389
            command = args[ 1 ]
390
            if command == 'start':
391
                self.mn.get( sw ).start( self.mn.controllers )
392
            elif command == 'stop':
393 23dfbe4a Brian O'Connor
                self.mn.get( sw ).stop( deleteIntfs=False )
394 b57e5d93 Brian O'Connor
            else:
395 7a3159c9 Bob Lantz
                error( 'invalid command: '
396
                       'switch <switch name> {start, stop}\n' )
397 b57e5d93 Brian O'Connor
398 114dcd56 Brandon Heller
    def default( self, line ):
399
        """Called on an input line when the command prefix is not recognized.
400
        Overridden to run shell commands when a node is the first CLI argument.
401
        Past the first CLI argument, node names are automatically replaced with
402 64c451e0 Bob Lantz
        corresponding IP addrs."""
403 31b43002 Bob Lantz
404 114dcd56 Brandon Heller
        first, args, line = self.parseline( line )
405
406 8e04a9f8 Brian O'Connor
        if first in self.mn:
407 e9a835ac Brian O'Connor
            if not args:
408
                print "*** Enter a command for node: %s <cmd>" % first
409
                return
410 8e04a9f8 Brian O'Connor
            node = self.mn[ first ]
411 e9a835ac Brian O'Connor
            rest = args.split( ' ' )
412 114dcd56 Brandon Heller
            # Substitute IP addresses for node names in command
413 c7e86f93 Brian O'Connor
            # If updateIP() returns None, then use node name
414
            rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg
415 8e04a9f8 Brian O'Connor
                     if arg in self.mn else arg
416 edf60032 Brandon Heller
                     for arg in rest ]
417 114dcd56 Brandon Heller
            rest = ' '.join( rest )
418
            # Run cmd on node:
419 549f1ebc Bob Lantz
            node.sendCmd( rest )
420 82b72072 Bob Lantz
            self.waitForNode( node )
421 114dcd56 Brandon Heller
        else:
422 e9a835ac Brian O'Connor
            error( '*** Unknown command: %s\n' % line )
423 114dcd56 Brandon Heller
424 82b72072 Bob Lantz
    def waitForNode( self, node ):
425 549f1ebc Bob Lantz
        "Wait for a node to finish, and print its output."
426 f800e512 Bob Lantz
        # Pollers
427
        nodePoller = poll()
428
        nodePoller.register( node.stdout )
429
        bothPoller = poll()
430 cece39e4 Bob Lantz
        bothPoller.register( self.stdin, POLLIN )
431
        bothPoller.register( node.stdout, POLLIN )
432 f800e512 Bob Lantz
        if self.isatty():
433
            # Buffer by character, so that interactive
434
            # commands sort of work
435
            quietRun( 'stty -icanon min 1' )
436
        while True:
437
            try:
438
                bothPoller.poll()
439 5c24e186 Bob Lantz
                # XXX BL: this doesn't quite do what we want.
440
                if False and self.inputFile:
441 9d0dbe48 Bob Lantz
                    key = self.inputFile.read( 1 )
442
                    if key is not '':
443 549f1ebc Bob Lantz
                        node.write( key )
444 9d0dbe48 Bob Lantz
                    else:
445
                        self.inputFile = None
446 82b72072 Bob Lantz
                if isReadable( self.inPoller ):
447 f800e512 Bob Lantz
                    key = self.stdin.read( 1 )
448
                    node.write( key )
449 82b72072 Bob Lantz
                if isReadable( nodePoller ):
450 f800e512 Bob Lantz
                    data = node.monitor()
451 e555f83c Bob Lantz
                    output( data )
452 f800e512 Bob Lantz
                if not node.waiting:
453
                    break
454
            except KeyboardInterrupt:
455 549f1ebc Bob Lantz
                # There is an at least one race condition here, since
456
                # it's possible to interrupt ourselves after we've
457
                # read data but before it has been printed.
458 f800e512 Bob Lantz
                node.sendInt()
459 e91b2815 Bob Lantz
460 c5e8f09b cody burkard
    def precmd( self, line ):
461
        "allow for comments in the cli"
462
        if '#' in line:
463
            line = line.split( '#' )[ 0 ]
464
        return line
465
466 549f1ebc Bob Lantz
467 82b72072 Bob Lantz
# Helper functions
468
469
def isReadable( poller ):
470
    "Check whether a Poll object has a readable fd."
471
    for fdmask in poller.poll( 0 ):
472
        mask = fdmask[ 1 ]
473
        if mask & POLLIN:
474
            return True