Commit 2ed2f600 authored by Martín Ferrari's avatar Martín Ferrari

Change arguments and semantics to better match subprocess.Popen

parent da2705aa
...@@ -437,17 +437,19 @@ class Client(object): ...@@ -437,17 +437,19 @@ class Client(object):
raise raise
self._read_and_check_reply() self._read_and_check_reply()
def spawn(self, executable, argv = None, cwd = None, env = None, def spawn(self, argv, executable = None,
stdin = None, stdout = None, stderr = None, user = None): stdin = None, stdout = None, stderr = None,
cwd = None, env = None, user = None):
"""Start a subprocess in the slave; the interface resembles """Start a subprocess in the slave; the interface resembles
subprocess.Popen, but with less functionality. In particular subprocess.Popen, but with less functionality. In particular
stdin/stdout/stderr can only be None or a open file descriptor. stdin/stdout/stderr can only be None or a open file descriptor.
See netns.subprocess.spawn for details.""" See netns.subprocess.spawn for details."""
if executable == None:
executable = argv[0]
params = ["PROC", "CRTE", _b64(executable)] params = ["PROC", "CRTE", _b64(executable)]
if argv != None: for i in argv:
for i in argv: params.append(_b64(i))
params.append(_b64(i))
self._send_cmd(*params) self._send_cmd(*params)
self._read_and_check_reply() self._read_and_check_reply()
......
...@@ -14,8 +14,9 @@ class Subprocess(object): ...@@ -14,8 +14,9 @@ class Subprocess(object):
interface.""" interface."""
# FIXME # FIXME
default_user = None default_user = None
def __init__(self, node, executable, argv = None, cwd = None, env = None, def __init__(self, node, argv, executable = None,
stdin = None, stdout = None, stderr = None, user = None): stdin = None, stdout = None, stderr = None,
shell = False, cwd = None, env = None, user = None):
self._slave = node._slave self._slave = node._slave
"""Forks and execs a program, with stdio redirection and user """Forks and execs a program, with stdio redirection and user
switching. switching.
...@@ -48,12 +49,16 @@ class Subprocess(object): ...@@ -48,12 +49,16 @@ class Subprocess(object):
if user == None: if user == None:
user = Subprocess.default_user user = Subprocess.default_user
if isinstance(argv, str):
argv = [ argv ]
if shell:
argv = [ '/bin/sh', '-c' ] + argv
# confusingly enough, to go to the function at the top of this file, # confusingly enough, to go to the function at the top of this file,
# I need to call it thru the communications protocol: remember that # I need to call it thru the communications protocol: remember that
# happens in another process! # happens in another process!
self._pid = self._slave.spawn(executable, argv = argv, cwd = cwd, self._pid = self._slave.spawn(argv, executable = executable,
env = env, stdin = stdin, stdout = stdout, stderr = stderr, stdin = stdin, stdout = stdout, stderr = stderr,
user = user) cwd = cwd, env = env, user = user)
node._add_subprocess(self) node._add_subprocess(self)
self._returncode = None self._returncode = None
...@@ -107,9 +112,9 @@ class Popen(Subprocess): ...@@ -107,9 +112,9 @@ class Popen(Subprocess):
"""Higher-level interface for executing processes, that tries to emulate """Higher-level interface for executing processes, that tries to emulate
the stdlib's subprocess.Popen as much as possible.""" the stdlib's subprocess.Popen as much as possible."""
def __init__(self, node, executable, argv = None, cwd = None, env = None, def __init__(self, node, argv, executable = None,
stdin = None, stdout = None, stderr = None, user = None, stdin = None, stdout = None, stderr = None, bufsize = 0,
bufsize = 0): shell = False, cwd = None, env = None, user = None):
"""As in Subprocess, `node' specifies the netns Node to run in. """As in Subprocess, `node' specifies the netns Node to run in.
The `stdin', `stdout', and `stderr' parameters also accept the special The `stdin', `stdout', and `stderr' parameters also accept the special
...@@ -139,9 +144,10 @@ class Popen(Subprocess): ...@@ -139,9 +144,10 @@ class Popen(Subprocess):
if stderr == STDOUT: if stderr == STDOUT:
fdmap['stderr'] = fdmap['stdout'] fdmap['stderr'] = fdmap['stdout']
super(Popen, self).__init__(node, executable, argv = argv, cwd = cwd, super(Popen, self).__init__(node, argv, executable = executable,
env = env, stdin = fdmap['stdin'], stdout = fdmap['stdout'], stdin = fdmap['stdin'], stdout = fdmap['stdout'],
stderr = fdmap['stderr'], user = user) stderr = fdmap['stderr'],
shell = shell, cwd = cwd, env = env, user = user)
# Close pipes, they have been dup()ed to the child # Close pipes, they have been dup()ed to the child
for k, v in fdmap.items(): for k, v in fdmap.items():
...@@ -208,24 +214,21 @@ class Popen(Subprocess): ...@@ -208,24 +214,21 @@ class Popen(Subprocess):
def system(node, args): def system(node, args):
"""Emulates system() function, if `args' is an string, it uses `/bin/sh' to """Emulates system() function, if `args' is an string, it uses `/bin/sh' to
exexecute it, otherwise is interpreted as the argv array to call execve.""" exexecute it, otherwise is interpreted as the argv array to call execve."""
if isinstance(args, str): shell = isinstance(args, str)
args = [ '/bin/sh', '/bin/sh', '-c', args ] return Popen(node, args, shell = shell).wait()
return Popen(node, args[0], args[1:]).wait()
def backticks(node, args): def backticks(node, args):
"""Emulates shell backticks, if `args' is an string, it uses `/bin/sh' to """Emulates shell backticks, if `args' is an string, it uses `/bin/sh' to
exexecute it, otherwise is interpreted as the argv array to call execve.""" exexecute it, otherwise is interpreted as the argv array to call execve."""
if isinstance(args, str): shell = isinstance(args, str)
args = [ '/bin/sh', '/bin/sh', '-c', args ] return Popen(node, args, shell = shell, stdout = PIPE).communicate()[0]
return Popen(node, args[0], args[1:], stdout = PIPE).communicate()[0]
def backticks_raise(node, args): def backticks_raise(node, args):
"""Emulates shell backticks, if `args' is an string, it uses `/bin/sh' to """Emulates shell backticks, if `args' is an string, it uses `/bin/sh' to
exexecute it, otherwise is interpreted as the argv array to call execve. exexecute it, otherwise is interpreted as the argv array to call execve.
Raises an RuntimeError if the return value is not 0.""" Raises an RuntimeError if the return value is not 0."""
if isinstance(args, str): shell = isinstance(args, str)
args = [ '/bin/sh', '/bin/sh', '-c', args ] p = Popen(node, args, shell = shell, stdout = PIPE)
p = Popen(node, args[0], args[1:], stdout = PIPE)
out = p.communicate()[0] out = p.communicate()[0]
ret = p.returncode ret = p.returncode
if ret > 0: if ret > 0:
...@@ -238,8 +241,8 @@ def backticks_raise(node, args): ...@@ -238,8 +241,8 @@ def backticks_raise(node, args):
# #
# Server-side code, called from netns.protocol.Server # Server-side code, called from netns.protocol.Server
def spawn(executable, argv = None, cwd = None, env = None, stdin = None, def spawn(executable, argv = None, cwd = None, env = None, close_fds = False,
stdout = None, stderr = None, close_fds = False, user = None): stdin = None, stdout = None, stderr = None, user = None):
"""Internal function that performs all the dirty work for Subprocess, Popen """Internal function that performs all the dirty work for Subprocess, Popen
and friends. This is executed in the slave process, directly from the and friends. This is executed in the slave process, directly from the
protocol.Server class. protocol.Server class.
......
...@@ -81,7 +81,7 @@ class TestSubprocess(unittest.TestCase): ...@@ -81,7 +81,7 @@ class TestSubprocess(unittest.TestCase):
def test_Subprocess_chuser(self): def test_Subprocess_chuser(self):
node = netns.Node(nonetns = True) node = netns.Node(nonetns = True)
user = 'nobody' user = 'nobody'
p = Subprocess(node, '/bin/sleep', ['/bin/sleep', '1000'], user = user) p = Subprocess(node, ['/bin/sleep', '1000'], user = user)
self._check_ownership(user, p.pid) self._check_ownership(user, p.pid)
p.signal() p.signal()
self.assertEquals(p.wait(), -signal.SIGTERM) self.assertEquals(p.wait(), -signal.SIGTERM)
...@@ -126,9 +126,9 @@ class TestSubprocess(unittest.TestCase): ...@@ -126,9 +126,9 @@ class TestSubprocess(unittest.TestCase):
node = netns.Node(nonetns = True) node = netns.Node(nonetns = True)
# User does not exist # User does not exist
self.assertRaises(RuntimeError, Subprocess, node, self.assertRaises(RuntimeError, Subprocess, node,
'/bin/sleep', ['/bin/sleep', '1000'], user = self.nouser) ['/bin/sleep', '1000'], user = self.nouser)
self.assertRaises(RuntimeError, Subprocess, node, self.assertRaises(RuntimeError, Subprocess, node,
'/bin/sleep', ['/bin/sleep', '1000'], user = self.nouid) ['/bin/sleep', '1000'], user = self.nouid)
# Invalid CWD: it is a file # Invalid CWD: it is a file
self.assertRaises(RuntimeError, Subprocess, node, self.assertRaises(RuntimeError, Subprocess, node,
'/bin/sleep', cwd = '/bin/sleep') '/bin/sleep', cwd = '/bin/sleep')
...@@ -141,15 +141,21 @@ class TestSubprocess(unittest.TestCase): ...@@ -141,15 +141,21 @@ class TestSubprocess(unittest.TestCase):
self.assertRaises(RuntimeError, Subprocess, node, self.assertRaises(RuntimeError, Subprocess, node,
'sleep', env = {'PATH': ''}) 'sleep', env = {'PATH': ''})
# Argv
self.assertRaises(RuntimeError, Subprocess, node, 'true; false')
self.assertEquals(Subprocess(node, 'true').wait(), 0)
self.assertEquals(Subprocess(node, 'true; false', shell = True).wait(),
1)
# Piping # Piping
r, w = os.pipe() r, w = os.pipe()
p = Subprocess(node, 'echo', ['echo', 'hello world'], stdout = w) p = Subprocess(node, ['echo', 'hello world'], stdout = w)
os.close(w) os.close(w)
self.assertEquals(_readall(r), "hello world\n") self.assertEquals(_readall(r), "hello world\n")
os.close(r) os.close(r)
p.wait() p.wait()
p = Subprocess(node, 'sleep', ['sleep', '100']) p = Subprocess(node, ['sleep', '100'])
self.assertTrue(p.pid > 0) self.assertTrue(p.pid > 0)
self.assertEquals(p.poll(), None) # not finished self.assertEquals(p.poll(), None) # not finished
p.signal() p.signal()
...@@ -158,14 +164,14 @@ class TestSubprocess(unittest.TestCase): ...@@ -158,14 +164,14 @@ class TestSubprocess(unittest.TestCase):
self.assertEquals(p.wait(), -signal.SIGTERM) # no-op self.assertEquals(p.wait(), -signal.SIGTERM) # no-op
self.assertEquals(p.poll(), -signal.SIGTERM) # no-op self.assertEquals(p.poll(), -signal.SIGTERM) # no-op
p = Subprocess(node, 'sleep', ['sleep', '100']) p = Subprocess(node, ['sleep', '100'])
os.kill(p.pid, signal.SIGTERM) os.kill(p.pid, signal.SIGTERM)
time.sleep(0.2) time.sleep(0.2)
p.signal() # since it has not been waited for, it should not raise p.signal() # since it has not been waited for, it should not raise
self.assertEquals(p.wait(), -signal.SIGTERM) self.assertEquals(p.wait(), -signal.SIGTERM)
def test_Popen(self): def test_Popen(self):
node = netns.Node(nonetns = True, debug=0) node = netns.Node(nonetns = True, debug = 0)
# repeat test with Popen interface # repeat test with Popen interface
r0, w0 = os.pipe() r0, w0 = os.pipe()
...@@ -204,20 +210,18 @@ class TestSubprocess(unittest.TestCase): ...@@ -204,20 +210,18 @@ class TestSubprocess(unittest.TestCase):
p = Popen(node, 'cat', stdin = PIPE) p = Popen(node, 'cat', stdin = PIPE)
self.assertEquals(p.communicate(), (None, None)) self.assertEquals(p.communicate(), (None, None))
p = Popen(node, '/bin/sh', ['sh', '-c', 'cat >&2'], p = Popen(node, 'cat >&2', shell = True, stdin = PIPE, stderr = PIPE)
stdin = PIPE, stderr = PIPE)
p.stdin.write("hello world\n") p.stdin.write("hello world\n")
p.stdin.close() p.stdin.close()
self.assertEquals(p.stderr.readlines(), ["hello world\n"]) self.assertEquals(p.stderr.readlines(), ["hello world\n"])
self.assertEquals(p.stdout, None) self.assertEquals(p.stdout, None)
self.assertEquals(p.wait(), 0) self.assertEquals(p.wait(), 0)
p = Popen(node, '/bin/sh', ['sh', '-c', 'cat >&2'], p = Popen(node, ['sh', '-c', 'cat >&2'], stdin = PIPE, stderr = PIPE)
stdin = PIPE, stderr = PIPE)
self.assertEquals(p.communicate(_longstring), (None, _longstring)) self.assertEquals(p.communicate(_longstring), (None, _longstring))
# #
p = Popen(node, '/bin/sh', ['sh', '-c', 'cat >&2'], p = Popen(node, ['sh', '-c', 'cat >&2'],
stdin = PIPE, stdout = PIPE, stderr = STDOUT) stdin = PIPE, stdout = PIPE, stderr = STDOUT)
p.stdin.write("hello world\n") p.stdin.write("hello world\n")
p.stdin.close() p.stdin.close()
...@@ -225,12 +229,12 @@ class TestSubprocess(unittest.TestCase): ...@@ -225,12 +229,12 @@ class TestSubprocess(unittest.TestCase):
self.assertEquals(p.stderr, None) self.assertEquals(p.stderr, None)
self.assertEquals(p.wait(), 0) self.assertEquals(p.wait(), 0)
p = Popen(node, '/bin/sh', ['sh', '-c', 'cat >&2'], p = Popen(node, ['sh', '-c', 'cat >&2'],
stdin = PIPE, stdout = PIPE, stderr = STDOUT) stdin = PIPE, stdout = PIPE, stderr = STDOUT)
self.assertEquals(p.communicate(_longstring), (_longstring, None)) self.assertEquals(p.communicate(_longstring), (_longstring, None))
# #
p = Popen(node, 'tee', ['tee', '/dev/stderr'], p = Popen(node, ['tee', '/dev/stderr'],
stdin = PIPE, stdout = PIPE, stderr = STDOUT) stdin = PIPE, stdout = PIPE, stderr = STDOUT)
p.stdin.write("hello world\n") p.stdin.write("hello world\n")
p.stdin.close() p.stdin.close()
...@@ -238,13 +242,13 @@ class TestSubprocess(unittest.TestCase): ...@@ -238,13 +242,13 @@ class TestSubprocess(unittest.TestCase):
self.assertEquals(p.stderr, None) self.assertEquals(p.stderr, None)
self.assertEquals(p.wait(), 0) self.assertEquals(p.wait(), 0)
p = Popen(node, 'tee', ['tee', '/dev/stderr'], p = Popen(node, ['tee', '/dev/stderr'],
stdin = PIPE, stdout = PIPE, stderr = STDOUT) stdin = PIPE, stdout = PIPE, stderr = STDOUT)
self.assertEquals(p.communicate(_longstring[0:512]), self.assertEquals(p.communicate(_longstring[0:512]),
(_longstring[0:512] * 2, None)) (_longstring[0:512] * 2, None))
# #
p = Popen(node, 'tee', ['tee', '/dev/stderr'], p = Popen(node, ['tee', '/dev/stderr'],
stdin = PIPE, stdout = PIPE, stderr = PIPE) stdin = PIPE, stdout = PIPE, stderr = PIPE)
p.stdin.write("hello world\n") p.stdin.write("hello world\n")
p.stdin.close() p.stdin.close()
...@@ -252,16 +256,16 @@ class TestSubprocess(unittest.TestCase): ...@@ -252,16 +256,16 @@ class TestSubprocess(unittest.TestCase):
self.assertEquals(p.stderr.readlines(), ["hello world\n"]) self.assertEquals(p.stderr.readlines(), ["hello world\n"])
self.assertEquals(p.wait(), 0) self.assertEquals(p.wait(), 0)
p = Popen(node, 'tee', ['tee', '/dev/stderr'], p = Popen(node, ['tee', '/dev/stderr'],
stdin = PIPE, stdout = PIPE, stderr = PIPE) stdin = PIPE, stdout = PIPE, stderr = PIPE)
self.assertEquals(p.communicate(_longstring), (_longstring, ) * 2) self.assertEquals(p.communicate(_longstring), (_longstring, ) * 2)
def test_backticks(self): def test_backticks(self):
node = netns.Node(nonetns = True, debug=0) node = netns.Node(nonetns = True, debug = 0)
self.assertEquals(backticks(node, "echo hello world"), "hello world\n") self.assertEquals(backticks(node, "echo hello world"), "hello world\n")
self.assertEquals(backticks(node, r"echo hello\ \ world"), self.assertEquals(backticks(node, r"echo hello\ \ world"),
"hello world\n") "hello world\n")
self.assertEquals(backticks(node, ["echo", "echo", "hello", "world"]), self.assertEquals(backticks(node, ["echo", "hello", "world"]),
"hello world\n") "hello world\n")
self.assertEquals(backticks(node, "echo hello world > /dev/null"), "") self.assertEquals(backticks(node, "echo hello world > /dev/null"), "")
self.assertEquals(backticks_raise(node, "true"), "") self.assertEquals(backticks_raise(node, "true"), "")
...@@ -269,7 +273,7 @@ class TestSubprocess(unittest.TestCase): ...@@ -269,7 +273,7 @@ class TestSubprocess(unittest.TestCase):
self.assertRaises(RuntimeError, backticks_raise, node, "kill $$") self.assertRaises(RuntimeError, backticks_raise, node, "kill $$")
def test_system(self): def test_system(self):
node = netns.Node(nonetns = True, debug=0) node = netns.Node(nonetns = True, debug = 0)
self.assertEquals(system(node, "true"), 0) self.assertEquals(system(node, "true"), 0)
self.assertEquals(system(node, "false"), 1) self.assertEquals(system(node, "false"), 1)
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# vim:ts=4:sw=4:et:ai:sts=4 # vim:ts=4:sw=4:et:ai:sts=4
import re, subprocess, sys import re, subprocess, sys
import netns.subprocess
def process_ipcmd(str): def process_ipcmd(str):
cur = None cur = None
...@@ -55,11 +56,11 @@ def get_devs(): ...@@ -55,11 +56,11 @@ def get_devs():
ipcmd = subprocess.Popen(["ip", "addr", "list"], ipcmd = subprocess.Popen(["ip", "addr", "list"],
stdout = subprocess.PIPE) stdout = subprocess.PIPE)
(outdata, errdata) = ipcmd.communicate() (outdata, errdata) = ipcmd.communicate()
ipcmd.wait()
return process_ipcmd(outdata) return process_ipcmd(outdata)
def get_devs_netns(node): def get_devs_netns(node):
(outdata, errdata) = node.run_process(["ip", "addr", "list"]) (outdata, errdata) = netns.subprocess.backticks_raise(node,
["ip", "addr", "list"])
return process_ipcmd(outdata) return process_ipcmd(outdata)
# Unittest from Python 2.6 doesn't have these decorators # Unittest from Python 2.6 doesn't have these decorators
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment