Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ 92a28881

History | View | Annotate | Download (12.9 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 beb05a71 Bob Lantz
from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
40 82b72072 Bob Lantz
41 114dcd56 Brandon Heller
class CLI( Cmd ):
42 496b5f9e Bob Lantz
    "Simple command-line interface to talk to nodes."
43
44 114dcd56 Brandon Heller
    prompt = 'mininet> '
45 496b5f9e Bob Lantz
46 e3a1fbb0 Bob Lantz
    def __init__( self, mininet, stdin=sys.stdin, script=None ):
47 496b5f9e Bob Lantz
        self.mn = mininet
48 98cb3359 Brian O'Connor
        # Local variable bindings for py command
49 9281719d Brian O'Connor
        self.locals = { 'net': mininet }
50 f800e512 Bob Lantz
        # Attempt to handle input
51
        self.stdin = stdin
52
        self.inPoller = poll()
53
        self.inPoller.register( stdin )
54 e3a1fbb0 Bob Lantz
        self.inputFile = script
55 114dcd56 Brandon Heller
        Cmd.__init__( self )
56 4e69ae83 Brandon Heller
        info( '*** Starting CLI:\n' )
57 a905be22 Rich Lane
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 e3a1fbb0 Bob Lantz
        if self.inputFile:
70
            self.do_source( self.inputFile )
71
            return
72 bcacfc05 Bob Lantz
        while True:
73
            try:
74 f800e512 Bob Lantz
                # Make sure no nodes are still waiting
75 8e04a9f8 Brian O'Connor
                for node in self.mn.values():
76 f800e512 Bob Lantz
                    while node.waiting:
77
                        node.sendInt()
78
                        node.monitor()
79
                if self.isatty():
80
                    quietRun( 'stty sane' )
81 bcacfc05 Bob Lantz
                self.cmdloop()
82
                break
83
            except KeyboardInterrupt:
84 f800e512 Bob Lantz
                output( '\nInterrupt\n' )
85 bcacfc05 Bob Lantz
86
    def emptyline( self ):
87
        "Don't repeat last command when you hit return."
88
        pass
89 496b5f9e Bob Lantz
90 9281719d Brian O'Connor
    def getLocals( self ):
91 8e04a9f8 Brian O'Connor
        "Local variable bindings for py command"
92 9281719d Brian O'Connor
        self.locals.update( self.mn )
93
        return self.locals
94 8e04a9f8 Brian O'Connor
95 b2ef87ae Bob Lantz
    # 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 14ff3ad3 Bob Lantz
    # pylint: disable-msg=R0201
99 496b5f9e Bob Lantz
100 82b72072 Bob Lantz
    helpStr = (
101 caf024bc Bob Lantz
        '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 2a554ae3 Bob Lantz
    def do_help( self, line ):
120 114dcd56 Brandon Heller
        "Describe available CLI commands."
121 2a554ae3 Bob Lantz
        Cmd.do_help( self, line )
122
        if line is '':
123 caf024bc Bob Lantz
            output( self.helpStr )
124 496b5f9e Bob Lantz
125 14ff3ad3 Bob Lantz
    def do_nodes( self, _line ):
126 496b5f9e Bob Lantz
        "List all nodes."
127 9281719d Brian O'Connor
        nodes = ' '.join( sorted( self.mn ) )
128 cdeaca86 Brandon Heller
        output( 'available nodes are: \n%s\n' % nodes )
129 496b5f9e Bob Lantz
130 14ff3ad3 Bob Lantz
    def do_net( self, _line ):
131 496b5f9e Bob Lantz
        "List network connections."
132 9281719d Brian O'Connor
        dumpNodeConnections( self.mn.values() )
133 496b5f9e Bob Lantz
134 2a554ae3 Bob Lantz
    def do_sh( self, line ):
135 496b5f9e Bob Lantz
        "Run an external shell command"
136 2a554ae3 Bob Lantz
        call( line, shell=True )
137 496b5f9e Bob Lantz
138 23c70f60 Bob Lantz
    # do_py() and do_px() need to catch any exception during eval()/exec()
139 1fdcd676 Bob Lantz
    # pylint: disable-msg=W0703
140
141 2a554ae3 Bob Lantz
    def do_py( self, line ):
142 1fdcd676 Bob Lantz
        """Evaluate a Python expression.
143 23c70f60 Bob Lantz
           Node names may be used, e.g.: py h1.cmd('ls')"""
144 1fdcd676 Bob Lantz
        try:
145 9281719d Brian O'Connor
            result = eval( line, globals(), self.getLocals() )
146 1fdcd676 Bob Lantz
            if not result:
147
                return
148
            elif isinstance( result, str ):
149 f800e512 Bob Lantz
                output( result + '\n' )
150 1fdcd676 Bob Lantz
            else:
151 f800e512 Bob Lantz
                output( repr( result ) + '\n' )
152 1fdcd676 Bob Lantz
        except Exception, e:
153 f800e512 Bob Lantz
            output( str( e ) + '\n' )
154 1fdcd676 Bob Lantz
155 23c70f60 Bob Lantz
    # 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 9281719d Brian O'Connor
            exec( line, globals(), self.getLocals() )
163 23c70f60 Bob Lantz
        except Exception, e:
164
            output( str( e ) + '\n' )
165
166
    # pylint: enable-msg=W0703,W0122
167 1fdcd676 Bob Lantz
168 14ff3ad3 Bob Lantz
    def do_pingall( self, _line ):
169 496b5f9e Bob Lantz
        "Ping between all hosts."
170
        self.mn.pingAll()
171
172 14ff3ad3 Bob Lantz
    def do_pingpair( self, _line ):
173 496b5f9e Bob Lantz
        "Ping between first two hosts, useful for testing."
174
        self.mn.pingPair()
175
176 1f1d590c Brandon Heller
    def do_pingallfull( self, _line ):
177 24fe68d9 Baohua Yang
        "Ping between all hosts, returns all ping results."
178 1f1d590c Brandon Heller
        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 2a554ae3 Bob Lantz
    def do_iperf( self, line ):
185 60a39a72 Brandon Heller
        "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 8e04a9f8 Brian O'Connor
                if arg not in self.mn:
194 60a39a72 Brandon Heller
                    err = True
195
                    error( "node '%s' not in network\n" % arg )
196
                else:
197 8e04a9f8 Brian O'Connor
                    hosts.append( self.mn[ arg ] )
198 60a39a72 Brandon Heller
            if not err:
199
                self.mn.iperf( hosts )
200
        else:
201
            error( 'invalid number of args: iperf src dst\n' )
202 496b5f9e Bob Lantz
203 2a554ae3 Bob Lantz
    def do_iperfudp( self, line ):
204 24fe68d9 Baohua Yang
        "Simple iperf UDP test between two (optionally specified) hosts."
205 2a554ae3 Bob Lantz
        args = line.split()
206 57aae3e1 Brandon Heller
        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 8e04a9f8 Brian O'Connor
                if arg not in self.mn:
214 57aae3e1 Brandon Heller
                    err = True
215
                    error( "node '%s' not in network\n" % arg )
216
                else:
217 8e04a9f8 Brian O'Connor
                    hosts.append( self.mn[ arg ] )
218 57aae3e1 Brandon Heller
            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 496b5f9e Bob Lantz
224 14ff3ad3 Bob Lantz
    def do_intfs( self, _line ):
225 496b5f9e Bob Lantz
        "List interfaces."
226 9281719d Brian O'Connor
        for node in self.mn.values():
227 cdeaca86 Brandon Heller
            output( '%s: %s\n' %
228 edf60032 Brandon Heller
                    ( node.name, ','.join( node.intfNames() ) ) )
229 496b5f9e Bob Lantz
230 14ff3ad3 Bob Lantz
    def do_dump( self, _line ):
231 496b5f9e Bob Lantz
        "Dump node info."
232 9281719d Brian O'Connor
        for node in self.mn.values():
233 8856d284 Bob Lantz
            output( '%s\n' % repr( node ) )
234 496b5f9e Bob Lantz
235 2a554ae3 Bob Lantz
    def do_link( self, line ):
236 c70aab0a Bob Lantz
        "Bring link(s) between two nodes up or down."
237 2a554ae3 Bob Lantz
        args = line.split()
238 8d3c2859 Brandon Heller
        if len(args) != 3:
239 c70aab0a Bob Lantz
            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 8d3c2859 Brandon Heller
        else:
243 c70aab0a Bob Lantz
            self.mn.configLinkStatus( *args )
244 8d3c2859 Brandon Heller
245 2a554ae3 Bob Lantz
    def do_xterm( self, line, term='xterm' ):
246 68f97b74 Bob Lantz
        "Spawn xterm(s) for the given node(s)."
247 2a554ae3 Bob Lantz
        args = line.split()
248 ce9cd5be Brandon Heller
        if not args:
249 f800e512 Bob Lantz
            error( 'usage: %s node1 node2 ...\n' % term )
250 ce9cd5be Brandon Heller
        else:
251
            for arg in args:
252 8e04a9f8 Brian O'Connor
                if arg not in self.mn:
253 68f97b74 Bob Lantz
                    error( "node '%s' not in network\n" % arg )
254 ce9cd5be Brandon Heller
                else:
255 8e04a9f8 Brian O'Connor
                    node = self.mn[ arg ]
256 15b482e3 Brandon Heller
                    self.mn.terms += makeTerms( [ node ], term = term )
257 ce9cd5be Brandon Heller
258 e3d07bc1 Bob Lantz
    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 2a554ae3 Bob Lantz
    def do_gterm( self, line ):
270 68f97b74 Bob Lantz
        "Spawn gnome-terminal(s) for the given node(s)."
271 2a554ae3 Bob Lantz
        self.do_xterm( line, term='gterm' )
272 68f97b74 Bob Lantz
273 14ff3ad3 Bob Lantz
    def do_exit( self, _line ):
274 114dcd56 Brandon Heller
        "Exit"
275
        return 'exited by user command'
276 496b5f9e Bob Lantz
277 2a554ae3 Bob Lantz
    def do_quit( self, line ):
278 114dcd56 Brandon Heller
        "Exit"
279 2a554ae3 Bob Lantz
        return self.do_exit( line )
280 64c451e0 Bob Lantz
281 2a554ae3 Bob Lantz
    def do_EOF( self, line ):
282 64c451e0 Bob Lantz
        "Exit"
283 f800e512 Bob Lantz
        output( '\n' )
284 2a554ae3 Bob Lantz
        return self.do_exit( line )
285 114dcd56 Brandon Heller
286 f800e512 Bob Lantz
    def isatty( self ):
287
        "Is our standard input a tty?"
288
        return isatty( self.stdin.fileno() )
289 82b72072 Bob Lantz
290 f800e512 Bob Lantz
    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 9d0dbe48 Bob Lantz
        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 dde9c91d Bob Lantz
            error( 'error reading file %s\n' % args[ 0 ] )
314 3780d9cd Baohua Yang
        self.inputFile.close()
315 9d0dbe48 Bob Lantz
        self.inputFile = None
316 f800e512 Bob Lantz
317 9da63d4e Brandon Heller
    def do_dpctl( self, line ):
318 229f112f Bob Lantz
        "Run dpctl (or ovs-ofctl) command on all switches."
319 9da63d4e Brandon Heller
        args = line.split()
320 8856d284 Bob Lantz
        if len(args) < 1:
321 9da63d4e Brandon Heller
            error( 'usage: dpctl command [arg1] [arg2] ...\n' )
322
            return
323
        for sw in self.mn.switches:
324
            output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
325 8856d284 Bob Lantz
            output( sw.dpctl( *args ) )
326 9da63d4e Brandon Heller
327 f509ae28 Brandon Heller
    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 114dcd56 Brandon Heller
    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 64c451e0 Bob Lantz
        corresponding IP addrs."""
339 31b43002 Bob Lantz
340 114dcd56 Brandon Heller
        first, args, line = self.parseline( line )
341
342 8e04a9f8 Brian O'Connor
        if first in self.mn:
343 e9a835ac Brian O'Connor
            if not args:
344
                print "*** Enter a command for node: %s <cmd>" % first
345
                return
346 8e04a9f8 Brian O'Connor
            node = self.mn[ first ]
347 e9a835ac Brian O'Connor
            rest = args.split( ' ' )
348 114dcd56 Brandon Heller
            # Substitute IP addresses for node names in command
349 c7e86f93 Brian O'Connor
            # If updateIP() returns None, then use node name
350
            rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg
351 8e04a9f8 Brian O'Connor
                     if arg in self.mn else arg
352 edf60032 Brandon Heller
                     for arg in rest ]
353 114dcd56 Brandon Heller
            rest = ' '.join( rest )
354
            # Run cmd on node:
355 82b72072 Bob Lantz
            builtin = isShellBuiltin( first )
356
            node.sendCmd( rest, printPid=( not builtin ) )
357
            self.waitForNode( node )
358 114dcd56 Brandon Heller
        else:
359 e9a835ac Brian O'Connor
            error( '*** Unknown command: %s\n' % line )
360 114dcd56 Brandon Heller
361 14ff3ad3 Bob Lantz
    # pylint: enable-msg=R0201
362 e91b2815 Bob Lantz
363 82b72072 Bob Lantz
    def waitForNode( self, node ):
364 f800e512 Bob Lantz
        "Wait for a node to finish, and  print its output."
365
        # Pollers
366
        nodePoller = poll()
367
        nodePoller.register( node.stdout )
368
        bothPoller = poll()
369 cece39e4 Bob Lantz
        bothPoller.register( self.stdin, POLLIN )
370
        bothPoller.register( node.stdout, POLLIN )
371 f800e512 Bob Lantz
        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 5c24e186 Bob Lantz
                # XXX BL: this doesn't quite do what we want.
379
                if False and self.inputFile:
380 9d0dbe48 Bob Lantz
                    key = self.inputFile.read( 1 )
381
                    if key is not '':
382
                        node.write(key)
383
                    else:
384
                        self.inputFile = None
385 82b72072 Bob Lantz
                if isReadable( self.inPoller ):
386 f800e512 Bob Lantz
                    key = self.stdin.read( 1 )
387
                    node.write( key )
388 82b72072 Bob Lantz
                if isReadable( nodePoller ):
389 f800e512 Bob Lantz
                    data = node.monitor()
390 e555f83c Bob Lantz
                    output( data )
391 f800e512 Bob Lantz
                if not node.waiting:
392
                    break
393
            except KeyboardInterrupt:
394
                node.sendInt()
395 e91b2815 Bob Lantz
396 82b72072 Bob Lantz
# 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