Statistics
| Branch: | Tag: | Revision:

mininet / mininet / cli.py @ c26875cb

History | View | Annotate | Download (8.89 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 hosts."
152
        self.mn.iperf()
153

    
154
    def do_iperfudp( self, line ):
155
        "Simple iperf UDP test between two hosts."
156
        args = line.split()
157
        udpBw = args[ 0 ] if len( args ) else '10M'
158
        self.mn.iperfUdp( udpBw )
159

    
160
    def do_intfs( self, line ):
161
        "List interfaces."
162
        for node in self.nodelist:
163
            output( '%s: %s\n' %
164
                ( node.name, ' '.join( sorted( node.intfs.values() ) ) ) )
165

    
166
    def do_dump( self, line ):
167
        "Dump node info."
168
        for node in self.nodelist:
169
            output( '%s\n' % node )
170

    
171
    def do_link( self, line ):
172
        "Bring link(s) between two nodes up or down."
173
        args = line.split()
174
        if len(args) != 3:
175
            error( 'invalid number of args: link end1 end2 [up down]\n' )
176
        elif args[ 2 ] not in [ 'up', 'down' ]:
177
            error( 'invalid type: link end1 end2 [up down]\n' )
178
        else:
179
            self.mn.configLinkStatus( *args )
180

    
181
    def do_xterm( self, line, term='xterm' ):
182
        "Spawn xterm(s) for the given node(s)."
183
        args = line.split()
184
        if not args:
185
            error( 'usage: %s node1 node2 ...\n' % term )
186
        else:
187
            for arg in args:
188
                if arg not in self.nodemap:
189
                    error( "node '%s' not in network\n" % arg )
190
                else:
191
                    node = self.nodemap[ arg ]
192
                    self.mn.terms += makeTerms( [ node ], term = term )
193

    
194
    def do_gterm( self, line ):
195
        "Spawn gnome-terminal(s) for the given node(s)."
196
        self.do_xterm( line, term='gterm' )
197

    
198
    def do_exit( self, line ):
199
        "Exit"
200
        return 'exited by user command'
201

    
202
    def do_quit( self, line ):
203
        "Exit"
204
        return self.do_exit( line )
205

    
206
    def do_EOF( self, line ):
207
        "Exit"
208
        output( '\n' )
209
        return self.do_exit( line )
210

    
211
    def isatty( self ):
212
        "Is our standard input a tty?"
213
        return isatty( self.stdin.fileno() )
214

    
215
    def do_noecho( self, line ):
216
        "Run an interactive command with echoing turned off."
217
        if self.isatty():
218
            quietRun( 'stty -echo' )
219
        self.default( line )
220
        # default() fixes tty
221

    
222
    def default( self, line ):
223
        """Called on an input line when the command prefix is not recognized.
224
        Overridden to run shell commands when a node is the first CLI argument.
225
        Past the first CLI argument, node names are automatically replaced with
226
        corresponding IP addrs."""
227

    
228
        first, args, line = self.parseline( line )
229
        if len(args) > 0 and args[ -1 ] == '\n':
230
            args = args[ :-1 ]
231
        rest = args.split( ' ' )
232

    
233
        if first in self.nodemap:
234
            node = self.nodemap[ first ]
235
            # Substitute IP addresses for node names in command
236
            rest = [ self.nodemap[ arg ].IP()
237
                    if arg in self.nodemap else arg
238
                    for arg in rest ]
239
            rest = ' '.join( rest )
240
            # Run cmd on node:
241
            builtin = isShellBuiltin( first )
242
            node.sendCmd( rest, printPid=( not builtin ) )
243
            self.waitForNode( node )
244
        else:
245
            error( '*** Unknown command: %s\n' % first )
246

    
247
    # pylint: enable-msg=W0613,R0201
248

    
249
    def waitForNode( self, node ):
250
        "Wait for a node to finish, and  print its output."
251
        # Pollers
252
        nodePoller = poll()
253
        nodePoller.register( node.stdout )
254
        bothPoller = poll()
255
        bothPoller.register( self.stdin )
256
        bothPoller.register( node.stdout )
257
        if self.isatty():
258
            # Buffer by character, so that interactive
259
            # commands sort of work
260
            quietRun( 'stty -icanon min 1' )
261
        while True:
262
            try:
263
                bothPoller.poll()
264
                if isReadable( self.inPoller ):
265
                    key = self.stdin.read( 1 )
266
                    node.write( key )
267
                if isReadable( nodePoller ):
268
                    data = node.monitor()
269
                    output( data )
270
                if not node.waiting:
271
                    break
272
            except KeyboardInterrupt:
273
                node.sendInt()
274

    
275
# Helper functions
276

    
277
def isReadable( poller ):
278
    "Check whether a Poll object has a readable fd."
279
    for fdmask in poller.poll( 0 ):
280
        mask = fdmask[ 1 ]
281
        if mask & POLLIN:
282
            return True