Index: client/tests/kvm/kvm_subprocess.py |
diff --git a/client/tests/kvm/kvm_subprocess.py b/client/tests/kvm/kvm_subprocess.py |
index c3e2dd7ecda07aa1892ab7d313412b54d6a5a8ea..8321bb3de778f07e33aac5f3f91e63ef2583a257 100755 |
--- a/client/tests/kvm/kvm_subprocess.py |
+++ b/client/tests/kvm/kvm_subprocess.py |
@@ -189,88 +189,6 @@ import subprocess, time, signal, re, threading, logging |
import common, kvm_utils |
-class ExpectError(Exception): |
- def __init__(self, patterns, output): |
- Exception.__init__(self, patterns, output) |
- self.patterns = patterns |
- self.output = output |
- |
- def _pattern_str(self): |
- if len(self.patterns) == 1: |
- return "pattern %r" % self.patterns[0] |
- else: |
- return "patterns %r" % self.patterns |
- |
- def __str__(self): |
- return ("Unknown error occurred while looking for %s (output: %r)" % |
- (self._pattern_str(), self.output)) |
- |
- |
-class ExpectTimeoutError(ExpectError): |
- def __str__(self): |
- return ("Timeout expired while looking for %s (output: %r)" % |
- (self._pattern_str(), self.output)) |
- |
- |
-class ExpectProcessTerminatedError(ExpectError): |
- def __init__(self, patterns, status, output): |
- ExpectError.__init__(self, patterns, output) |
- self.status = status |
- |
- def __str__(self): |
- return ("Process terminated while looking for %s (status: %s, output: " |
- "%r)" % (self._pattern_str(), self.status, self.output)) |
- |
- |
-class ShellError(Exception): |
- def __init__(self, cmd, output): |
- Exception.__init__(self, cmd, output) |
- self.cmd = cmd |
- self.output = output |
- |
- def __str__(self): |
- return ("Could not execute shell command %r (output: %r)" % |
- (self.cmd, self.output)) |
- |
- |
-class ShellTimeoutError(ShellError): |
- def __str__(self): |
- return ("Timeout expired while waiting for shell command %r to " |
- "complete (output: %r)" % (self.cmd, self.output)) |
- |
- |
-class ShellProcessTerminatedError(ShellError): |
- # Raised when the shell process itself (e.g. ssh, netcat, telnet) |
- # terminates unexpectedly |
- def __init__(self, cmd, status, output): |
- ShellError.__init__(self, cmd, output) |
- self.status = status |
- |
- def __str__(self): |
- return ("Shell process terminated while waiting for command %r to " |
- "complete (status: %s, output: %r)" % |
- (self.cmd, self.status, self.output)) |
- |
- |
-class ShellCmdError(ShellError): |
- # Raised when a command executed in a shell terminates with a nonzero |
- # exit code (status) |
- def __init__(self, cmd, status, output): |
- ShellError.__init__(self, cmd, output) |
- self.status = status |
- |
- def __str__(self): |
- return ("Shell command %r failed with status %d (output: %r)" % |
- (self.cmd, self.status, self.output)) |
- |
- |
-class ShellStatusError(ShellError): |
- # Raised when the command's exit status cannot be obtained |
- def __str__(self): |
- return ("Could not get exit status of command %r (output: %r)" % |
- (self.cmd, self.output)) |
- |
- |
def run_bg(command, termination_func=None, output_func=None, output_prefix="", |
timeout=1.0): |
""" |
@@ -292,12 +210,12 @@ def run_bg(command, termination_func=None, output_func=None, output_prefix="", |
@param timeout: Time duration (in seconds) to wait for the subprocess to |
terminate before returning |
- @return: A Tail object. |
+ @return: A kvm_tail object. |
""" |
- process = Tail(command=command, |
- termination_func=termination_func, |
- output_func=output_func, |
- output_prefix=output_prefix) |
+ process = kvm_tail(command=command, |
+ termination_func=termination_func, |
+ output_func=output_func, |
+ output_prefix=output_prefix) |
end_time = time.time() + timeout |
while time.time() < end_time and process.is_alive(): |
@@ -338,7 +256,7 @@ def run_fg(command, output_func=None, output_prefix="", timeout=1.0): |
return (status, output) |
-class Spawn: |
+class kvm_spawn: |
""" |
This class is used for spawning and controlling a child process. |
@@ -350,7 +268,7 @@ class Spawn: |
The text file can be accessed at any time using get_output(). |
In addition, the server opens as many pipes as requested by the client and |
writes the output to them. |
- The pipes are requested and accessed by classes derived from Spawn. |
+ The pipes are requested and accessed by classes derived from kvm_spawn. |
These pipes are referred to as "readers". |
The server also receives input from the client and sends it to the child |
process. |
@@ -634,7 +552,7 @@ _thread_kill_requested = False |
def kill_tail_threads(): |
""" |
- Kill all Tail threads. |
+ Kill all kvm_tail threads. |
After calling this function no new threads should be started. |
""" |
@@ -646,12 +564,12 @@ def kill_tail_threads(): |
_thread_kill_requested = False |
-class Tail(Spawn): |
+class kvm_tail(kvm_spawn): |
""" |
This class runs a child process in the background and sends its output in |
real time, line-by-line, to a callback function. |
- See Spawn's docstring. |
+ See kvm_spawn's docstring. |
This class uses a single pipe reader to read data in real time from the |
child process and report it to a given callback function. |
@@ -692,10 +610,10 @@ class Tail(Spawn): |
""" |
# Add a reader and a close hook |
self._add_reader("tail") |
- self._add_close_hook(Tail._join_thread) |
+ self._add_close_hook(kvm_tail._join_thread) |
# Init the superclass |
- Spawn.__init__(self, command, id, auto_close, echo, linesep) |
+ kvm_spawn.__init__(self, command, id, auto_close, echo, linesep) |
# Remember some attributes |
self.termination_func = termination_func |
@@ -711,11 +629,11 @@ class Tail(Spawn): |
def __getinitargs__(self): |
- return Spawn.__getinitargs__(self) + (self.termination_func, |
- self.termination_params, |
- self.output_func, |
- self.output_params, |
- self.output_prefix) |
+ return kvm_spawn.__getinitargs__(self) + (self.termination_func, |
+ self.termination_params, |
+ self.output_func, |
+ self.output_params, |
+ self.output_prefix) |
def set_termination_func(self, termination_func): |
@@ -847,15 +765,15 @@ class Tail(Spawn): |
t.join() |
-class Expect(Tail): |
+class kvm_expect(kvm_tail): |
""" |
This class runs a child process in the background and provides expect-like |
services. |
- It also provides all of Tail's functionality. |
+ It also provides all of kvm_tail's functionality. |
""" |
- def __init__(self, command=None, id=None, auto_close=True, echo=False, |
+ def __init__(self, command=None, id=None, auto_close=False, echo=False, |
linesep="\n", termination_func=None, termination_params=(), |
output_func=None, output_params=(), output_prefix=""): |
""" |
@@ -888,13 +806,13 @@ class Expect(Tail): |
self._add_reader("expect") |
# Init the superclass |
- Tail.__init__(self, command, id, auto_close, echo, linesep, |
- termination_func, termination_params, |
- output_func, output_params, output_prefix) |
+ kvm_tail.__init__(self, command, id, auto_close, echo, linesep, |
+ termination_func, termination_params, |
+ output_func, output_params, output_prefix) |
def __getinitargs__(self): |
- return Tail.__getinitargs__(self) |
+ return kvm_tail.__getinitargs__(self) |
def read_nonblocking(self, timeout=None): |
@@ -940,7 +858,7 @@ class Expect(Tail): |
def read_until_output_matches(self, patterns, filter=lambda x: x, |
- timeout=60, internal_timeout=None, |
+ timeout=30.0, internal_timeout=None, |
print_func=None): |
""" |
Read using read_nonblocking until a match is found using match_patterns, |
@@ -958,14 +876,13 @@ class Expect(Tail): |
@param internal_timeout: The timeout to pass to read_nonblocking |
@param print_func: A function to be used to print the data being read |
(should take a string parameter) |
- @return: Tuple containing the match index and the data read so far |
- @raise ExpectTimeoutError: Raised if timeout expires |
- @raise ExpectProcessTerminatedError: Raised if the child process |
- terminates while waiting for output |
- @raise ExpectError: Raised if an unknown error occurs |
+ @return: Tuple containing the match index (or None if no match was |
+ found) and the data read so far. |
""" |
+ match = None |
+ data = "" |
+ |
fd = self._get_fd("expect") |
- o = "" |
end_time = time.time() + timeout |
while True: |
try: |
@@ -973,31 +890,41 @@ class Expect(Tail): |
max(0, end_time - time.time())) |
except (select.error, TypeError): |
break |
- if not r: |
- raise ExpectTimeoutError(patterns, o) |
- # Read data from child |
- data = self.read_nonblocking(internal_timeout) |
- if not data: |
+ if fd not in r: |
break |
+ # Read data from child |
+ newdata = self.read_nonblocking(internal_timeout) |
# Print it if necessary |
- if print_func: |
- for line in data.splitlines(): |
+ if print_func and newdata: |
+ str = newdata |
+ if str.endswith("\n"): |
+ str = str[:-1] |
+ for line in str.split("\n"): |
print_func(line) |
+ data += newdata |
+ |
+ done = False |
# Look for patterns |
- o += data |
- match = self.match_patterns(filter(o), patterns) |
+ match = self.match_patterns(filter(data), patterns) |
if match is not None: |
- return match, o |
+ done = True |
+ # Check if child has died |
+ if not self.is_alive(): |
+ logging.debug("Process terminated with status %s" % |
+ self.get_status()) |
+ done = True |
+ # Are we done? |
+ if done: break |
- # Check if the child has terminated |
- if kvm_utils.wait_for(lambda: not self.is_alive(), 5, 0, 0.1): |
- raise ExpectProcessTerminatedError(patterns, self.get_status(), o) |
- else: |
- # This shouldn't happen |
- raise ExpectError(patterns, o) |
+ # Print some debugging info |
+ if match is None and (self.is_alive() or self.get_status() != 0): |
+ logging.debug("Timeout elapsed or process terminated. Output:" + |
+ kvm_utils.format_str_for_message(data.strip())) |
+ return (match, data) |
- def read_until_last_word_matches(self, patterns, timeout=60, |
+ |
+ def read_until_last_word_matches(self, patterns, timeout=30.0, |
internal_timeout=None, print_func=None): |
""" |
Read using read_nonblocking until the last word of the output matches |
@@ -1009,11 +936,8 @@ class Expect(Tail): |
@param internal_timeout: The timeout to pass to read_nonblocking |
@param print_func: A function to be used to print the data being read |
(should take a string parameter) |
- @return: A tuple containing the match index and the data read so far |
- @raise ExpectTimeoutError: Raised if timeout expires |
- @raise ExpectProcessTerminatedError: Raised if the child process |
- terminates while waiting for output |
- @raise ExpectError: Raised if an unknown error occurs |
+ @return: A tuple containing the match index (or None if no match was |
+ found) and the data read so far. |
""" |
def get_last_word(str): |
if str: |
@@ -1026,7 +950,7 @@ class Expect(Tail): |
print_func) |
- def read_until_last_line_matches(self, patterns, timeout=60, |
+ def read_until_last_line_matches(self, patterns, timeout=30.0, |
internal_timeout=None, print_func=None): |
""" |
Read using read_nonblocking until the last non-empty line of the output |
@@ -1043,11 +967,6 @@ class Expect(Tail): |
@param internal_timeout: The timeout to pass to read_nonblocking |
@param print_func: A function to be used to print the data being read |
(should take a string parameter) |
- @return: A tuple containing the match index and the data read so far |
- @raise ExpectTimeoutError: Raised if timeout expires |
- @raise ExpectProcessTerminatedError: Raised if the child process |
- terminates while waiting for output |
- @raise ExpectError: Raised if an unknown error occurs |
""" |
def get_last_nonempty_line(str): |
nonempty_lines = [l for l in str.splitlines() if l.strip()] |
@@ -1061,12 +980,12 @@ class Expect(Tail): |
print_func) |
-class ShellSession(Expect): |
+class kvm_shell_session(kvm_expect): |
""" |
This class runs a child process in the background. It it suited for |
processes that provide an interactive shell, such as SSH and Telnet. |
- It provides all services of Expect and Tail. In addition, it |
+ It provides all services of kvm_expect and kvm_tail. In addition, it |
provides command running services, and a utility function to test the |
process for responsiveness. |
""" |
@@ -1103,12 +1022,12 @@ class ShellSession(Expect): |
@param prompt: Regular expression describing the shell's prompt line. |
@param status_test_command: Command to be used for getting the last |
exit status of commands run inside the shell (used by |
- cmd_status_output() and friends). |
+ get_command_status_output() and friends). |
""" |
# Init the superclass |
- Expect.__init__(self, command, id, auto_close, echo, linesep, |
- termination_func, termination_params, |
- output_func, output_params, output_prefix) |
+ kvm_expect.__init__(self, command, id, auto_close, echo, linesep, |
+ termination_func, termination_params, |
+ output_func, output_params, output_prefix) |
# Remember some attributes |
self.prompt = prompt |
@@ -1116,8 +1035,8 @@ class ShellSession(Expect): |
def __getinitargs__(self): |
- return Expect.__getinitargs__(self) + (self.prompt, |
- self.status_test_command) |
+ return kvm_expect.__getinitargs__(self) + (self.prompt, |
+ self.status_test_command) |
def set_prompt(self, prompt): |
@@ -1166,7 +1085,7 @@ class ShellSession(Expect): |
return False |
- def read_up_to_prompt(self, timeout=60, internal_timeout=None, |
+ def read_up_to_prompt(self, timeout=30.0, internal_timeout=None, |
print_func=None): |
""" |
Read using read_nonblocking until the last non-empty line of the output |
@@ -1182,34 +1101,31 @@ class ShellSession(Expect): |
@param print_func: A function to be used to print the data being |
read (should take a string parameter) |
- @return: The data read so far |
- @raise ExpectTimeoutError: Raised if timeout expires |
- @raise ExpectProcessTerminatedError: Raised if the shell process |
- terminates while waiting for output |
- @raise ExpectError: Raised if an unknown error occurs |
+ @return: A tuple containing True/False indicating whether the prompt |
+ was found, and the data read so far. |
""" |
- m, o = self.read_until_last_line_matches([self.prompt], timeout, |
- internal_timeout, print_func) |
- return o |
+ (match, output) = self.read_until_last_line_matches([self.prompt], |
+ timeout, |
+ internal_timeout, |
+ print_func) |
+ return (match is not None, output) |
- def cmd_output(self, cmd, timeout=60, internal_timeout=None, |
- print_func=None): |
+ def get_command_status_output(self, command, timeout=30.0, |
+ internal_timeout=None, print_func=None): |
""" |
- Send a command and return its output. |
+ Send a command and return its exit status and output. |
- @param cmd: Command to send (must not contain newline characters) |
- @param timeout: The duration (in seconds) to wait for the prompt to |
- return |
+ @param command: Command to send (must not contain newline characters) |
+ @param timeout: The duration (in seconds) to wait until a match is |
+ found |
@param internal_timeout: The timeout to pass to read_nonblocking |
@param print_func: A function to be used to print the data being read |
(should take a string parameter) |
- @return: The output of cmd |
- @raise ShellTimeoutError: Raised if timeout expires |
- @raise ShellProcessTerminatedError: Raised if the shell process |
- terminates while waiting for output |
- @raise ShellError: Raised if an unknown error occurs |
+ @return: A tuple (status, output) where status is the exit status or |
+ None if no exit status is available (e.g. timeout elapsed), and |
+ output is the output of command. |
""" |
def remove_command_echo(str, cmd): |
if str and str.splitlines()[0] == cmd: |
@@ -1219,132 +1135,79 @@ class ShellSession(Expect): |
def remove_last_nonempty_line(str): |
return "".join(str.rstrip().splitlines(True)[:-1]) |
- logging.debug("Sending command: %s" % cmd) |
- self.read_nonblocking(timeout=0) |
- self.sendline(cmd) |
- try: |
- o = self.read_up_to_prompt(timeout, internal_timeout, print_func) |
- except ExpectError, e: |
- o = remove_command_echo(e.output, cmd) |
- if isinstance(e, ExpectTimeoutError): |
- raise ShellTimeoutError(cmd, o) |
- elif isinstance(e, ExpectProcessTerminatedError): |
- raise ShellProcessTerminatedError(cmd, e.status, o) |
- else: |
- raise ShellError(cmd, o) |
- |
- # Remove the echoed command and the final shell prompt |
- return remove_last_nonempty_line(remove_command_echo(o, cmd)) |
+ # Print some debugging info |
+ logging.debug("Sending command: %s" % command) |
+ # Read everything that's waiting to be read |
+ self.read_nonblocking(timeout=0) |
- def cmd_status_output(self, cmd, timeout=60, internal_timeout=None, |
- print_func=None): |
- """ |
- Send a command and return its exit status and output. |
- |
- @param cmd: Command to send (must not contain newline characters) |
- @param timeout: The duration (in seconds) to wait for the prompt to |
- return |
- @param internal_timeout: The timeout to pass to read_nonblocking |
- @param print_func: A function to be used to print the data being read |
- (should take a string parameter) |
+ # Send the command and get its output |
+ self.sendline(command) |
+ (match, output) = self.read_up_to_prompt(timeout, internal_timeout, |
+ print_func) |
+ # Remove the echoed command from the output |
+ output = remove_command_echo(output, command) |
+ # If the prompt was not found, return the output so far |
+ if not match: |
+ return (None, output) |
+ # Remove the final shell prompt from the output |
+ output = remove_last_nonempty_line(output) |
+ |
+ # Send the 'echo ...' command to get the last exit status |
+ self.sendline(self.status_test_command) |
+ (match, status) = self.read_up_to_prompt(10.0, internal_timeout) |
+ if not match: |
+ return (None, output) |
+ status = remove_command_echo(status, self.status_test_command) |
+ status = remove_last_nonempty_line(status) |
+ # Get the first line consisting of digits only |
+ digit_lines = [l for l in status.splitlines() if l.strip().isdigit()] |
+ if not digit_lines: |
+ return (None, output) |
+ status = int(digit_lines[0].strip()) |
- @return: A tuple (status, output) where status is the exit status and |
- output is the output of cmd |
- @raise ShellTimeoutError: Raised if timeout expires |
- @raise ShellProcessTerminatedError: Raised if the shell process |
- terminates while waiting for output |
- @raise ShellStatusError: Raised if the exit status cannot be obtained |
- @raise ShellError: Raised if an unknown error occurs |
- """ |
- o = self.cmd_output(cmd, timeout, internal_timeout, print_func) |
- try: |
- # Send the 'echo $?' (or equivalent) command to get the exit status |
- s = self.cmd_output(self.status_test_command, 10, internal_timeout) |
- except ShellError: |
- raise ShellStatusError(cmd, o) |
+ # Print some debugging info |
+ if status != 0: |
+ logging.debug("Command failed; status: %d, output:%s", status, |
+ kvm_utils.format_str_for_message(output.strip())) |
- # Get the first line consisting of digits only |
- digit_lines = [l for l in s.splitlines() if l.strip().isdigit()] |
- if digit_lines: |
- return int(digit_lines[0].strip()), o |
- else: |
- raise ShellStatusError(cmd, o) |
+ return (status, output) |
- def cmd_status(self, cmd, timeout=60, internal_timeout=None, |
- print_func=None): |
+ def get_command_status(self, command, timeout=30.0, internal_timeout=None, |
+ print_func=None): |
""" |
Send a command and return its exit status. |
- @param cmd: Command to send (must not contain newline characters) |
- @param timeout: The duration (in seconds) to wait for the prompt to |
- return |
+ @param command: Command to send |
+ @param timeout: The duration (in seconds) to wait until a match is |
+ found |
@param internal_timeout: The timeout to pass to read_nonblocking |
@param print_func: A function to be used to print the data being read |
(should take a string parameter) |
- @return: The exit status of cmd |
- @raise ShellTimeoutError: Raised if timeout expires |
- @raise ShellProcessTerminatedError: Raised if the shell process |
- terminates while waiting for output |
- @raise ShellStatusError: Raised if the exit status cannot be obtained |
- @raise ShellError: Raised if an unknown error occurs |
+ @return: Exit status or None if no exit status is available (e.g. |
+ timeout elapsed). |
""" |
- s, o = self.cmd_status_output(cmd, timeout, internal_timeout, |
- print_func) |
- return s |
+ (status, output) = self.get_command_status_output(command, timeout, |
+ internal_timeout, |
+ print_func) |
+ return status |
- def cmd(self, cmd, timeout=60, internal_timeout=None, print_func=None): |
+ def get_command_output(self, command, timeout=30.0, internal_timeout=None, |
+ print_func=None): |
""" |
- Send a command and return its output. If the command's exit status is |
- nonzero, raise an exception. |
+ Send a command and return its output. |
- @param cmd: Command to send (must not contain newline characters) |
- @param timeout: The duration (in seconds) to wait for the prompt to |
- return |
+ @param command: Command to send |
+ @param timeout: The duration (in seconds) to wait until a match is |
+ found |
@param internal_timeout: The timeout to pass to read_nonblocking |
@param print_func: A function to be used to print the data being read |
(should take a string parameter) |
- |
- @return: The output of cmd |
- @raise ShellTimeoutError: Raised if timeout expires |
- @raise ShellProcessTerminatedError: Raised if the shell process |
- terminates while waiting for output |
- @raise ShellError: Raised if the exit status cannot be obtained or if |
- an unknown error occurs |
- @raise ShellStatusError: Raised if the exit status cannot be obtained |
- @raise ShellError: Raised if an unknown error occurs |
- @raise ShellCmdError: Raised if the exit status is nonzero |
- """ |
- s, o = self.cmd_status_output(cmd, timeout, internal_timeout, |
- print_func) |
- if s != 0: |
- raise ShellCmdError(cmd, s, o) |
- return o |
- |
- |
- def get_command_output(self, cmd, timeout=60, internal_timeout=None, |
- print_func=None): |
- """ |
- Alias for cmd_output() for backward compatibility. |
- """ |
- return self.cmd_output(cmd, timeout, internal_timeout, print_func) |
- |
- |
- def get_command_status_output(self, cmd, timeout=60, internal_timeout=None, |
- print_func=None): |
- """ |
- Alias for cmd_status_output() for backward compatibility. |
- """ |
- return self.cmd_status_output(cmd, timeout, internal_timeout, |
- print_func) |
- |
- |
- def get_command_status(self, cmd, timeout=60, internal_timeout=None, |
- print_func=None): |
- """ |
- Alias for cmd_status() for backward compatibility. |
""" |
- return self.cmd_status(cmd, timeout, internal_timeout, print_func) |
+ (status, output) = self.get_command_status_output(command, timeout, |
+ internal_timeout, |
+ print_func) |
+ return output |