Fix default shell for command execution

Change-Id: Id783ef736b82615458d918de46d0e2e5ab82f706
This commit is contained in:
Federico Ressi 2021-06-04 11:39:29 +02:00
parent ba4ef88e01
commit 739a4e3c55
4 changed files with 62 additions and 40 deletions

View File

@ -17,6 +17,7 @@ from __future__ import absolute_import
import collections
import os
import shlex
import typing
import tobiko
@ -93,10 +94,10 @@ class JournalLogDigger(LogFileDigger):
def grep_lines(self, pattern) -> typing.List[str]:
try:
result = sh.execute(["journalctl", '--no-pager',
"--unit", self.filename,
"--unit", shlex.quote(self.filename),
"--since", "30 minutes ago",
'--output', 'short-iso',
'--grep', "'%s'" % pattern],
'--grep', shlex.quote(pattern)],
**self.execute_params)
except sh.ShellCommandFailed as ex:
if ex.stdout.endswith('-- No entries --\n'):

View File

@ -15,7 +15,6 @@
# under the License.
from __future__ import absolute_import
import fcntl
import os
import subprocess
import sys
@ -24,6 +23,7 @@ from oslo_log import log
import tobiko
from tobiko.shell.sh import _io
from tobiko.shell.sh import _exception
from tobiko.shell.sh import _execute
from tobiko.shell.sh import _path
from tobiko.shell.sh import _process
@ -68,6 +68,7 @@ class LocalExecutePathFixture(_path.ExecutePathFixture):
class LocalShellProcessFixture(_process.ShellProcessFixture):
path_execute = tobiko.required_fixture(LocalExecutePathFixture)
default_shell = True
def create_process(self):
tobiko.setup_fixture(self.path_execute)
@ -77,15 +78,23 @@ class LocalShellProcessFixture(_process.ShellProcessFixture):
stdout = parameters.stdout and subprocess.PIPE or None
stderr = parameters.stderr and subprocess.PIPE or None
env = merge_dictionaries(os.environ, parameters.environment)
return subprocess.Popen(args=args,
bufsize=parameters.buffer_size,
shell=False,
universal_newlines=False,
env=env,
cwd=parameters.current_dir,
stdin=stdin,
stdout=stdout,
stderr=stderr)
try:
return subprocess.Popen(args=args,
bufsize=parameters.buffer_size,
shell=False,
universal_newlines=False,
env=env,
cwd=parameters.current_dir,
stdin=stdin,
stdout=stdout,
stderr=stderr)
except FileNotFoundError as ex:
LOG.debug(f"Error executing local command: args={args}")
raise _exception.ShellCommandFailed(command=self.command,
exit_status=-1,
stdin=parameters.stdin,
stdout=None,
stderr=str(ex)) from ex
def setup_stdin(self):
self.stdin = _io.ShellStdin(delegate=self.process.stdin,
@ -96,7 +105,6 @@ class LocalShellProcessFixture(_process.ShellProcessFixture):
buffer_size=self.parameters.buffer_size)
def setup_stderr(self):
set_non_blocking(self.process.stderr.fileno())
self.stderr = _io.ShellStderr(delegate=self.process.stderr,
buffer_size=self.parameters.buffer_size)
@ -131,11 +139,6 @@ class LocalShellProcessFixture(_process.ShellProcessFixture):
return process and process.pid or None
def set_non_blocking(fd):
flag = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, flag | os.O_NONBLOCK)
def merge_dictionaries(*dictionaries):
merged = {}
for d in dictionaries:

View File

@ -72,17 +72,17 @@ class ShellProcessParameters(Parameters):
environment = None
current_dir = None
timeout: tobiko.Seconds = None
shell = None
stdin = False
stdout = True
stderr = True
buffer_size = io.DEFAULT_BUFFER_SIZE
poll_interval = 1.
sudo = None
network_namespace = None
retry_count: typing.Optional[int] = 3
retry_interval: tobiko.Seconds = 5.
retry_timeout: tobiko.Seconds = 120.
shell: typing.Union[None, bool, str] = None
sudo: typing.Union[None, bool, str] = None
class ShellProcessFixture(tobiko.SharedFixture):
@ -92,6 +92,7 @@ class ShellProcessFixture(tobiko.SharedFixture):
stdin = None
stdout = None
stderr = None
default_shell: typing.Union[None, bool, str] = None
_exit_status = None
@ -122,16 +123,16 @@ class ShellProcessFixture(tobiko.SharedFixture):
command = _command.shell_command(self.parameters.command)
network_namespace = self.parameters.network_namespace
sudo = self.parameters.sudo
shell = self.parameters.shell
if shell:
if shell is True:
shell = default_shell_command()
else:
shell = _command.shell_command(shell)
command = shell + [str(command)]
else:
command = _command.shell_command(command)
if shell is None:
shell = self.default_shell
if shell is not None:
tobiko.check_valid_type(shell, (bool, str))
if isinstance(shell, str):
command = _command.shell_command(shell) + [str(command)]
elif shell is True:
command = default_shell_command() + [str(command)]
if network_namespace:
if sudo is None:
@ -333,7 +334,7 @@ class ShellProcessFixture(tobiko.SharedFixture):
poll_interval = self.parameters.poll_interval
LOG.debug(f"Waiting for process data {poll_interval} "
f"seconds... \n"
f" command: '{self.command}'\n"
f" command: {self.command}\n"
f" attempt: {attempt.details}\n"
f" streams: {streams}")
@ -432,7 +433,7 @@ def str_from_stream(stream):
def default_shell_command():
from tobiko import config
CONF = config.CONF
return _command.shell_command(CONF.tobiko.shell.sudo.command)
return _command.shell_command(CONF.tobiko.shell.command)
def default_sudo_command():

View File

@ -15,6 +15,8 @@
# under the License.
from __future__ import absolute_import
import typing
import testtools
import tobiko
@ -22,15 +24,23 @@ from tobiko import config
from tobiko.openstack import keystone
from tobiko.openstack import stacks
from tobiko.shell import sh
from tobiko.shell import ssh
CONF = config.CONF
SSH_EXPECTED_SHELL = None
LOCAL_EXPECTED_SHELL = '/bin/sh -c'
class ExecuteTest(testtools.TestCase):
ssh_client = None
shell = '/bin/sh -c'
@property
def expected_shell(self) -> typing.Optional[str]:
if ssh.ssh_proxy_client() is None:
return LOCAL_EXPECTED_SHELL
else:
return SSH_EXPECTED_SHELL
def test_succeed(self, command='true', stdin=None, stdout=None,
stderr=None, expect_exit_status=0, **kwargs):
@ -153,34 +163,41 @@ class ExecuteTest(testtools.TestCase):
self.assertEqual(timeout, ex.timeout)
def execute(self, **kwargs):
kwargs.setdefault('shell', self.shell)
kwargs.setdefault('ssh_client', self.ssh_client)
return sh.execute(**kwargs)
def expected_command(self, command):
command = sh.shell_command(command)
if self.shell:
command = sh.shell_command(self.shell) + [str(command)]
if self.expected_shell is not None:
command = sh.shell_command(self.expected_shell) + [str(command)]
return str(command)
class LocalExecuteTest(ExecuteTest):
expected_shell = LOCAL_EXPECTED_SHELL
def execute(self, **kwargs):
kwargs.setdefault('shell', self.shell)
return sh.local_execute(**kwargs)
@keystone.skip_unless_has_keystone_credentials()
class SSHExecuteTest(ExecuteTest):
expected_shell = SSH_EXPECTED_SHELL
server_stack = tobiko.required_setup_fixture(
stacks.CirrosServerStackFixture)
stacks.UbuntuMinimalServerStackFixture)
@property
def ssh_client(self):
return self.server_stack.ssh_client
def execute(self, **kwargs):
kwargs.setdefault('shell', self.shell)
return sh.ssh_execute(ssh_client=self.ssh_client, **kwargs)
@keystone.skip_unless_has_keystone_credentials()
class CirrosSSHExecuteTest(SSHExecuteTest):
server_stack = tobiko.required_setup_fixture(
stacks.CirrosServerStackFixture)