Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ 149a1f56

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

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

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

    
41
    prompt = 'mininet> '
42

    
43
    def __init__( self, mininet, stdin=sys.stdin, script=None ):
44
        self.mn = mininet
45
        self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
46
        self.nodemap = {}  # map names to Node objects
47
        for node in self.nodelist:
48
            self.nodemap[ node.name ] = node
49
        # Local variable bindings for py command
50
        self.locals = { 'net': mininet }
51
        self.locals.update( self.nodemap )
52
        # Attempt to handle input
53
        self.stdin = stdin
54
        self.inPoller = poll()
55
        self.inPoller.register( stdin )
56
        self.inputFile = script
57
        Cmd.__init__( self )
58
        info( '*** Starting CLI:\n' )
59
        if self.inputFile:
60
            self.do_source( self.inputFile )
61
            return
62
        while True:
63
            try:
64
                # 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
                self.cmdloop()
72
                break
73
            except KeyboardInterrupt:
74
                output( '\nInterrupt\n' )
75

    
76
    def emptyline( self ):
77
        "Don't repeat last command when you hit return."
78
        pass
79

    
80
    # 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
    # pylint: disable-msg=R0201
84

    
85
    helpStr = (
86
        '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
    def do_help( self, line ):
105
        "Describe available CLI commands."
106
        Cmd.do_help( self, line )
107
        if line is '':
108
            output( self.helpStr )
109

    
110
    def do_nodes( self, _line ):
111
        "List all nodes."
112
        nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
113
        output( 'available nodes are: \n%s\n' % nodes )
114

    
115
    def do_net( self, _line ):
116
        "List network connections."
117
        dumpNodeConnections( self.nodelist )
118

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

    
123
    # do_py() needs to catch any exception during eval()
124
    # pylint: disable-msg=W0703
125

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

    
140
    # pylint: enable-msg=W0703
141

    
142
    def do_pingall( self, _line ):
143
        "Ping between all hosts."
144
        self.mn.pingAll()
145

    
146
    def do_pingpair( self, _line ):
147
        "Ping between first two hosts, useful for testing."
148
        self.mn.pingPair()
149

    
150
    def do_iperf( self, line ):
151
        "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

    
169
    def do_iperfudp( self, line ):
170
        "Simple iperf TCP test between two (optionally specified) hosts."
171
        args = line.split()
172
        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

    
190
    def do_intfs( self, _line ):
191
        "List interfaces."
192
        for node in self.nodelist:
193
            output( '%s: %s\n' %
194
                ( node.name, ','.join( node.intfNames() ) ) )
195

    
196
    def do_dump( self, _line ):
197
        "Dump node info."
198
        for node in self.nodelist:
199
            output( '%s\n' % repr( node ) )
200

    
201
    def do_link( self, line ):
202
        "Bring link(s) between two nodes up or down."
203
        args = line.split()
204
        if len(args) != 3:
205
            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
        else:
209
            self.mn.configLinkStatus( *args )
210

    
211
    def do_xterm( self, line, term='xterm' ):
212
        "Spawn xterm(s) for the given node(s)."
213
        args = line.split()
214
        if not args:
215
            error( 'usage: %s node1 node2 ...\n' % term )
216
        else:
217
            for arg in args:
218
                if arg not in self.nodemap:
219
                    error( "node '%s' not in network\n" % arg )
220
                else:
221
                    node = self.nodemap[ arg ]
222
                    self.mn.terms += makeTerms( [ node ], term = term )
223

    
224
    def do_gterm( self, line ):
225
        "Spawn gnome-terminal(s) for the given node(s)."
226
        self.do_xterm( line, term='gterm' )
227

    
228
    def do_exit( self, _line ):
229
        "Exit"
230
        return 'exited by user command'
231

    
232
    def do_quit( self, line ):
233
        "Exit"
234
        return self.do_exit( line )
235

    
236
    def do_EOF( self, line ):
237
        "Exit"
238
        output( '\n' )
239
        return self.do_exit( line )
240

    
241
    def isatty( self ):
242
        "Is our standard input a tty?"
243
        return isatty( self.stdin.fileno() )
244

    
245
    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
        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
            error( 'error reading file %s\n' % args[ 0 ] )
269
        self.inputFile = None
270

    
271
    def do_dpctl( self, line ):
272
        "Run dpctl command on all switches."
273
        args = line.split()
274
        if len(args) < 1:
275
            error( 'usage: dpctl command [arg1] [arg2] ...\n' )
276
            return
277
        for sw in self.mn.switches:
278
            output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
279
            output( sw.dpctl( *args ) )
280

    
281
    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
        corresponding IP addrs."""
286

    
287
        first, args, line = self.parseline( line )
288
        if not args:
289
            return
290
        if args and len(args) > 0 and args[ -1 ] == '\n':
291
            args = args[ :-1 ]
292
        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
                    if arg in self.nodemap else arg
299
                    for arg in rest ]
300
            rest = ' '.join( rest )
301
            # Run cmd on node:
302
            builtin = isShellBuiltin( first )
303
            node.sendCmd( rest, printPid=( not builtin ) )
304
            self.waitForNode( node )
305
        else:
306
            error( '*** Unknown command: %s\n' % first )
307

    
308
    # pylint: enable-msg=R0201
309

    
310
    def waitForNode( self, node ):
311
        "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
                # XXX BL: this doesn't quite do what we want.
326
                if False and self.inputFile:
327
                    key = self.inputFile.read( 1 )
328
                    if key is not '':
329
                        node.write(key)
330
                    else:
331
                        self.inputFile = None
332
                if isReadable( self.inPoller ):
333
                    key = self.stdin.read( 1 )
334
                    node.write( key )
335
                if isReadable( nodePoller ):
336
                    data = node.monitor()
337
                    output( data )
338
                if not node.waiting:
339
                    break
340
            except KeyboardInterrupt:
341
                node.sendInt()
342

    
343
# 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