Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ 08643fe6

History | View | Annotate | Download (13.2 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.monitor()
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
        call( line, shell=True )
142

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
274
    def do_gterm( self, line ):
275
        "Spawn gnome-terminal(s) for the given node(s)."
276
        self.do_xterm( line, term='gterm' )
277

    
278
    def do_exit( self, _line ):
279
        "Exit"
280
        return 'exited by user command'
281

    
282
    def do_quit( self, line ):
283
        "Exit"
284
        return self.do_exit( line )
285

    
286
    def do_EOF( self, line ):
287
        "Exit"
288
        output( '\n' )
289
        return self.do_exit( line )
290

    
291
    def isatty( self ):
292
        "Is our standard input a tty?"
293
        return isatty( self.stdin.fileno() )
294

    
295
    def do_noecho( self, line ):
296
        "Run an interactive command with echoing turned off."
297
        if self.isatty():
298
            quietRun( 'stty -echo' )
299
        self.default( line )
300
        if self.isatty():
301
            quietRun( 'stty echo' )
302

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

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

    
332
    def do_time( self, line ):
333
        "Measure time taken for any command in Mininet."
334
        start = time.time()
335
        self.onecmd(line)
336
        elapsed = time.time() - start
337
        self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
338

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

    
345
        first, args, line = self.parseline( line )
346

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

    
365
    # pylint: enable-msg=R0201
366

    
367
    def waitForNode( self, node ):
368
        "Wait for a node to finish, and print its output."
369
        # Pollers
370
        nodePoller = poll()
371
        nodePoller.register( node.stdout )
372
        bothPoller = poll()
373
        bothPoller.register( self.stdin, POLLIN )
374
        bothPoller.register( node.stdout, POLLIN )
375
        if self.isatty():
376
            # Buffer by character, so that interactive
377
            # commands sort of work
378
            quietRun( 'stty -icanon min 1' )
379
        while True:
380
            try:
381
                bothPoller.poll()
382
                # XXX BL: this doesn't quite do what we want.
383
                if False and self.inputFile:
384
                    key = self.inputFile.read( 1 )
385
                    if key is not '':
386
                        node.write( key )
387
                    else:
388
                        self.inputFile = None
389
                if isReadable( self.inPoller ):
390
                    key = self.stdin.read( 1 )
391
                    node.write( key )
392
                if isReadable( nodePoller ):
393
                    data = node.monitor()
394
                    output( data )
395
                if not node.waiting:
396
                    break
397
            except KeyboardInterrupt:
398
                # There is an at least one race condition here, since
399
                # it's possible to interrupt ourselves after we've
400
                # read data but before it has been printed.
401
                node.sendInt()
402

    
403

    
404
# Helper functions
405

    
406
def isReadable( poller ):
407
    "Check whether a Poll object has a readable fd."
408
    for fdmask in poller.poll( 0 ):
409
        mask = fdmask[ 1 ]
410
        if mask & POLLIN:
411
            return True