Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ 98cb3359

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

    
35
from mininet.log import info, output, error
36
from mininet.term import makeTerms, runX11
37
from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
38

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

    
42
    prompt = 'mininet> '
43

    
44
    def __init__( self, mininet, stdin=sys.stdin, script=None ):
45
        self.mn = mininet
46
        # Local variable bindings for py command
47
        self.locals = { 'net': mininet }
48
        # Attempt to handle input
49
        self.stdin = stdin
50
        self.inPoller = poll()
51
        self.inPoller.register( stdin )
52
        self.inputFile = script
53
        Cmd.__init__( self )
54
        info( '*** Starting CLI:\n' )
55
        if self.inputFile:
56
            self.do_source( self.inputFile )
57
            return
58
        while True:
59
            try:
60
                # Make sure no nodes are still waiting
61
                for node in self.mn.values():
62
                    while node.waiting:
63
                        node.sendInt()
64
                        node.monitor()
65
                if self.isatty():
66
                    quietRun( 'stty sane' )
67
                self.cmdloop()
68
                break
69
            except KeyboardInterrupt:
70
                output( '\nInterrupt\n' )
71

    
72
    def emptyline( self ):
73
        "Don't repeat last command when you hit return."
74
        pass
75

    
76
    def getLocals( self ):
77
        "Local variable bindings for py command"
78
        self.locals.update( self.mn )
79
        return self.locals
80

    
81
    # Disable pylint "Unused argument: 'arg's'" messages, as well as
82
    # "method could be a function" warning, since each CLI function
83
    # must have the same interface
84
    # pylint: disable-msg=R0201
85

    
86
    helpStr = (
87
        'You may also send a command to a node using:\n'
88
        '  <node> command {args}\n'
89
        'For example:\n'
90
        '  mininet> h1 ifconfig\n'
91
        '\n'
92
        'The interpreter automatically substitutes IP addresses\n'
93
        'for node names when a node is the first arg, so commands\n'
94
        'like\n'
95
        '  mininet> h2 ping h3\n'
96
        'should work.\n'
97
        '\n'
98
        'Some character-oriented interactive commands require\n'
99
        'noecho:\n'
100
        '  mininet> noecho h2 vi foo.py\n'
101
        'However, starting up an xterm/gterm is generally better:\n'
102
        '  mininet> xterm h2\n\n'
103
    )
104

    
105
    def do_help( self, line ):
106
        "Describe available CLI commands."
107
        Cmd.do_help( self, line )
108
        if line is '':
109
            output( self.helpStr )
110

    
111
    def do_nodes( self, _line ):
112
        "List all nodes."
113
        nodes = ' '.join( sorted( self.mn ) )
114
        output( 'available nodes are: \n%s\n' % nodes )
115

    
116
    def do_net( self, _line ):
117
        "List network connections."
118
        dumpNodeConnections( self.mn.values() )
119

    
120
    def do_sh( self, line ):
121
        "Run an external shell command"
122
        call( line, shell=True )
123

    
124
    # do_py() and do_px() need to catch any exception during eval()/exec()
125
    # pylint: disable-msg=W0703
126

    
127
    def do_py( self, line ):
128
        """Evaluate a Python expression.
129
           Node names may be used, e.g.: py h1.cmd('ls')"""
130
        try:
131
            result = eval( line, globals(), self.getLocals() )
132
            if not result:
133
                return
134
            elif isinstance( result, str ):
135
                output( result + '\n' )
136
            else:
137
                output( repr( result ) + '\n' )
138
        except Exception, e:
139
            output( str( e ) + '\n' )
140

    
141
    # We are in fact using the exec() pseudo-function
142
    # pylint: disable-msg=W0122
143

    
144
    def do_px( self, line ):
145
        """Execute a Python statement.
146
            Node names may be used, e.g.: px print h1.cmd('ls')"""
147
        try:
148
            exec( line, globals(), self.getLocals() )
149
        except Exception, e:
150
            output( str( e ) + '\n' )
151

    
152
    # pylint: enable-msg=W0703,W0122
153

    
154
    def do_pingall( self, _line ):
155
        "Ping between all hosts."
156
        self.mn.pingAll()
157

    
158
    def do_pingpair( self, _line ):
159
        "Ping between first two hosts, useful for testing."
160
        self.mn.pingPair()
161

    
162
    def do_pingallfull( self, _line ):
163
        "Ping between first two hosts, returns all ping results."
164
        self.mn.pingAllFull()
165

    
166
    def do_pingpairfull( self, _line ):
167
        "Ping between first two hosts, returns all ping results."
168
        self.mn.pingPairFull()
169

    
170
    def do_iperf( self, line ):
171
        "Simple iperf TCP test between two (optionally specified) hosts."
172
        args = line.split()
173
        if not args:
174
            self.mn.iperf()
175
        elif len(args) == 2:
176
            hosts = []
177
            err = False
178
            for arg in args:
179
                if arg not in self.mn:
180
                    err = True
181
                    error( "node '%s' not in network\n" % arg )
182
                else:
183
                    hosts.append( self.mn[ arg ] )
184
            if not err:
185
                self.mn.iperf( hosts )
186
        else:
187
            error( 'invalid number of args: iperf src dst\n' )
188

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

    
210
    def do_intfs( self, _line ):
211
        "List interfaces."
212
        for node in self.mn.values():
213
            output( '%s: %s\n' %
214
                    ( node.name, ','.join( node.intfNames() ) ) )
215

    
216
    def do_dump( self, _line ):
217
        "Dump node info."
218
        for node in self.mn.values():
219
            output( '%s\n' % repr( node ) )
220

    
221
    def do_link( self, line ):
222
        "Bring link(s) between two nodes up or down."
223
        args = line.split()
224
        if len(args) != 3:
225
            error( 'invalid number of args: link end1 end2 [up down]\n' )
226
        elif args[ 2 ] not in [ 'up', 'down' ]:
227
            error( 'invalid type: link end1 end2 [up down]\n' )
228
        else:
229
            self.mn.configLinkStatus( *args )
230

    
231
    def do_xterm( self, line, term='xterm' ):
232
        "Spawn xterm(s) for the given node(s)."
233
        args = line.split()
234
        if not args:
235
            error( 'usage: %s node1 node2 ...\n' % term )
236
        else:
237
            for arg in args:
238
                if arg not in self.mn:
239
                    error( "node '%s' not in network\n" % arg )
240
                else:
241
                    node = self.mn[ arg ]
242
                    self.mn.terms += makeTerms( [ node ], term = term )
243

    
244
    def do_x( self, line ):
245
        """Create an X11 tunnel to the given node,
246
           optionally starting a client."""
247
        args = line.split()
248
        if not args:
249
            error( 'usage: x node [cmd args]...\n' )
250
        else:
251
            node = self.mn[ args[ 0 ] ]
252
            cmd = args[ 1: ]
253
            self.mn.terms += runX11( node, cmd )
254

    
255
    def do_gterm( self, line ):
256
        "Spawn gnome-terminal(s) for the given node(s)."
257
        self.do_xterm( line, term='gterm' )
258

    
259
    def do_exit( self, _line ):
260
        "Exit"
261
        return 'exited by user command'
262

    
263
    def do_quit( self, line ):
264
        "Exit"
265
        return self.do_exit( line )
266

    
267
    def do_EOF( self, line ):
268
        "Exit"
269
        output( '\n' )
270
        return self.do_exit( line )
271

    
272
    def isatty( self ):
273
        "Is our standard input a tty?"
274
        return isatty( self.stdin.fileno() )
275

    
276
    def do_noecho( self, line ):
277
        "Run an interactive command with echoing turned off."
278
        if self.isatty():
279
            quietRun( 'stty -echo' )
280
        self.default( line )
281
        if self.isatty():
282
            quietRun( 'stty echo' )
283

    
284
    def do_source( self, line ):
285
        "Read commands from an input file."
286
        args = line.split()
287
        if len(args) != 1:
288
            error( 'usage: source <file>\n' )
289
            return
290
        try:
291
            self.inputFile = open( args[ 0 ] )
292
            while True:
293
                line = self.inputFile.readline()
294
                if len( line ) > 0:
295
                    self.onecmd( line )
296
                else:
297
                    break
298
        except IOError:
299
            error( 'error reading file %s\n' % args[ 0 ] )
300
        self.inputFile = None
301

    
302
    def do_dpctl( self, line ):
303
        "Run dpctl (or ovs-ofctl) command on all switches."
304
        args = line.split()
305
        if len(args) < 1:
306
            error( 'usage: dpctl command [arg1] [arg2] ...\n' )
307
            return
308
        for sw in self.mn.switches:
309
            output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
310
            output( sw.dpctl( *args ) )
311

    
312
    def do_time( self, line ):
313
        "Measure time taken for any command in Mininet."
314
        start = time.time()
315
        self.onecmd(line)
316
        elapsed = time.time() - start
317
        self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
318

    
319
    def default( self, line ):
320
        """Called on an input line when the command prefix is not recognized.
321
        Overridden to run shell commands when a node is the first CLI argument.
322
        Past the first CLI argument, node names are automatically replaced with
323
        corresponding IP addrs."""
324

    
325
        first, args, line = self.parseline( line )
326
        if not args:
327
            return
328
        if args and len(args) > 0 and args[ -1 ] == '\n':
329
            args = args[ :-1 ]
330
        rest = args.split( ' ' )
331

    
332
        if first in self.mn:
333
            node = self.mn[ first ]
334
            # Substitute IP addresses for node names in command
335
            rest = [ self.mn[ arg ].defaultIntf().updateIP()
336
                     if arg in self.mn else arg
337
                     for arg in rest ]
338
            rest = ' '.join( rest )
339
            # Run cmd on node:
340
            builtin = isShellBuiltin( first )
341
            node.sendCmd( rest, printPid=( not builtin ) )
342
            self.waitForNode( node )
343
        else:
344
            error( '*** Unknown command: %s\n' % first )
345

    
346
    # pylint: enable-msg=R0201
347

    
348
    def waitForNode( self, node ):
349
        "Wait for a node to finish, and  print its output."
350
        # Pollers
351
        nodePoller = poll()
352
        nodePoller.register( node.stdout )
353
        bothPoller = poll()
354
        bothPoller.register( self.stdin, POLLIN )
355
        bothPoller.register( node.stdout, POLLIN )
356
        if self.isatty():
357
            # Buffer by character, so that interactive
358
            # commands sort of work
359
            quietRun( 'stty -icanon min 1' )
360
        while True:
361
            try:
362
                bothPoller.poll()
363
                # XXX BL: this doesn't quite do what we want.
364
                if False and self.inputFile:
365
                    key = self.inputFile.read( 1 )
366
                    if key is not '':
367
                        node.write(key)
368
                    else:
369
                        self.inputFile = None
370
                if isReadable( self.inPoller ):
371
                    key = self.stdin.read( 1 )
372
                    node.write( key )
373
                if isReadable( nodePoller ):
374
                    data = node.monitor()
375
                    output( data )
376
                if not node.waiting:
377
                    break
378
            except KeyboardInterrupt:
379
                node.sendInt()
380

    
381
# Helper functions
382

    
383
def isReadable( poller ):
384
    "Check whether a Poll object has a readable fd."
385
    for fdmask in poller.poll( 0 ):
386
        mask = fdmask[ 1 ]
387
        if mask & POLLIN:
388
            return True