Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ b57e5d93

History | View | Annotate | Download (14.6 KB)

1
"""
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
mininet> h2 ping h3
19

20
should work correctly and allow host h2 to ping host h3
21

22
Several useful commands are provided, including the ability to
23
list all nodes ('nodes'), to print out the network topology
24
('net') and to check connectivity ('pingall', 'pingpair')
25
and bandwidth ('iperf'.)
26
"""
27

    
28
from subprocess import call
29
from cmd import Cmd
30
from os import isatty
31
from select import poll, POLLIN
32
import sys
33
import time
34
import os
35
import atexit
36

    
37
from mininet.log import info, output, error
38
from mininet.term import makeTerms, runX11
39
from mininet.util import ( quietRun, isShellBuiltin, dumpNodeConnections,
40
                         dumpPorts )
41

    
42
class CLI( Cmd ):
43
    "Simple command-line interface to talk to nodes."
44

    
45
    prompt = 'mininet> '
46

    
47
    def __init__( self, mininet, stdin=sys.stdin, script=None ):
48
        self.mn = mininet
49
        # Local variable bindings for py command
50
        self.locals = { 'net': mininet }
51
        # Attempt to handle input
52
        self.stdin = stdin
53
        self.inPoller = poll()
54
        self.inPoller.register( stdin )
55
        self.inputFile = script
56
        Cmd.__init__( self )
57
        info( '*** Starting CLI:\n' )
58

    
59
        # Set up history if readline is available
60
        try:
61
            import readline
62
        except ImportError:
63
            pass
64
        else:
65
            history_path = os.path.expanduser('~/.mininet_history')
66
            if os.path.isfile(history_path):
67
                readline.read_history_file(history_path)
68
            atexit.register(lambda: readline.write_history_file(history_path))
69

    
70
        if self.inputFile:
71
            self.do_source( self.inputFile )
72
            return
73
        while True:
74
            try:
75
                # Make sure no nodes are still waiting
76
                for node in self.mn.values():
77
                    while node.waiting:
78
                        node.sendInt()
79
                        node.waitOutput()
80
                if self.isatty():
81
                    quietRun( 'stty echo sane intr "^C"' )
82
                self.cmdloop()
83
                break
84
            except KeyboardInterrupt:
85
                output( '\nInterrupt\n' )
86

    
87
    def emptyline( self ):
88
        "Don't repeat last command when you hit return."
89
        pass
90

    
91
    def getLocals( self ):
92
        "Local variable bindings for py command"
93
        self.locals.update( self.mn )
94
        return self.locals
95

    
96
    # Disable pylint "Unused argument: 'arg's'" messages, as well as
97
    # "method could be a function" warning, since each CLI function
98
    # must have the same interface
99
    # pylint: disable-msg=R0201
100

    
101
    helpStr = (
102
        'You may also send a command to a node using:\n'
103
        '  <node> command {args}\n'
104
        'For example:\n'
105
        '  mininet> h1 ifconfig\n'
106
        '\n'
107
        'The interpreter automatically substitutes IP addresses\n'
108
        'for node names when a node is the first arg, so commands\n'
109
        'like\n'
110
        '  mininet> h2 ping h3\n'
111
        'should work.\n'
112
        '\n'
113
        'Some character-oriented interactive commands require\n'
114
        'noecho:\n'
115
        '  mininet> noecho h2 vi foo.py\n'
116
        'However, starting up an xterm/gterm is generally better:\n'
117
        '  mininet> xterm h2\n\n'
118
    )
119

    
120
    def do_help( self, line ):
121
        "Describe available CLI commands."
122
        Cmd.do_help( self, line )
123
        if line is '':
124
            output( self.helpStr )
125

    
126
    def do_nodes( self, _line ):
127
        "List all nodes."
128
        nodes = ' '.join( sorted( self.mn ) )
129
        output( 'available nodes are: \n%s\n' % nodes )
130

    
131
    def do_ports( self, line ):
132
        "display ports and interfaces for each switch"
133
        dumpPorts( self.mn.switches )
134

    
135
    def do_net( self, _line ):
136
        "List network connections."
137
        dumpNodeConnections( self.mn.values() )
138

    
139
    def do_sh( self, line ):
140
        """Run an external shell command
141
           Usage: sh [cmd args]"""
142
        call( line, shell=True )
143

    
144
    # do_py() and do_px() need to catch any exception during eval()/exec()
145
    # pylint: disable-msg=W0703
146

    
147
    def do_py( self, line ):
148
        """Evaluate a Python expression.
149
           Node names may be used, e.g.: py h1.cmd('ls')"""
150
        try:
151
            result = eval( line, globals(), self.getLocals() )
152
            if not result:
153
                return
154
            elif isinstance( result, str ):
155
                output( result + '\n' )
156
            else:
157
                output( repr( result ) + '\n' )
158
        except Exception, e:
159
            output( str( e ) + '\n' )
160

    
161
    # We are in fact using the exec() pseudo-function
162
    # pylint: disable-msg=W0122
163

    
164
    def do_px( self, line ):
165
        """Execute a Python statement.
166
            Node names may be used, e.g.: px print h1.cmd('ls')"""
167
        try:
168
            exec( line, globals(), self.getLocals() )
169
        except Exception, e:
170
            output( str( e ) + '\n' )
171

    
172
    # pylint: enable-msg=W0703,W0122
173

    
174
    def do_pingall( self, line ):
175
        "Ping between all hosts."
176
        self.mn.pingAll( line )
177

    
178
    def do_pingpair( self, _line ):
179
        "Ping between first two hosts, useful for testing."
180
        self.mn.pingPair()
181

    
182
    def do_pingallfull( self, _line ):
183
        "Ping between all hosts, returns all ping results."
184
        self.mn.pingAllFull()
185

    
186
    def do_pingpairfull( self, _line ):
187
        "Ping between first two hosts, returns all ping results."
188
        self.mn.pingPairFull()
189

    
190
    def do_iperf( self, line ):
191
        """Simple iperf TCP test between two (optionally specified) hosts.
192
           Usage: iperf node1 node2"""
193
        args = line.split()
194
        if not args:
195
            self.mn.iperf()
196
        elif len(args) == 2:
197
            hosts = []
198
            err = False
199
            for arg in args:
200
                if arg not in self.mn:
201
                    err = True
202
                    error( "node '%s' not in network\n" % arg )
203
                else:
204
                    hosts.append( self.mn[ arg ] )
205
            if not err:
206
                self.mn.iperf( hosts )
207
        else:
208
            error( 'invalid number of args: iperf src dst\n' )
209

    
210
    def do_iperfudp( self, line ):
211
        """Simple iperf UDP test between two (optionally specified) hosts.
212
           Usage: iperfudp bw node1 node2"""
213
        args = line.split()
214
        if not args:
215
            self.mn.iperf( l4Type='UDP' )
216
        elif len(args) == 3:
217
            udpBw = args[ 0 ]
218
            hosts = []
219
            err = False
220
            for arg in args[ 1:3 ]:
221
                if arg not in self.mn:
222
                    err = True
223
                    error( "node '%s' not in network\n" % arg )
224
                else:
225
                    hosts.append( self.mn[ arg ] )
226
            if not err:
227
                self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
228
        else:
229
            error( 'invalid number of args: iperfudp bw src dst\n' +
230
                   'bw examples: 10M\n' )
231

    
232
    def do_intfs( self, _line ):
233
        "List interfaces."
234
        for node in self.mn.values():
235
            output( '%s: %s\n' %
236
                    ( node.name, ','.join( node.intfNames() ) ) )
237

    
238
    def do_dump( self, _line ):
239
        "Dump node info."
240
        for node in self.mn.values():
241
            output( '%s\n' % repr( node ) )
242

    
243
    def do_link( self, line ):
244
        """Bring link(s) between two nodes up or down.
245
           Usage: link node1 node2 [up/down]"""
246
        args = line.split()
247
        if len(args) != 3:
248
            error( 'invalid number of args: link end1 end2 [up down]\n' )
249
        elif args[ 2 ] not in [ 'up', 'down' ]:
250
            error( 'invalid type: link end1 end2 [up down]\n' )
251
        else:
252
            self.mn.configLinkStatus( *args )
253

    
254
    def do_xterm( self, line, term='xterm' ):
255
        """Spawn xterm(s) for the given node(s).
256
           Usage: xterm node1 node2 ..."""
257
        args = line.split()
258
        if not args:
259
            error( 'usage: %s node1 node2 ...\n' % term )
260
        else:
261
            for arg in args:
262
                if arg not in self.mn:
263
                    error( "node '%s' not in network\n" % arg )
264
                else:
265
                    node = self.mn[ arg ]
266
                    self.mn.terms += makeTerms( [ node ], term = term )
267

    
268
    def do_x( self, line ):
269
        """Create an X11 tunnel to the given node,
270
           optionally starting a client.
271
           Usage: x node [cmd args]"""
272
        args = line.split()
273
        if not args:
274
            error( 'usage: x node [cmd args]...\n' )
275
        else:
276
            node = self.mn[ args[ 0 ] ]
277
            cmd = args[ 1: ]
278
            self.mn.terms += runX11( node, cmd )
279

    
280
    def do_gterm( self, line ):
281
        """Spawn gnome-terminal(s) for the given node(s).
282
           Usage: gterm node1 node2 ..."""
283
        self.do_xterm( line, term='gterm' )
284

    
285
    def do_exit( self, _line ):
286
        "Exit"
287
        return 'exited by user command'
288

    
289
    def do_quit( self, line ):
290
        "Exit"
291
        return self.do_exit( line )
292

    
293
    def do_EOF( self, line ):
294
        "Exit"
295
        output( '\n' )
296
        return self.do_exit( line )
297

    
298
    def isatty( self ):
299
        "Is our standard input a tty?"
300
        return isatty( self.stdin.fileno() )
301

    
302
    def do_noecho( self, line ):
303
        """Run an interactive command with echoing turned off.
304
           Usage: noecho [cmd args]"""
305
        if self.isatty():
306
            quietRun( 'stty -echo' )
307
        self.default( line )
308
        if self.isatty():
309
            quietRun( 'stty echo' )
310

    
311
    def do_source( self, line ):
312
        """Read commands from an input file.
313
           Usage: source <file>"""
314
        args = line.split()
315
        if len(args) != 1:
316
            error( 'usage: source <file>\n' )
317
            return
318
        try:
319
            self.inputFile = open( args[ 0 ] )
320
            while True:
321
                line = self.inputFile.readline()
322
                if len( line ) > 0:
323
                    self.onecmd( line )
324
                else:
325
                    break
326
        except IOError:
327
            error( 'error reading file %s\n' % args[ 0 ] )
328
        self.inputFile.close()
329
        self.inputFile = None
330

    
331
    def do_dpctl( self, line ):
332
        """Run dpctl (or ovs-ofctl) command on all switches.
333
           Usage: dpctl command [arg1] [arg2] ..."""
334
        args = line.split()
335
        if len(args) < 1:
336
            error( 'usage: dpctl command [arg1] [arg2] ...\n' )
337
            return
338
        for sw in self.mn.switches:
339
            output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
340
            output( sw.dpctl( *args ) )
341

    
342
    def do_time( self, line ):
343
        "Measure time taken for any command in Mininet."
344
        start = time.time()
345
        self.onecmd(line)
346
        elapsed = time.time() - start
347
        self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
348

    
349
    def do_links( self, line ):
350
        "Report on links"
351
        for link in self.mn.links:
352
            print link, link.status()
353

    
354
    def do_switch( self, line ):
355
        "Starts or stops a switch"
356
        args = line.split()
357
        if len(args) != 2:
358
            error( 'invalid number of args: switch <switch name> {start, stop}\n' )
359
            return
360
        sw = args[ 0 ]
361
        command = args[ 1 ]
362
        if sw not in self.mn or self.mn.get( sw ) not in self.mn.switches :
363
            error( 'invalid switch: %s\n' % args[ 1 ] )
364
        else:
365
            sw = args[ 0 ]
366
            command = args[ 1 ]
367
            if command == 'start':
368
                self.mn.get( sw ).start( self.mn.controllers )
369
            elif command == 'stop':
370
                self.mn.get( sw ).stop( False )
371
            else:
372
                error( 'invalid command: switch <switch name> {start, stop}\n' )
373

    
374
    def default( self, line ):
375
        """Called on an input line when the command prefix is not recognized.
376
        Overridden to run shell commands when a node is the first CLI argument.
377
        Past the first CLI argument, node names are automatically replaced with
378
        corresponding IP addrs."""
379

    
380
        first, args, line = self.parseline( line )
381

    
382
        if first in self.mn:
383
            if not args:
384
                print "*** Enter a command for node: %s <cmd>" % first
385
                return
386
            node = self.mn[ first ]
387
            rest = args.split( ' ' )
388
            # Substitute IP addresses for node names in command
389
            # If updateIP() returns None, then use node name
390
            rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg
391
                     if arg in self.mn else arg
392
                     for arg in rest ]
393
            rest = ' '.join( rest )
394
            # Run cmd on node:
395
            node.sendCmd( rest )
396
            self.waitForNode( node )
397
        else:
398
            error( '*** Unknown command: %s\n' % line )
399

    
400
    # pylint: enable-msg=R0201
401

    
402
    def waitForNode( self, node ):
403
        "Wait for a node to finish, and print its output."
404
        # Pollers
405
        nodePoller = poll()
406
        nodePoller.register( node.stdout )
407
        bothPoller = poll()
408
        bothPoller.register( self.stdin, POLLIN )
409
        bothPoller.register( node.stdout, POLLIN )
410
        if self.isatty():
411
            # Buffer by character, so that interactive
412
            # commands sort of work
413
            quietRun( 'stty -icanon min 1' )
414
        while True:
415
            try:
416
                bothPoller.poll()
417
                # XXX BL: this doesn't quite do what we want.
418
                if False and self.inputFile:
419
                    key = self.inputFile.read( 1 )
420
                    if key is not '':
421
                        node.write( key )
422
                    else:
423
                        self.inputFile = None
424
                if isReadable( self.inPoller ):
425
                    key = self.stdin.read( 1 )
426
                    node.write( key )
427
                if isReadable( nodePoller ):
428
                    data = node.monitor()
429
                    output( data )
430
                if not node.waiting:
431
                    break
432
            except KeyboardInterrupt:
433
                # There is an at least one race condition here, since
434
                # it's possible to interrupt ourselves after we've
435
                # read data but before it has been printed.
436
                node.sendInt()
437

    
438
    def precmd( self, line ):
439
        "allow for comments in the cli"
440
        if '#' in line:
441
            line = line.split( '#' )[ 0 ]
442
        return line
443

    
444

    
445
# Helper functions
446

    
447
def isReadable( poller ):
448
    "Check whether a Poll object has a readable fd."
449
    for fdmask in poller.poll( 0 ):
450
        mask = fdmask[ 1 ]
451
        if mask & POLLIN:
452
            return True