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:
@@ -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
@@ -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
@@ -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 )
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user