Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ 9d0dbe48

History | View | Annotate | Download (10.8 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
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 ):
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
        # Attempt to handle input
50
        self.stdin = stdin
51
        self.inPoller = poll()
52
        self.inPoller.register( stdin )
53
        self.inputFile = None
54
        Cmd.__init__( self )
55
        info( '*** Starting CLI:\n' )
56
        while True:
57
            try:
58
                # Make sure no nodes are still waiting
59
                for node in self.nodelist:
60
                    while node.waiting:
61
                        node.sendInt()
62
                        node.monitor()
63
                if self.isatty():
64
                    quietRun( 'stty sane' )
65
                self.cmdloop()
66
                break
67
            except KeyboardInterrupt:
68
                output( '\nInterrupt\n' )
69

    
70
    def emptyline( self ):
71
        "Don't repeat last command when you hit return."
72
        pass
73

    
74
    # Disable pylint "Unused argument: 'arg's'" messages, as well as
75
    # "method could be a function" warning, since each CLI function
76
    # must have the same interface
77
    # pylint: disable-msg=W0613,R0201
78

    
79
    helpStr = (
80
        'You may also send a command to a node using:\n'
81
        '  <node> command {args}\n'
82
        'For example:\n'
83
        '  mininet> h1 ifconfig\n'
84
        '\n'
85
        'The interpreter automatically substitutes IP addresses\n'
86
        'for node names when a node is the first arg, so commands\n'
87
        'like\n'
88
        '  mininet> h2 ping h3\n'
89
        'should work.\n'
90
        '\n'
91
        'Some character-oriented interactive commands require\n'
92
        'noecho:\n'
93
        '  mininet> noecho h2 vi foo.py\n'
94
        'However, starting up an xterm/gterm is generally better:\n'
95
        '  mininet> xterm h2\n\n'
96
    )
97

    
98
    def do_help( self, line ):
99
        "Describe available CLI commands."
100
        Cmd.do_help( self, line )
101
        if line is '':
102
            output( self.helpStr )
103

    
104
    def do_nodes( self, line ):
105
        "List all nodes."
106
        nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
107
        output( 'available nodes are: \n%s\n' % nodes )
108

    
109
    def do_net( self, line ):
110
        "List network connections."
111
        for switch in self.mn.switches:
112
            output( switch.name, '<->' )
113
            for intf in switch.intfs.values():
114
                # Ugly, but pylint wants it
115
                name = switch.connection.get( intf,
116
                    ( None, 'Unknown ' ) )[ 1 ]
117
                output( ' %s' % name )
118
            output( '\n' )
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.nodemap )
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( sorted( node.intfs.values() ) ) ) )
196

    
197
    def do_dump( self, line ):
198
        "Dump node info."
199
        for node in self.nodelist:
200
            output( '%s\n' % 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
            pass
270
        self.inputFile = None
271

    
272
    def default( self, line ):
273
        """Called on an input line when the command prefix is not recognized.
274
        Overridden to run shell commands when a node is the first CLI argument.
275
        Past the first CLI argument, node names are automatically replaced with
276
        corresponding IP addrs."""
277

    
278
        first, args, line = self.parseline( line )
279
        if args and len(args) > 0 and args[ -1 ] == '\n':
280
            args = args[ :-1 ]
281
        rest = args.split( ' ' )
282

    
283
        if first in self.nodemap:
284
            node = self.nodemap[ first ]
285
            # Substitute IP addresses for node names in command
286
            rest = [ self.nodemap[ arg ].IP()
287
                    if arg in self.nodemap else arg
288
                    for arg in rest ]
289
            rest = ' '.join( rest )
290
            # Run cmd on node:
291
            builtin = isShellBuiltin( first )
292
            node.sendCmd( rest, printPid=( not builtin ) )
293
            self.waitForNode( node )
294
        else:
295
            error( '*** Unknown command: %s\n' % first )
296
        
297

    
298
    # pylint: enable-msg=W0613,R0201
299

    
300
    def waitForNode( self, node ):
301
        "Wait for a node to finish, and  print its output."
302
        # Pollers
303
        nodePoller = poll()
304
        nodePoller.register( node.stdout )
305
        bothPoller = poll()
306
        bothPoller.register( self.stdin )
307
        bothPoller.register( node.stdout )
308
        if self.isatty():
309
            # Buffer by character, so that interactive
310
            # commands sort of work
311
            quietRun( 'stty -icanon min 1' )
312
        while True:
313
            try:
314
                bothPoller.poll()
315
                if self.inputFile:
316
                    key = self.inputFile.read( 1 )
317
                    if key is not '':
318
                        node.write(key)
319
                    else:
320
                        self.inputFile = None
321
                if isReadable( self.inPoller ):
322
                    key = self.stdin.read( 1 )
323
                    node.write( key )
324
                if isReadable( nodePoller ):
325
                    data = node.monitor()
326
                    output( data )
327
                if not node.waiting:
328
                    break
329
            except KeyboardInterrupt:
330
                node.sendInt()
331

    
332
# Helper functions
333

    
334
def isReadable( poller ):
335
    "Check whether a Poll object has a readable fd."
336
    for fdmask in poller.poll( 0 ):
337
        mask = fdmask[ 1 ]
338
        if mask & POLLIN:
339
            return True