Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ 57aae3e1

History | View | Annotate | Download (9.98 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
        Cmd.__init__( self )
54
        info( '*** Starting CLI:\n' )
55
        while True:
56
            try:
57
                # Make sure no nodes are still waiting
58
                for node in self.nodelist:
59
                    while node.waiting:
60
                        node.sendInt()
61
                        node.monitor()
62
                if self.isatty():
63
                    quietRun( 'stty sane' )
64
                self.cmdloop()
65
                break
66
            except KeyboardInterrupt:
67
                output( '\nInterrupt\n' )
68

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

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

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

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

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

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

    
196
    def do_dump( self, line ):
197
        "Dump node info."
198
        for node in self.nodelist:
199
            output( '%s\n' % 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
        # default() fixes tty
251

    
252
    def default( self, line ):
253
        """Called on an input line when the command prefix is not recognized.
254
        Overridden to run shell commands when a node is the first CLI argument.
255
        Past the first CLI argument, node names are automatically replaced with
256
        corresponding IP addrs."""
257

    
258
        first, args, line = self.parseline( line )
259
        if len(args) > 0 and args[ -1 ] == '\n':
260
            args = args[ :-1 ]
261
        rest = args.split( ' ' )
262

    
263
        if first in self.nodemap:
264
            node = self.nodemap[ first ]
265
            # Substitute IP addresses for node names in command
266
            rest = [ self.nodemap[ arg ].IP()
267
                    if arg in self.nodemap else arg
268
                    for arg in rest ]
269
            rest = ' '.join( rest )
270
            # Run cmd on node:
271
            builtin = isShellBuiltin( first )
272
            node.sendCmd( rest, printPid=( not builtin ) )
273
            self.waitForNode( node )
274
        else:
275
            error( '*** Unknown command: %s\n' % first )
276

    
277
    # pylint: enable-msg=W0613,R0201
278

    
279
    def waitForNode( self, node ):
280
        "Wait for a node to finish, and  print its output."
281
        # Pollers
282
        nodePoller = poll()
283
        nodePoller.register( node.stdout )
284
        bothPoller = poll()
285
        bothPoller.register( self.stdin )
286
        bothPoller.register( node.stdout )
287
        if self.isatty():
288
            # Buffer by character, so that interactive
289
            # commands sort of work
290
            quietRun( 'stty -icanon min 1' )
291
        while True:
292
            try:
293
                bothPoller.poll()
294
                if isReadable( self.inPoller ):
295
                    key = self.stdin.read( 1 )
296
                    node.write( key )
297
                if isReadable( nodePoller ):
298
                    data = node.monitor()
299
                    output( data )
300
                if not node.waiting:
301
                    break
302
            except KeyboardInterrupt:
303
                node.sendInt()
304

    
305
# Helper functions
306

    
307
def isReadable( poller ):
308
    "Check whether a Poll object has a readable fd."
309
    for fdmask in poller.poll( 0 ):
310
        mask = fdmask[ 1 ]
311
        if mask & POLLIN:
312
            return True