Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ 149a1f56

History | View | Annotate | Download (11.2 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 496b5f9e Bob Lantz
34 8d3c2859 Brandon Heller
from mininet.log import info, output, error
35 15b482e3 Brandon Heller
from mininet.term import makeTerms
36 beb05a71 Bob Lantz
from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
37 82b72072 Bob Lantz
38 114dcd56 Brandon Heller
class CLI( Cmd ):
39 496b5f9e Bob Lantz
    "Simple command-line interface to talk to nodes."
40
41 114dcd56 Brandon Heller
    prompt = 'mininet> '
42 496b5f9e Bob Lantz
43 e3a1fbb0 Bob Lantz
    def __init__( self, mininet, stdin=sys.stdin, script=None ):
44 496b5f9e Bob Lantz
        self.mn = mininet
45 1fdcd676 Bob Lantz
        self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
46 cf6f6704 Bob Lantz
        self.nodemap = {}  # map names to Node objects
47 78073e1b Bob Lantz
        for node in self.nodelist:
48 496b5f9e Bob Lantz
            self.nodemap[ node.name ] = node
49 2d924f8a Bob Lantz
        # Local variable bindings for py command
50
        self.locals = { 'net': mininet }
51
        self.locals.update( self.nodemap )
52 f800e512 Bob Lantz
        # Attempt to handle input
53
        self.stdin = stdin
54
        self.inPoller = poll()
55
        self.inPoller.register( stdin )
56 e3a1fbb0 Bob Lantz
        self.inputFile = script
57 114dcd56 Brandon Heller
        Cmd.__init__( self )
58 4e69ae83 Brandon Heller
        info( '*** Starting CLI:\n' )
59 e3a1fbb0 Bob Lantz
        if self.inputFile:
60
            self.do_source( self.inputFile )
61
            return
62 bcacfc05 Bob Lantz
        while True:
63
            try:
64 f800e512 Bob Lantz
                # Make sure no nodes are still waiting
65
                for node in self.nodelist:
66
                    while node.waiting:
67
                        node.sendInt()
68
                        node.monitor()
69
                if self.isatty():
70
                    quietRun( 'stty sane' )
71 bcacfc05 Bob Lantz
                self.cmdloop()
72
                break
73
            except KeyboardInterrupt:
74 f800e512 Bob Lantz
                output( '\nInterrupt\n' )
75 bcacfc05 Bob Lantz
76
    def emptyline( self ):
77
        "Don't repeat last command when you hit return."
78
        pass
79 496b5f9e Bob Lantz
80 b2ef87ae Bob Lantz
    # Disable pylint "Unused argument: 'arg's'" messages, as well as
81
    # "method could be a function" warning, since each CLI function
82
    # must have the same interface
83 14ff3ad3 Bob Lantz
    # pylint: disable-msg=R0201
84 496b5f9e Bob Lantz
85 82b72072 Bob Lantz
    helpStr = (
86 caf024bc Bob Lantz
        'You may also send a command to a node using:\n'
87
        '  <node> command {args}\n'
88
        'For example:\n'
89
        '  mininet> h1 ifconfig\n'
90
        '\n'
91
        'The interpreter automatically substitutes IP addresses\n'
92
        'for node names when a node is the first arg, so commands\n'
93
        'like\n'
94
        '  mininet> h2 ping h3\n'
95
        'should work.\n'
96
        '\n'
97
        'Some character-oriented interactive commands require\n'
98
        'noecho:\n'
99
        '  mininet> noecho h2 vi foo.py\n'
100
        'However, starting up an xterm/gterm is generally better:\n'
101
        '  mininet> xterm h2\n\n'
102
    )
103
104 2a554ae3 Bob Lantz
    def do_help( self, line ):
105 114dcd56 Brandon Heller
        "Describe available CLI commands."
106 2a554ae3 Bob Lantz
        Cmd.do_help( self, line )
107
        if line is '':
108 caf024bc Bob Lantz
            output( self.helpStr )
109 496b5f9e Bob Lantz
110 14ff3ad3 Bob Lantz
    def do_nodes( self, _line ):
111 496b5f9e Bob Lantz
        "List all nodes."
112
        nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
113 cdeaca86 Brandon Heller
        output( 'available nodes are: \n%s\n' % nodes )
114 496b5f9e Bob Lantz
115 14ff3ad3 Bob Lantz
    def do_net( self, _line ):
116 496b5f9e Bob Lantz
        "List network connections."
117 beb05a71 Bob Lantz
        dumpNodeConnections( self.nodelist )
118 496b5f9e Bob Lantz
119 2a554ae3 Bob Lantz
    def do_sh( self, line ):
120 496b5f9e Bob Lantz
        "Run an external shell command"
121 2a554ae3 Bob Lantz
        call( line, shell=True )
122 496b5f9e Bob Lantz
123 1fdcd676 Bob Lantz
    # do_py() needs to catch any exception during eval()
124
    # pylint: disable-msg=W0703
125
126 2a554ae3 Bob Lantz
    def do_py( self, line ):
127 1fdcd676 Bob Lantz
        """Evaluate a Python expression.
128
           Node names may be used, e.g.: h1.cmd('ls')"""
129
        try:
130 2d924f8a Bob Lantz
            result = eval( line, globals(), self.locals )
131 1fdcd676 Bob Lantz
            if not result:
132
                return
133
            elif isinstance( result, str ):
134 f800e512 Bob Lantz
                output( result + '\n' )
135 1fdcd676 Bob Lantz
            else:
136 f800e512 Bob Lantz
                output( repr( result ) + '\n' )
137 1fdcd676 Bob Lantz
        except Exception, e:
138 f800e512 Bob Lantz
            output( str( e ) + '\n' )
139 1fdcd676 Bob Lantz
140
    # pylint: enable-msg=W0703
141
142 14ff3ad3 Bob Lantz
    def do_pingall( self, _line ):
143 496b5f9e Bob Lantz
        "Ping between all hosts."
144
        self.mn.pingAll()
145
146 14ff3ad3 Bob Lantz
    def do_pingpair( self, _line ):
147 496b5f9e Bob Lantz
        "Ping between first two hosts, useful for testing."
148
        self.mn.pingPair()
149
150 2a554ae3 Bob Lantz
    def do_iperf( self, line ):
151 60a39a72 Brandon Heller
        "Simple iperf TCP test between two (optionally specified) hosts."
152
        args = line.split()
153
        if not args:
154
            self.mn.iperf()
155
        elif len(args) == 2:
156
            hosts = []
157
            err = False
158
            for arg in args:
159
                if arg not in self.nodemap:
160
                    err = True
161
                    error( "node '%s' not in network\n" % arg )
162
                else:
163
                    hosts.append( self.nodemap[ arg ] )
164
            if not err:
165
                self.mn.iperf( hosts )
166
        else:
167
            error( 'invalid number of args: iperf src dst\n' )
168 496b5f9e Bob Lantz
169 2a554ae3 Bob Lantz
    def do_iperfudp( self, line ):
170 57aae3e1 Brandon Heller
        "Simple iperf TCP test between two (optionally specified) hosts."
171 2a554ae3 Bob Lantz
        args = line.split()
172 57aae3e1 Brandon Heller
        if not args:
173
            self.mn.iperf( l4Type='UDP' )
174
        elif len(args) == 3:
175
            udpBw = args[ 0 ]
176
            hosts = []
177
            err = False
178
            for arg in args[ 1:3 ]:
179
                if arg not in self.nodemap:
180
                    err = True
181
                    error( "node '%s' not in network\n" % arg )
182
                else:
183
                    hosts.append( self.nodemap[ arg ] )
184
            if not err:
185
                self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
186
        else:
187
            error( 'invalid number of args: iperfudp bw src dst\n' +
188
                   'bw examples: 10M\n' )
189 496b5f9e Bob Lantz
190 14ff3ad3 Bob Lantz
    def do_intfs( self, _line ):
191 496b5f9e Bob Lantz
        "List interfaces."
192 2235f216 Bob Lantz
        for node in self.nodelist:
193 cdeaca86 Brandon Heller
            output( '%s: %s\n' %
194 8856d284 Bob Lantz
                ( node.name, ','.join( node.intfNames() ) ) )
195 496b5f9e Bob Lantz
196 14ff3ad3 Bob Lantz
    def do_dump( self, _line ):
197 496b5f9e Bob Lantz
        "Dump node info."
198 2235f216 Bob Lantz
        for node in self.nodelist:
199 8856d284 Bob Lantz
            output( '%s\n' % repr( node ) )
200 496b5f9e Bob Lantz
201 2a554ae3 Bob Lantz
    def do_link( self, line ):
202 c70aab0a Bob Lantz
        "Bring link(s) between two nodes up or down."
203 2a554ae3 Bob Lantz
        args = line.split()
204 8d3c2859 Brandon Heller
        if len(args) != 3:
205 c70aab0a Bob Lantz
            error( 'invalid number of args: link end1 end2 [up down]\n' )
206
        elif args[ 2 ] not in [ 'up', 'down' ]:
207
            error( 'invalid type: link end1 end2 [up down]\n' )
208 8d3c2859 Brandon Heller
        else:
209 c70aab0a Bob Lantz
            self.mn.configLinkStatus( *args )
210 8d3c2859 Brandon Heller
211 2a554ae3 Bob Lantz
    def do_xterm( self, line, term='xterm' ):
212 68f97b74 Bob Lantz
        "Spawn xterm(s) for the given node(s)."
213 2a554ae3 Bob Lantz
        args = line.split()
214 ce9cd5be Brandon Heller
        if not args:
215 f800e512 Bob Lantz
            error( 'usage: %s node1 node2 ...\n' % term )
216 ce9cd5be Brandon Heller
        else:
217
            for arg in args:
218
                if arg not in self.nodemap:
219 68f97b74 Bob Lantz
                    error( "node '%s' not in network\n" % arg )
220 ce9cd5be Brandon Heller
                else:
221 15b482e3 Brandon Heller
                    node = self.nodemap[ arg ]
222
                    self.mn.terms += makeTerms( [ node ], term = term )
223 ce9cd5be Brandon Heller
224 2a554ae3 Bob Lantz
    def do_gterm( self, line ):
225 68f97b74 Bob Lantz
        "Spawn gnome-terminal(s) for the given node(s)."
226 2a554ae3 Bob Lantz
        self.do_xterm( line, term='gterm' )
227 68f97b74 Bob Lantz
228 14ff3ad3 Bob Lantz
    def do_exit( self, _line ):
229 114dcd56 Brandon Heller
        "Exit"
230
        return 'exited by user command'
231 496b5f9e Bob Lantz
232 2a554ae3 Bob Lantz
    def do_quit( self, line ):
233 114dcd56 Brandon Heller
        "Exit"
234 2a554ae3 Bob Lantz
        return self.do_exit( line )
235 64c451e0 Bob Lantz
236 2a554ae3 Bob Lantz
    def do_EOF( self, line ):
237 64c451e0 Bob Lantz
        "Exit"
238 f800e512 Bob Lantz
        output( '\n' )
239 2a554ae3 Bob Lantz
        return self.do_exit( line )
240 114dcd56 Brandon Heller
241 f800e512 Bob Lantz
    def isatty( self ):
242
        "Is our standard input a tty?"
243
        return isatty( self.stdin.fileno() )
244 82b72072 Bob Lantz
245 f800e512 Bob Lantz
    def do_noecho( self, line ):
246
        "Run an interactive command with echoing turned off."
247
        if self.isatty():
248
            quietRun( 'stty -echo' )
249
        self.default( line )
250 9d0dbe48 Bob Lantz
        if self.isatty():
251
            quietRun( 'stty echo' )
252
253
    def do_source( self, line ):
254
        "Read commands from an input file."
255
        args = line.split()
256
        if len(args) != 1:
257
            error( 'usage: source <file>\n' )
258
            return
259
        try:
260
            self.inputFile = open( args[ 0 ] )
261
            while True:
262
                line = self.inputFile.readline()
263
                if len( line ) > 0:
264
                    self.onecmd( line )
265
                else:
266
                    break
267
        except IOError:
268 dde9c91d Bob Lantz
            error( 'error reading file %s\n' % args[ 0 ] )
269 9d0dbe48 Bob Lantz
        self.inputFile = None
270 f800e512 Bob Lantz
271 9da63d4e Brandon Heller
    def do_dpctl( self, line ):
272
        "Run dpctl command on all switches."
273
        args = line.split()
274 8856d284 Bob Lantz
        if len(args) < 1:
275 9da63d4e Brandon Heller
            error( 'usage: dpctl command [arg1] [arg2] ...\n' )
276
            return
277
        for sw in self.mn.switches:
278
            output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
279 8856d284 Bob Lantz
            output( sw.dpctl( *args ) )
280 9da63d4e Brandon Heller
281 114dcd56 Brandon Heller
    def default( self, line ):
282
        """Called on an input line when the command prefix is not recognized.
283
        Overridden to run shell commands when a node is the first CLI argument.
284
        Past the first CLI argument, node names are automatically replaced with
285 64c451e0 Bob Lantz
        corresponding IP addrs."""
286 31b43002 Bob Lantz
287 114dcd56 Brandon Heller
        first, args, line = self.parseline( line )
288 8856d284 Bob Lantz
        if not args:
289
            return
290 9d0dbe48 Bob Lantz
        if args and len(args) > 0 and args[ -1 ] == '\n':
291 64c451e0 Bob Lantz
            args = args[ :-1 ]
292 114dcd56 Brandon Heller
        rest = args.split( ' ' )
293
294
        if first in self.nodemap:
295
            node = self.nodemap[ first ]
296
            # Substitute IP addresses for node names in command
297
            rest = [ self.nodemap[ arg ].IP()
298 496b5f9e Bob Lantz
                    if arg in self.nodemap else arg
299
                    for arg in rest ]
300 114dcd56 Brandon Heller
            rest = ' '.join( rest )
301
            # Run cmd on node:
302 82b72072 Bob Lantz
            builtin = isShellBuiltin( first )
303
            node.sendCmd( rest, printPid=( not builtin ) )
304
            self.waitForNode( node )
305 114dcd56 Brandon Heller
        else:
306 e555f83c Bob Lantz
            error( '*** Unknown command: %s\n' % first )
307 114dcd56 Brandon Heller
308 14ff3ad3 Bob Lantz
    # pylint: enable-msg=R0201
309 e91b2815 Bob Lantz
310 82b72072 Bob Lantz
    def waitForNode( self, node ):
311 f800e512 Bob Lantz
        "Wait for a node to finish, and  print its output."
312
        # Pollers
313
        nodePoller = poll()
314
        nodePoller.register( node.stdout )
315
        bothPoller = poll()
316
        bothPoller.register( self.stdin )
317
        bothPoller.register( node.stdout )
318
        if self.isatty():
319
            # Buffer by character, so that interactive
320
            # commands sort of work
321
            quietRun( 'stty -icanon min 1' )
322
        while True:
323
            try:
324
                bothPoller.poll()
325 5c24e186 Bob Lantz
                # XXX BL: this doesn't quite do what we want.
326
                if False and self.inputFile:
327 9d0dbe48 Bob Lantz
                    key = self.inputFile.read( 1 )
328
                    if key is not '':
329
                        node.write(key)
330
                    else:
331
                        self.inputFile = None
332 82b72072 Bob Lantz
                if isReadable( self.inPoller ):
333 f800e512 Bob Lantz
                    key = self.stdin.read( 1 )
334
                    node.write( key )
335 82b72072 Bob Lantz
                if isReadable( nodePoller ):
336 f800e512 Bob Lantz
                    data = node.monitor()
337 e555f83c Bob Lantz
                    output( data )
338 f800e512 Bob Lantz
                if not node.waiting:
339
                    break
340
            except KeyboardInterrupt:
341
                node.sendInt()
342 e91b2815 Bob Lantz
343 82b72072 Bob Lantz
# Helper functions
344
345
def isReadable( poller ):
346
    "Check whether a Poll object has a readable fd."
347
    for fdmask in poller.poll( 0 ):
348
        mask = fdmask[ 1 ]
349
        if mask & POLLIN:
350
            return True