Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ edf60032

History | View | Annotate | Download (11.5 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
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
        self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
47
        self.nodemap = {}  # map names to Node objects
48
        for node in self.nodelist:
49
            self.nodemap[ node.name ] = node
50
        # Local variable bindings for py command
51
        self.locals = { 'net': mininet }
52
        self.locals.update( self.nodemap )
53
        # Attempt to handle input
54
        self.stdin = stdin
55
        self.inPoller = poll()
56
        self.inPoller.register( stdin )
57
        self.inputFile = script
58
        Cmd.__init__( self )
59
        info( '*** Starting CLI:\n' )
60
        if self.inputFile:
61
            self.do_source( self.inputFile )
62
            return
63
        while True:
64
            try:
65
                # Make sure no nodes are still waiting
66
                for node in self.nodelist:
67
                    while node.waiting:
68
                        node.sendInt()
69
                        node.monitor()
70
                if self.isatty():
71
                    quietRun( 'stty sane' )
72
                self.cmdloop()
73
                break
74
            except KeyboardInterrupt:
75
                output( '\nInterrupt\n' )
76

    
77
    def emptyline( self ):
78
        "Don't repeat last command when you hit return."
79
        pass
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( [ node.name for node in sorted( self.nodelist ) ] )
114
        output( 'available nodes are: \n%s\n' % nodes )
115

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

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

    
124
    # do_py() needs to catch any exception during eval()
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.: h1.cmd('ls')"""
130
        try:
131
            result = eval( line, globals(), self.locals )
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
    # pylint: enable-msg=W0703
142

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

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

    
151
    def do_iperf( self, line ):
152
        "Simple iperf TCP test between two (optionally specified) hosts."
153
        args = line.split()
154
        if not args:
155
            self.mn.iperf()
156
        elif len(args) == 2:
157
            hosts = []
158
            err = False
159
            for arg in args:
160
                if arg not in self.nodemap:
161
                    err = True
162
                    error( "node '%s' not in network\n" % arg )
163
                else:
164
                    hosts.append( self.nodemap[ arg ] )
165
            if not err:
166
                self.mn.iperf( hosts )
167
        else:
168
            error( 'invalid number of args: iperf src dst\n' )
169

    
170
    def do_iperfudp( self, line ):
171
        "Simple iperf TCP test between two (optionally specified) hosts."
172
        args = line.split()
173
        if not args:
174
            self.mn.iperf( l4Type='UDP' )
175
        elif len(args) == 3:
176
            udpBw = args[ 0 ]
177
            hosts = []
178
            err = False
179
            for arg in args[ 1:3 ]:
180
                if arg not in self.nodemap:
181
                    err = True
182
                    error( "node '%s' not in network\n" % arg )
183
                else:
184
                    hosts.append( self.nodemap[ arg ] )
185
            if not err:
186
                self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
187
        else:
188
            error( 'invalid number of args: iperfudp bw src dst\n' +
189
                   'bw examples: 10M\n' )
190

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

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

    
202
    def do_link( self, line ):
203
        "Bring link(s) between two nodes up or down."
204
        args = line.split()
205
        if len(args) != 3:
206
            error( 'invalid number of args: link end1 end2 [up down]\n' )
207
        elif args[ 2 ] not in [ 'up', 'down' ]:
208
            error( 'invalid type: link end1 end2 [up down]\n' )
209
        else:
210
            self.mn.configLinkStatus( *args )
211

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

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

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

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

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

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

    
246
    def do_noecho( self, line ):
247
        "Run an interactive command with echoing turned off."
248
        if self.isatty():
249
            quietRun( 'stty -echo' )
250
        self.default( line )
251
        if self.isatty():
252
            quietRun( 'stty echo' )
253

    
254
    def do_source( self, line ):
255
        "Read commands from an input file."
256
        args = line.split()
257
        if len(args) != 1:
258
            error( 'usage: source <file>\n' )
259
            return
260
        try:
261
            self.inputFile = open( args[ 0 ] )
262
            while True:
263
                line = self.inputFile.readline()
264
                if len( line ) > 0:
265
                    self.onecmd( line )
266
                else:
267
                    break
268
        except IOError:
269
            error( 'error reading file %s\n' % args[ 0 ] )
270
        self.inputFile = None
271

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

    
282
    def do_time( self, line ):
283
        "Measure time taken for any command in Mininet."
284
        start = time.time()
285
        self.onecmd(line)
286
        elapsed = time.time() - start
287
        self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
288

    
289
    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
        corresponding IP addrs."""
294

    
295
        first, args, line = self.parseline( line )
296
        if not args:
297
            return
298
        if args and len(args) > 0 and args[ -1 ] == '\n':
299
            args = args[ :-1 ]
300
        rest = args.split( ' ' )
301

    
302
        if first in self.nodemap:
303
            node = self.nodemap[ first ]
304
            # Substitute IP addresses for node names in command
305
            rest = [ self.nodemap[ arg ].IP()
306
                     if arg in self.nodemap else arg
307
                     for arg in rest ]
308
            rest = ' '.join( rest )
309
            # Run cmd on node:
310
            builtin = isShellBuiltin( first )
311
            node.sendCmd( rest, printPid=( not builtin ) )
312
            self.waitForNode( node )
313
        else:
314
            error( '*** Unknown command: %s\n' % first )
315

    
316
    # pylint: enable-msg=R0201
317

    
318
    def waitForNode( self, node ):
319
        "Wait for a node to finish, and  print its output."
320
        # Pollers
321
        nodePoller = poll()
322
        nodePoller.register( node.stdout )
323
        bothPoller = poll()
324
        bothPoller.register( self.stdin, POLLIN )
325
        bothPoller.register( node.stdout, POLLIN )
326
        if self.isatty():
327
            # Buffer by character, so that interactive
328
            # commands sort of work
329
            quietRun( 'stty -icanon min 1' )
330
        while True:
331
            try:
332
                bothPoller.poll()
333
                # XXX BL: this doesn't quite do what we want.
334
                if False and self.inputFile:
335
                    key = self.inputFile.read( 1 )
336
                    if key is not '':
337
                        node.write(key)
338
                    else:
339
                        self.inputFile = None
340
                if isReadable( self.inPoller ):
341
                    key = self.stdin.read( 1 )
342
                    node.write( key )
343
                if isReadable( nodePoller ):
344
                    data = node.monitor()
345
                    output( data )
346
                if not node.waiting:
347
                    break
348
            except KeyboardInterrupt:
349
                node.sendInt()
350

    
351
# Helper functions
352

    
353
def isReadable( poller ):
354
    "Check whether a Poll object has a readable fd."
355
    for fdmask in poller.poll( 0 ):
356
        mask = fdmask[ 1 ]
357
        if mask & POLLIN:
358
            return True