From bec0cb0fd8f84d29b7228a8fcd3b455655f6fa9c Mon Sep 17 00:00:00 2001 From: Federico Ressi Date: Wed, 25 Sep 2019 11:03:51 +0200 Subject: [PATCH] Update get_exit_status for shell process classes. Change-Id: Ic1d8940a152dfce165bfa3648686a2a499c0a28e --- tobiko/shell/sh/_local.py | 35 ++++++++++--------- tobiko/shell/sh/_process.py | 31 ++++++++++++---- tobiko/shell/sh/_ssh.py | 19 +++++----- tobiko/tests/functional/shell/test_execute.py | 2 +- 4 files changed, 56 insertions(+), 31 deletions(-) diff --git a/tobiko/shell/sh/_local.py b/tobiko/shell/sh/_local.py index b4c255688..266b0a27b 100644 --- a/tobiko/shell/sh/_local.py +++ b/tobiko/shell/sh/_local.py @@ -106,24 +106,27 @@ class LocalShellProcessFixture(_process.ShellProcessFixture): def poll_exit_status(self): return self.process.poll() - def get_exit_status(self, timeout=None): - exit_status = self.process.poll() - while exit_status is None: - timeout = self.check_timeout(timeout=timeout) - LOG.debug("Waiting for remote command termination: " - "timeout=%r, command=%r", timeout, self.command) - if TimeoutExpired is None: - # Workaround for old Python versions that don't accept timeout - # as parameters for wait method + if TimeoutExpired is None: + # Workaround for old Python versions + def _get_exit_status(self, time_left): + start_time = now = time.time() + end_time = start_time + time_left + exit_status = self.poll_exit_status() + while exit_status is None and now < end_time: time.sleep(0.1) - exit_status = self.process.poll() + exit_status = self.poll_exit_status() + now = time.time() + return exit_status + else: + def _get_exit_status(self, time_left): + try: + exit_status = self.process.wait(timeout=time_left) + except TimeoutExpired: + LOG.exception("Failed waiting for subprocess termination") + return None else: - try: - exit_status = self.process.wait(timeout=min(5., timeout)) - except TimeoutExpired: - LOG.exception("Failed waiting for subprocess termination") - - return exit_status + assert exit_status is not None + return exit_status def kill(self): process = self.process diff --git a/tobiko/shell/sh/_process.py b/tobiko/shell/sh/_process.py index a50b35612..8324a78bb 100644 --- a/tobiko/shell/sh/_process.py +++ b/tobiko/shell/sh/_process.py @@ -30,6 +30,9 @@ from tobiko.shell.sh import _io LOG = log.getLogger(__name__) +MAX_TIMEOUT = 3600. # 1 hour + + def process(command=None, environment=None, timeout=None, shell=None, stdin=None, stdout=None, stderr=None, ssh_client=None, **kwargs): kwargs.update(command=command, environment=environment, timeout=timeout, @@ -187,7 +190,7 @@ class ShellProcessFixture(tobiko.SharedFixture): self.close_stdout() self.close_stderr() try: - exit_status = self.get_exit_status() + exit_status = self.poll_exit_status() except Exception: LOG.exception('Error getting exit status') exit_status = None @@ -210,6 +213,23 @@ class ShellProcessFixture(tobiko.SharedFixture): raise NotImplementedError def get_exit_status(self, timeout=None): + time_left, timeout = get_time_left([self.timeout, timeout]) + if time_left > 0.: + exit_status = self._get_exit_status(time_left=time_left) + if exit_status is not None: + return exit_status + + ex = _exception.ShellTimeoutExpired( + command=str(self.command), + timeout=timeout and timeout.timeout or None, + stdin=str_from_stream(self.stdin), + stdout=str_from_stream(self.stdout), + stderr=str_from_stream(self.stderr)) + LOG.debug("Timed out while waiting for command termination:\n%s", + self.command) + raise ex + + def _get_exit_status(self, time_left): raise NotImplementedError @property @@ -417,7 +437,7 @@ def shell_process_timeout(timeout): def get_time_left(timeouts, now=None): now = now or time.time() - min_time_left = float('inf') + min_time_left = float(MAX_TIMEOUT) min_timeout = None for timeout in timeouts: if timeout is not None: @@ -431,14 +451,13 @@ def get_time_left(timeouts, now=None): class ShellProcessTimeout(object): - timeout = float('inf') + timeout = MAX_TIMEOUT def __init__(self, timeout=None, start_time=None): if timeout is None: - timeout = float('inf') + timeout = self.timeout else: - timeout = float(timeout) - self.timeout = timeout + self.timeout = float(timeout) start_time = start_time and float(start_time) or time.time() self.start_time = start_time self.end_time = start_time + timeout diff --git a/tobiko/shell/sh/_ssh.py b/tobiko/shell/sh/_ssh.py index e2b3942ad..4d6ec2ca5 100644 --- a/tobiko/shell/sh/_ssh.py +++ b/tobiko/shell/sh/_ssh.py @@ -18,10 +18,10 @@ from __future__ import absolute_import from oslo_log import log import paramiko +from tobiko.shell.sh import _execute from tobiko.shell.sh import _io from tobiko.shell.sh import _local from tobiko.shell.sh import _process -from tobiko.shell.sh import _execute from tobiko.shell import ssh @@ -114,15 +114,18 @@ class SSHShellProcessFixture(_process.ShellProcessFixture): exit_status = None return exit_status - def get_exit_status(self, timeout=None): + def _get_exit_status(self, time_left=None): process = self.process - while not process.exit_status_ready(): - timeout = self.check_timeout(timeout=timeout) - LOG.debug("Waiting for remote command termination: " - "timeout=%r, command=%r", timeout, self.command) - process.status_event.wait(timeout=min(timeout, 5.)) + if not process.exit_status_ready(): + LOG.debug('Waiting for command (%s) exit status', self.command) + # recv_exit_status method doesn't accept timeout parameter + if process.status_event.wait(timeout=time_left): + assert process.exit_status_ready() + else: + assert not process.exit_status_ready() + return None - assert process.status_event.is_set() + assert process.exit_status is not None return process.exit_status def kill(self): diff --git a/tobiko/tests/functional/shell/test_execute.py b/tobiko/tests/functional/shell/test_execute.py index 303531f94..4d900e126 100644 --- a/tobiko/tests/functional/shell/test_execute.py +++ b/tobiko/tests/functional/shell/test_execute.py @@ -116,7 +116,7 @@ class ExecuteTest(testtools.TestCase): stdin='some input\n', stdout='some input\n') - def test_timeout_expires(self, command='sleep 5', timeout=0.1, stdin=None, + def test_timeout_expires(self, command='sleep 10', timeout=5., stdin=None, stdout=None, stderr=None, **kwargs): ex = self.assertRaises(sh.ShellTimeoutExpired, self.execute,