Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ 92a28881

History | View | Annotate | Download (12.9 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

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

    
44
    prompt = 'mininet> '
45

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

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

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

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

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

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

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

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

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

    
130
    def do_net( self, _line ):
131
        "List network connections."
132
        dumpNodeConnections( self.mn.values() )
133

    
134
    def do_sh( self, line ):
135
        "Run an external shell command"
136
        call( line, shell=True )
137

    
138
    # do_py() and do_px() need to catch any exception during eval()/exec()
139
    # pylint: disable-msg=W0703
140

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

    
155
    # We are in fact using the exec() pseudo-function
156
    # pylint: disable-msg=W0122
157

    
158
    def do_px( self, line ):
159
        """Execute a Python statement.
160
            Node names may be used, e.g.: px print h1.cmd('ls')"""
161
        try:
162
            exec( line, globals(), self.getLocals() )
163
        except Exception, e:
164
            output( str( e ) + '\n' )
165

    
166
    # pylint: enable-msg=W0703,W0122
167

    
168
    def do_pingall( self, _line ):
169
        "Ping between all hosts."
170
        self.mn.pingAll()
171

    
172
    def do_pingpair( self, _line ):
173
        "Ping between first two hosts, useful for testing."
174
        self.mn.pingPair()
175

    
176
    def do_pingallfull( self, _line ):
177
        "Ping between all hosts, returns all ping results."
178
        self.mn.pingAllFull()
179

    
180
    def do_pingpairfull( self, _line ):
181
        "Ping between first two hosts, returns all ping results."
182
        self.mn.pingPairFull()
183

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

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

    
224
    def do_intfs( self, _line ):
225
        "List interfaces."
226
        for node in self.mn.values():
227
            output( '%s: %s\n' %
228
                    ( node.name, ','.join( node.intfNames() ) ) )
229

    
230
    def do_dump( self, _line ):
231
        "Dump node info."
232
        for node in self.mn.values():
233
            output( '%s\n' % repr( node ) )
234

    
235
    def do_link( self, line ):
236
        "Bring link(s) between two nodes up or down."
237
        args = line.split()
238
        if len(args) != 3:
239
            error( 'invalid number of args: link end1 end2 [up down]\n' )
240
        elif args[ 2 ] not in [ 'up', 'down' ]:
241
            error( 'invalid type: link end1 end2 [up down]\n' )
242
        else:
243
            self.mn.configLinkStatus( *args )
244

    
245
    def do_xterm( self, line, term='xterm' ):
246
        "Spawn xterm(s) for the given node(s)."
247
        args = line.split()
248
        if not args:
249
            error( 'usage: %s node1 node2 ...\n' % term )
250
        else:
251
            for arg in args:
252
                if arg not in self.mn:
253
                    error( "node '%s' not in network\n" % arg )
254
                else:
255
                    node = self.mn[ arg ]
256
                    self.mn.terms += makeTerms( [ node ], term = term )
257

    
258
    def do_x( self, line ):
259
        """Create an X11 tunnel to the given node,
260
           optionally starting a client."""
261
        args = line.split()
262
        if not args:
263
            error( 'usage: x node [cmd args]...\n' )
264
        else:
265
            node = self.mn[ args[ 0 ] ]
266
            cmd = args[ 1: ]
267
            self.mn.terms += runX11( node, cmd )
268

    
269
    def do_gterm( self, line ):
270
        "Spawn gnome-terminal(s) for the given node(s)."
271
        self.do_xterm( line, term='gterm' )
272

    
273
    def do_exit( self, _line ):
274
        "Exit"
275
        return 'exited by user command'
276

    
277
    def do_quit( self, line ):
278
        "Exit"
279
        return self.do_exit( line )
280

    
281
    def do_EOF( self, line ):
282
        "Exit"
283
        output( '\n' )
284
        return self.do_exit( line )
285

    
286
    def isatty( self ):
287
        "Is our standard input a tty?"
288
        return isatty( self.stdin.fileno() )
289

    
290
    def do_noecho( self, line ):
291
        "Run an interactive command with echoing turned off."
292
        if self.isatty():
293
            quietRun( 'stty -echo' )
294
        self.default( line )
295
        if self.isatty():
296
            quietRun( 'stty echo' )
297

    
298
    def do_source( self, line ):
299
        "Read commands from an input file."
300
        args = line.split()
301
        if len(args) != 1:
302
            error( 'usage: source <file>\n' )
303
            return
304
        try:
305
            self.inputFile = open( args[ 0 ] )
306
            while True:
307
                line = self.inputFile.readline()
308
                if len( line ) > 0:
309
                    self.onecmd( line )
310
                else:
311
                    break
312
        except IOError:
313
            error( 'error reading file %s\n' % args[ 0 ] )
314
        self.inputFile.close()
315
        self.inputFile = None
316

    
317
    def do_dpctl( self, line ):
318
        "Run dpctl (or ovs-ofctl) command on all switches."
319
        args = line.split()
320
        if len(args) < 1:
321
            error( 'usage: dpctl command [arg1] [arg2] ...\n' )
322
            return
323
        for sw in self.mn.switches:
324
            output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
325
            output( sw.dpctl( *args ) )
326

    
327
    def do_time( self, line ):
328
        "Measure time taken for any command in Mininet."
329
        start = time.time()
330
        self.onecmd(line)
331
        elapsed = time.time() - start
332
        self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
333

    
334
    def default( self, line ):
335
        """Called on an input line when the command prefix is not recognized.
336
        Overridden to run shell commands when a node is the first CLI argument.
337
        Past the first CLI argument, node names are automatically replaced with
338
        corresponding IP addrs."""
339

    
340
        first, args, line = self.parseline( line )
341

    
342
        if first in self.mn:
343
            if not args:
344
                print "*** Enter a command for node: %s <cmd>" % first
345
                return
346
            node = self.mn[ first ]
347
            rest = args.split( ' ' )
348
            # Substitute IP addresses for node names in command
349
            # If updateIP() returns None, then use node name
350
            rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg
351
                     if arg in self.mn else arg
352
                     for arg in rest ]
353
            rest = ' '.join( rest )
354
            # Run cmd on node:
355
            builtin = isShellBuiltin( first )
356
            node.sendCmd( rest, printPid=( not builtin ) )
357
            self.waitForNode( node )
358
        else:
359
            error( '*** Unknown command: %s\n' % line )
360

    
361
    # pylint: enable-msg=R0201
362

    
363
    def waitForNode( self, node ):
364
        "Wait for a node to finish, and  print its output."
365
        # Pollers
366
        nodePoller = poll()
367
        nodePoller.register( node.stdout )
368
        bothPoller = poll()
369
        bothPoller.register( self.stdin, POLLIN )
370
        bothPoller.register( node.stdout, POLLIN )
371
        if self.isatty():
372
            # Buffer by character, so that interactive
373
            # commands sort of work
374
            quietRun( 'stty -icanon min 1' )
375
        while True:
376
            try:
377
                bothPoller.poll()
378
                # XXX BL: this doesn't quite do what we want.
379
                if False and self.inputFile:
380
                    key = self.inputFile.read( 1 )
381
                    if key is not '':
382
                        node.write(key)
383
                    else:
384
                        self.inputFile = None
385
                if isReadable( self.inPoller ):
386
                    key = self.stdin.read( 1 )
387
                    node.write( key )
388
                if isReadable( nodePoller ):
389
                    data = node.monitor()
390
                    output( data )
391
                if not node.waiting:
392
                    break
393
            except KeyboardInterrupt:
394
                node.sendInt()
395

    
396
# Helper functions
397

    
398
def isReadable( poller ):
399
    "Check whether a Poll object has a readable fd."
400
    for fdmask in poller.poll( 0 ):
401
        mask = fdmask[ 1 ]
402
        if mask & POLLIN:
403
            return True