Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ 8a130dea

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