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)
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+16
-14
@@ -15,10 +15,9 @@ size.
|
|||||||
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 @@ and bandwidth ('iperf'.)
|
|||||||
|
|
||||||
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 @@ class CLI( Cmd ):
|
|||||||
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 @@ class CLI( Cmd ):
|
|||||||
'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 @@ 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 )
|
node.sendCmd( rest, printPid=True )
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
done, data = node.monitor()
|
done, data = node.monitor()
|
||||||
|
|||||||
+34
-15
@@ -49,7 +49,7 @@ from subprocess import Popen, PIPE, STDOUT
|
|||||||
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 @@ class Node( object ):
|
|||||||
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 @@ class Node( object ):
|
|||||||
# 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 @@ class Node( object ):
|
|||||||
"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 @@ class Node( object ):
|
|||||||
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 ):
|
|||||||
"""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 ):
|
|||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user