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.
This commit is contained in:
Bob Lantz
2010-03-13 18:23:07 -08:00
parent c4ae423238
commit bcacfc0510
5 changed files with 68 additions and 31 deletions
+2 -1
View File
@@ -19,7 +19,8 @@ codecheck: $(PYSRC)
test: $(MININET) $(TEST)
mininet/test/test_nets.py
install:
install: mnexec
cp mnexec bin/
python setup.py install
+16 -14
View File
@@ -15,10 +15,9 @@ size.
The CLI automatically substitutes IP addresses for node names,
so commands like
mininet> h0 ping -c1 h31
mininet> h2 ping h3
should work correctly and allow host h0 to ping host h31.
Note the '-c1' argument as per the Bugs/limitations section below!
should work correctly and allow host h2 to ping host h3
Several useful commands are provided, including the ability to
list all nodes ('nodes'), to print out the network topology
@@ -27,10 +26,7 @@ and bandwidth ('iperf'.)
Bugs/limitations:
- 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.
- Interactive commands are not supported at the moment
"""
@@ -52,7 +48,16 @@ class CLI( Cmd ):
self.nodemap[ node.name ] = node
Cmd.__init__( self )
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.
# Each CLI function needs the same interface.
@@ -70,13 +75,10 @@ class CLI( Cmd ):
'addresses\n'
'for node names when a node is the first arg, so commands'
' like\n'
' mininet> h0 ping -c1 h1\n'
' mininet> h2 ping h3\n'
'should work.\n'
'\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' )
'Interactive commands are not supported yet.\n' )
if args is "":
self.stdout.write( helpStr )
@@ -176,7 +178,7 @@ class CLI( Cmd ):
for arg in rest ]
rest = ' '.join( rest )
# Run cmd on node:
node.sendCmd( rest )
node.sendCmd( rest, printPid=True )
while True:
try:
done, data = node.monitor()
+34 -15
View File
@@ -49,7 +49,7 @@ from subprocess import Popen, PIPE, STDOUT
from time import sleep
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 ):
"""A virtual network node is simply a shell in a network namespace.
@@ -65,21 +65,20 @@ class Node( object ):
defaultMAC: default MAC address for intf 0
defaultIP: default IP address for intf 0"""
self.name = name
closeFds = False # speed vs. memory use
# 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' ]
opts = '-cdp'
self.inNamespace = 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,
close_fds=closeFds )
close_fds=False )
self.stdin = self.shell.stdin
self.stdout = self.shell.stdout
self.pollOut = select.poll()
self.pollOut.register( self.stdout )
# 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()
self.outToNode[ self.stdout.fileno() ] = self
self.inToNode[ self.stdin.fileno() ] = self
@@ -89,10 +88,17 @@ class Node( object ):
# replace with Port objects, eventually ?
self.ips = {} # dict of interfaces to ip addresses as strings
self.connection = {} # remote node connected to each interface
self.waiting = False
self.execed = False
self.defaultIP = defaultIP
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
def fdToNode( cls, fd ):
@@ -130,7 +136,7 @@ class Node( object ):
"Wait until node's output is readable."
self.pollOut.poll()
def sendCmd( self, cmd ):
def sendCmd( self, cmd, printPid=False ):
"""Send a command, followed by a command to echo a sentinel,
and return without waiting for the command to complete."""
assert not self.waiting
@@ -141,23 +147,36 @@ class Node( object ):
cmd = cmd[ :-1 ]
else:
separator = ';'
if printPid and not isShellBuiltin( cmd ):
cmd = 'mnexec -p ' + cmd
self.write( cmd + separator + ' echo -n "\\0177" \n' )
self.lastCmd = cmd
self.lastPid = None
self.waiting = True
def sendInt( self ):
"""Placeholder for function to interrupt running subprocess.
This is a tricky problem to solve."""
self.write( chr( 3 ) )
"Interrupt running command."
if self.lastPid:
os.kill( self.lastPid, signal.SIGINT )
def monitor( self ):
"Monitor the output of a command, returning (done?, data)."
assert self.waiting
self.waitReadable()
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 ):
self.waiting = False
return True, data[ :-1 ]
elif chr( 127 ) in data:
self.waiting = False
return True, data.replace( chr( 127 ), '' )
return False, data
@@ -336,11 +355,11 @@ class Switch( Node ):
"""A Switch is a Node that is running (or has execed?)
an OpenFlow switch."""
def sendCmd( self, cmd ):
def sendCmd( self, cmd, printCmd=False):
"""Send command to Node.
cmd: string"""
if not self.execed:
return Node.sendCmd( self, cmd )
return Node.sendCmd( self, cmd, printCmd )
else:
error( '*** Error: %s has execed and cannot accept commands' %
self.name )
+15
View File
@@ -178,3 +178,18 @@ def makeNumeric( s ):
return float( s )
else:
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
+1 -1
View File
@@ -72,7 +72,7 @@ int main(int argc, char *argv[])
if (optind < argc) {
execvp(argv[optind], &argv[optind]);
perror("execvp");
perror(argv[optind]);
return 1;
}