Skip to content
Snippets Groups Projects
Commit bcacfc05 authored by Bob Lantz's avatar Bob Lantz
Browse files

Support for control-C. Finally.

I've changed the way things work a bit:

1. netns is replaced by mnexec, a general-purpose mininet helper.

2. For interactive commands, we now use mnexec -p, which prints out
   the pid, so we can kill it when someone hits control-C!

3. We close file descriptors for subshells. This might save memory,
   but who knows.

4. We detach our subshells from the tty using mnexec -s; thus
   control-C should not terminate everything.

5. Given 4, mn -c is now necessary if you kill mininet.
parent c4ae4232
No related branches found
No related tags found
No related merge requests found
...@@ -19,7 +19,8 @@ codecheck: $(PYSRC) ...@@ -19,7 +19,8 @@ codecheck: $(PYSRC)
test: $(MININET) $(TEST) test: $(MININET) $(TEST)
mininet/test/test_nets.py mininet/test/test_nets.py
install: install: mnexec
cp mnexec bin/
python setup.py install python setup.py install
......
...@@ -15,10 +15,9 @@ ...@@ -15,10 +15,9 @@
The CLI automatically substitutes IP addresses for node names, The CLI automatically substitutes IP addresses for node names,
so commands like so commands like
mininet> h0 ping -c1 h31 mininet> h2 ping h3
should work correctly and allow host h0 to ping host h31. should work correctly and allow host h2 to ping host h3
Note the '-c1' argument as per the Bugs/limitations section below!
Several useful commands are provided, including the ability to Several useful commands are provided, including the ability to
list all nodes ('nodes'), to print out the network topology list all nodes ('nodes'), to print out the network topology
...@@ -27,10 +26,7 @@ ...@@ -27,10 +26,7 @@
Bugs/limitations: Bugs/limitations:
- Interactive commands are not supported at the moment; - Interactive commands are not supported at the moment
notably, if you type 'ping h1', you can't interrupt it.
For now, we recommend limiting CLI use to non-interactive
commands which terminate in a reasonable amount of time.
""" """
...@@ -52,7 +48,16 @@ def __init__( self, mininet ): ...@@ -52,7 +48,16 @@ def __init__( self, mininet ):
self.nodemap[ node.name ] = node self.nodemap[ node.name ] = node
Cmd.__init__( self ) Cmd.__init__( self )
info( '*** Starting CLI:\n' ) info( '*** Starting CLI:\n' )
self.cmdloop() while True:
try:
self.cmdloop()
break
except KeyboardInterrupt:
info( 'Interrupt\n' )
def emptyline( self ):
"Don't repeat last command when you hit return."
pass
# Disable pylint "Unused argument: 'arg's'" messages. # Disable pylint "Unused argument: 'arg's'" messages.
# Each CLI function needs the same interface. # Each CLI function needs the same interface.
...@@ -70,13 +75,10 @@ def do_help( self, args ): ...@@ -70,13 +75,10 @@ def do_help( self, args ):
'addresses\n' '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'
' like\n' ' like\n'
' mininet> h0 ping -c1 h1\n' ' mininet> h2 ping h3\n'
'should work.\n' 'should work.\n'
'\n' '\n'
'Interactive commands are not supported yet,\n' 'Interactive commands are not supported yet.\n' )
'so please limit commands to ones that do not\n'
'require user interaction and will terminate\n'
'after a reasonable amount of time.\n' )
if args is "": if args is "":
self.stdout.write( helpStr ) self.stdout.write( helpStr )
...@@ -176,7 +178,7 @@ def default( self, line ): ...@@ -176,7 +178,7 @@ def default( self, line ):
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 ) node.sendCmd( rest, printPid=True )
while True: while True:
try: try:
done, data = node.monitor() done, data = node.monitor()
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
from time import sleep from time import sleep
from mininet.log import info, error, debug from mininet.log import info, error, debug
from mininet.util import quietRun, makeIntfPair, moveIntf from mininet.util import quietRun, makeIntfPair, moveIntf, isShellBuiltin
class Node( object ): class Node( object ):
"""A virtual network node is simply a shell in a network namespace. """A virtual network node is simply a shell in a network namespace.
...@@ -65,21 +65,20 @@ def __init__( self, name, inNamespace=True, ...@@ -65,21 +65,20 @@ def __init__( self, name, inNamespace=True,
defaultMAC: default MAC address for intf 0 defaultMAC: default MAC address for intf 0
defaultIP: default IP address for intf 0""" defaultIP: default IP address for intf 0"""
self.name = name self.name = name
closeFds = False # speed vs. memory use opts = '-cdp'
# setsid is necessary to detach from tty
# xpg_echo is needed so we can echo our sentinel in sendCmd
cmd = [ '/usr/bin/setsid', '/bin/bash', '-O', 'xpg_echo' ]
self.inNamespace = inNamespace self.inNamespace = inNamespace
if self.inNamespace: if self.inNamespace:
cmd = [ 'netns' ] + cmd opts += '-n'
# xpg_echo is needed so we can echo our sentinel in sendCmd
cmd = [ 'mnexec', opts, 'bash', '-O', 'xpg_echo', '-m' ]
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
close_fds=closeFds ) close_fds=False )
self.stdin = self.shell.stdin self.stdin = self.shell.stdin
self.stdout = self.shell.stdout self.stdout = self.shell.stdout
self.pollOut = select.poll() self.pollOut = select.poll()
self.pollOut.register( self.stdout ) self.pollOut.register( self.stdout )
# Maintain mapping between file descriptors and nodes # Maintain mapping between file descriptors and nodes
# This could be useful for monitoring multiple nodes # This is useful for monitoring multiple nodes
# using select.poll() # using select.poll()
self.outToNode[ self.stdout.fileno() ] = self self.outToNode[ self.stdout.fileno() ] = self
self.inToNode[ self.stdin.fileno() ] = self self.inToNode[ self.stdin.fileno() ] = self
...@@ -89,10 +88,17 @@ def __init__( self, name, inNamespace=True, ...@@ -89,10 +88,17 @@ def __init__( self, name, inNamespace=True,
# replace with Port objects, eventually ? # replace with Port objects, eventually ?
self.ips = {} # dict of interfaces to ip addresses as strings self.ips = {} # dict of interfaces to ip addresses as strings
self.connection = {} # remote node connected to each interface self.connection = {} # remote node connected to each interface
self.waiting = False
self.execed = False self.execed = False
self.defaultIP = defaultIP self.defaultIP = defaultIP
self.defaultMAC = defaultMAC self.defaultMAC = defaultMAC
self.lastCmd = None
self.lastPid = None
# Grab PID
self.waiting = True
while self.lastPid is None:
self.monitor()
self.pid = self.lastPid
self.waiting = False
@classmethod @classmethod
def fdToNode( cls, fd ): def fdToNode( cls, fd ):
...@@ -130,7 +136,7 @@ def waitReadable( self ): ...@@ -130,7 +136,7 @@ def waitReadable( self ):
"Wait until node's output is readable." "Wait until node's output is readable."
self.pollOut.poll() self.pollOut.poll()
def sendCmd( self, cmd ): def sendCmd( self, cmd, printPid=False ):
"""Send a command, followed by a command to echo a sentinel, """Send a command, followed by a command to echo a sentinel,
and return without waiting for the command to complete.""" and return without waiting for the command to complete."""
assert not self.waiting assert not self.waiting
...@@ -141,23 +147,36 @@ def sendCmd( self, cmd ): ...@@ -141,23 +147,36 @@ def sendCmd( self, cmd ):
cmd = cmd[ :-1 ] cmd = cmd[ :-1 ]
else: else:
separator = ';' separator = ';'
if printPid and not isShellBuiltin( cmd ):
cmd = 'mnexec -p ' + cmd
self.write( cmd + separator + ' echo -n "\\0177" \n' ) self.write( cmd + separator + ' echo -n "\\0177" \n' )
self.lastCmd = cmd
self.lastPid = None
self.waiting = True self.waiting = True
def sendInt( self ): def sendInt( self ):
"""Placeholder for function to interrupt running subprocess. "Interrupt running command."
This is a tricky problem to solve.""" if self.lastPid:
self.write( chr( 3 ) ) os.kill( self.lastPid, signal.SIGINT )
def monitor( self ): def monitor( self ):
"Monitor the output of a command, returning (done?, data)." "Monitor the output of a command, returning (done?, data)."
assert self.waiting assert self.waiting
self.waitReadable() self.waitReadable()
data = self.read( 1024 ) data = self.read( 1024 )
# Look for PID
marker = chr( 1 ) + r'\d+\n'
if chr( 1 ) in data:
markers = re.findall( marker, data )
if markers:
self.lastPid = int( markers[ 0 ][ 1: ] )
data = re.sub( marker, '', data )
# Look for sentinel/EOF
if len( data ) > 0 and data[ -1 ] == chr( 127 ): if len( data ) > 0 and data[ -1 ] == chr( 127 ):
self.waiting = False self.waiting = False
return True, data[ :-1 ] return True, data[ :-1 ]
elif chr( 127 ) in data: elif chr( 127 ) in data:
self.waiting = False
return True, data.replace( chr( 127 ), '' ) return True, data.replace( chr( 127 ), '' )
return False, data return False, data
...@@ -336,11 +355,11 @@ class Switch( Node ): ...@@ -336,11 +355,11 @@ class Switch( Node ):
"""A Switch is a Node that is running (or has execed?) """A Switch is a Node that is running (or has execed?)
an OpenFlow switch.""" an OpenFlow switch."""
def sendCmd( self, cmd ): def sendCmd( self, cmd, printCmd=False):
"""Send command to Node. """Send command to Node.
cmd: string""" cmd: string"""
if not self.execed: if not self.execed:
return Node.sendCmd( self, cmd ) return Node.sendCmd( self, cmd, printCmd )
else: else:
error( '*** Error: %s has execed and cannot accept commands' % error( '*** Error: %s has execed and cannot accept commands' %
self.name ) self.name )
......
...@@ -178,3 +178,18 @@ def makeNumeric( s ): ...@@ -178,3 +178,18 @@ def makeNumeric( s ):
return float( s ) return float( s )
else: else:
return s return s
# pylint: disable-msg=E1101,W0612
def isShellBuiltin( cmd ):
"Return True if cmd is a bash builtin."
if isShellBuiltin.builtIns is None:
isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
space = cmd.find( ' ' )
if space > 0:
cmd = cmd[ :space]
return cmd in isShellBuiltin.builtIns
isShellBuiltin.builtIns = None
# pylint: enable-msg=E1101,W0612
...@@ -72,7 +72,7 @@ int main(int argc, char *argv[]) ...@@ -72,7 +72,7 @@ int main(int argc, char *argv[])
if (optind < argc) { if (optind < argc) {
execvp(argv[optind], &argv[optind]); execvp(argv[optind], &argv[optind]);
perror("execvp"); perror(argv[optind]);
return 1; return 1;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment