Tweaks for better interactive command handling.
This commit is contained in:
+86
-37
@@ -32,32 +32,45 @@ Bugs/limitations:
|
|||||||
|
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
from cmd import Cmd
|
from cmd import Cmd
|
||||||
|
from os import isatty
|
||||||
|
from select import poll, select, POLLIN
|
||||||
|
from sys import stdin
|
||||||
|
from tty import setcbreak
|
||||||
|
|
||||||
from mininet.log import info, output, error
|
from mininet.log import info, output, error
|
||||||
from mininet.term import makeTerms
|
from mininet.term import makeTerms
|
||||||
|
from mininet.util import quietRun, isShellBuiltin
|
||||||
|
|
||||||
class CLI( Cmd ):
|
class CLI( Cmd ):
|
||||||
"Simple command-line interface to talk to nodes."
|
"Simple command-line interface to talk to nodes."
|
||||||
|
|
||||||
prompt = 'mininet> '
|
prompt = 'mininet> '
|
||||||
|
|
||||||
def __init__( self, mininet ):
|
def __init__( self, mininet, stdin=stdin ):
|
||||||
self.mn = mininet
|
self.mn = mininet
|
||||||
self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
|
self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
|
||||||
self.nodemap = {} # map names to Node objects
|
self.nodemap = {} # map names to Node objects
|
||||||
for node in self.nodelist:
|
for node in self.nodelist:
|
||||||
self.nodemap[ node.name ] = node
|
self.nodemap[ node.name ] = node
|
||||||
|
# Attempt to handle input
|
||||||
|
self.stdin = stdin
|
||||||
|
self.inPoller = poll()
|
||||||
|
self.inPoller.register( stdin )
|
||||||
Cmd.__init__( self )
|
Cmd.__init__( self )
|
||||||
info( '*** Starting CLI:\n' )
|
info( '*** Starting CLI:\n' )
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
# Make sure no nodes are still waiting
|
||||||
|
for node in self.nodelist:
|
||||||
|
while node.waiting:
|
||||||
|
node.sendInt()
|
||||||
|
node.monitor()
|
||||||
|
if self.isatty():
|
||||||
|
quietRun( 'stty sane' )
|
||||||
self.cmdloop()
|
self.cmdloop()
|
||||||
break
|
break
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
info( 'Interrupt\n' )
|
output( '\nInterrupt\n' )
|
||||||
for node in self.nodelist:
|
|
||||||
waitForNode( node )
|
|
||||||
|
|
||||||
def emptyline( self ):
|
def emptyline( self ):
|
||||||
"Don't repeat last command when you hit return."
|
"Don't repeat last command when you hit return."
|
||||||
@@ -71,21 +84,25 @@ class CLI( Cmd ):
|
|||||||
def do_help( self, args ):
|
def do_help( self, args ):
|
||||||
"Describe available CLI commands."
|
"Describe available CLI commands."
|
||||||
Cmd.do_help( self, args )
|
Cmd.do_help( self, args )
|
||||||
helpStr = ( 'You may also send a command to a node using:\n'
|
helpStr = (
|
||||||
' <node> command {args}\n'
|
'You may also send a command to a node using:\n'
|
||||||
'For example:\n'
|
' <node> command {args}\n'
|
||||||
' mininet> h0 ifconfig\n'
|
'For example:\n'
|
||||||
'\n'
|
' mininet> h1 ifconfig\n'
|
||||||
'The interpreter automatically substitutes IP '
|
'\n'
|
||||||
'addresses\n'
|
'The interpreter automatically substitutes IP addresses\n'
|
||||||
'for node names when a node is the first arg, so commands'
|
'for node names when a node is the first arg, so commands\n'
|
||||||
' like\n'
|
'like\n'
|
||||||
' mininet> h2 ping h3\n'
|
' mininet> h2 ping h3\n'
|
||||||
'should work.\n'
|
'should work.\n'
|
||||||
'\n'
|
'\n'
|
||||||
'Interactive commands that require user input are '
|
'Some character-oriented interactive commands require\n'
|
||||||
'not (yet) supported.\n\n' )
|
'noecho, e.g.\n'
|
||||||
if args is "":
|
' mininet> noecho h2 vi foo.py\n'
|
||||||
|
'However, starting up an xterm/gterm is generally better:\n'
|
||||||
|
' mininet> xterm h2\n\n'
|
||||||
|
)
|
||||||
|
if args is '':
|
||||||
self.stdout.write( helpStr )
|
self.stdout.write( helpStr )
|
||||||
|
|
||||||
def do_nodes( self, args ):
|
def do_nodes( self, args ):
|
||||||
@@ -117,11 +134,11 @@ class CLI( Cmd ):
|
|||||||
if not result:
|
if not result:
|
||||||
return
|
return
|
||||||
elif isinstance( result, str ):
|
elif isinstance( result, str ):
|
||||||
info( result + '\n' )
|
output( result + '\n' )
|
||||||
else:
|
else:
|
||||||
info( repr( result ) + '\n' )
|
output( repr( result ) + '\n' )
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
info( str( e ) + '\n' )
|
output( str( e ) + '\n' )
|
||||||
|
|
||||||
# pylint: enable-msg=W0703
|
# pylint: enable-msg=W0703
|
||||||
|
|
||||||
@@ -167,7 +184,7 @@ class CLI( Cmd ):
|
|||||||
"Spawn xterm(s) for the given node(s)."
|
"Spawn xterm(s) for the given node(s)."
|
||||||
args = args.split()
|
args = args.split()
|
||||||
if not args:
|
if not args:
|
||||||
info( 'usage: %s node1 node2 ...\n' % term )
|
error( 'usage: %s node1 node2 ...\n' % term )
|
||||||
else:
|
else:
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if arg not in self.nodemap:
|
if arg not in self.nodemap:
|
||||||
@@ -190,8 +207,20 @@ class CLI( Cmd ):
|
|||||||
|
|
||||||
def do_EOF( self, args ):
|
def do_EOF( self, args ):
|
||||||
"Exit"
|
"Exit"
|
||||||
|
output( '\n' )
|
||||||
return self.do_exit( args )
|
return self.do_exit( args )
|
||||||
|
|
||||||
|
def isatty( self ):
|
||||||
|
"Is our standard input a tty?"
|
||||||
|
return isatty( self.stdin.fileno() )
|
||||||
|
|
||||||
|
def do_noecho( self, line ):
|
||||||
|
"Run an interactive command with echoing turned off."
|
||||||
|
if self.isatty():
|
||||||
|
quietRun( 'stty -echo' )
|
||||||
|
self.default( line )
|
||||||
|
# default() fixes tty
|
||||||
|
|
||||||
def default( self, line ):
|
def default( self, line ):
|
||||||
"""Called on an input line when the command prefix is not recognized.
|
"""Called on an input line when the command prefix is not recognized.
|
||||||
Overridden to run shell commands when a node is the first CLI argument.
|
Overridden to run shell commands when a node is the first CLI argument.
|
||||||
@@ -211,22 +240,42 @@ class CLI( Cmd ):
|
|||||||
for arg in rest ]
|
for arg in rest ]
|
||||||
rest = ' '.join( rest )
|
rest = ' '.join( rest )
|
||||||
# Run cmd on node:
|
# Run cmd on node:
|
||||||
node.sendCmd( rest, printPid=True )
|
node.sendCmd( rest )
|
||||||
waitForNode( node )
|
self.waitForNode( node, isShellBuiltin( first ) )
|
||||||
else:
|
else:
|
||||||
self.stdout.write( '*** Unknown syntax: %s\n' % line )
|
self.stdout.write( '*** Unknown command: %s\n' % first )
|
||||||
|
|
||||||
# pylint: enable-msg=W0613,R0201
|
# pylint: enable-msg=W0613,R0201
|
||||||
|
|
||||||
|
def isReadable( self, poller ):
|
||||||
|
"Check whether a single polled object is readable."
|
||||||
|
for fd, mask in poller.poll( 0 ):
|
||||||
|
if mask & POLLIN:
|
||||||
|
return True
|
||||||
|
|
||||||
# This function may be a candidate for util.py
|
def waitForNode( self, node, isShellBuiltin=False ):
|
||||||
|
"Wait for a node to finish, and print its output."
|
||||||
def waitForNode( node ):
|
# Pollers
|
||||||
"Wait for a node to finish, and print its output."
|
nodePoller = poll()
|
||||||
while node.waiting:
|
nodePoller.register( node.stdout )
|
||||||
try:
|
bothPoller = poll()
|
||||||
data = node.monitor()
|
bothPoller.register( self.stdin )
|
||||||
info( '%s' % data )
|
bothPoller.register( node.stdout )
|
||||||
except KeyboardInterrupt:
|
if self.isatty():
|
||||||
node.sendInt()
|
# Buffer by character, so that interactive
|
||||||
|
# commands sort of work
|
||||||
|
quietRun( 'stty -icanon min 1' )
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
bothPoller.poll()
|
||||||
|
if self.isReadable( self.inPoller ):
|
||||||
|
key = self.stdin.read( 1 )
|
||||||
|
node.write( key )
|
||||||
|
if self.isReadable( nodePoller ):
|
||||||
|
data = node.monitor()
|
||||||
|
output( '%s' % data )
|
||||||
|
if not node.waiting:
|
||||||
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
node.sendInt()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user