Update tobiko shell package

Change-Id: I18d87d1763d5b501bd3694f79940d2b1ee441dd9
This commit is contained in:
Federico Ressi 2021-05-31 14:10:29 +02:00
parent 319803da4d
commit 2cde963248
6 changed files with 74 additions and 10 deletions

View File

@ -66,6 +66,7 @@ str_from_stream = _process.str_from_stream
ShellProcessFixture = _process.ShellProcessFixture ShellProcessFixture = _process.ShellProcessFixture
PsError = _ps.PsError PsError = _ps.PsError
PsProcess = _ps.PsProcess
PsWaitTimeout = _ps.PsWaitTimeout PsWaitTimeout = _ps.PsWaitTimeout
list_all_processes = _ps.list_all_processes list_all_processes = _ps.list_all_processes
list_kernel_processes = _ps.list_kernel_processes list_kernel_processes = _ps.list_kernel_processes

View File

@ -85,13 +85,12 @@ class ShellReadable(ShellIOBase):
def readable(self): def readable(self):
return True return True
def read(self, size=None): def read(self, size: int = None) -> bytes:
size = size or self.buffer_size size = size or self.buffer_size
try: try:
chunk = self.delegate.read(size) chunk = self.delegate.read(size)
except IOError: except IOError:
LOG.exception('Error reading from %r', self) LOG.exception('Error reading from %r', self)
chunk = None
try: try:
self.close() self.close()
except Exception: except Exception:
@ -100,6 +99,8 @@ class ShellReadable(ShellIOBase):
if chunk: if chunk:
self._data_chunks.append(chunk) self._data_chunks.append(chunk)
elif chunk is None:
chunk = 'b'
return chunk return chunk
@property @property

View File

@ -18,12 +18,20 @@ from __future__ import absolute_import
import collections import collections
import re import re
import time import time
import typing
from oslo_log import log
import tobiko import tobiko
from tobiko.shell.sh import _command
from tobiko.shell.sh import _exception
from tobiko.shell.sh import _execute from tobiko.shell.sh import _execute
from tobiko.shell.sh import _hostname from tobiko.shell.sh import _hostname
LOG = log.getLogger(__name__)
class PsError(tobiko.TobikoException): class PsError(tobiko.TobikoException):
message = "Unable to list processes from host: {error}" message = "Unable to list processes from host: {error}"
@ -36,6 +44,9 @@ class PsWaitTimeout(PsError):
IS_KERNEL_RE = re.compile('^\\[.*\\]$') IS_KERNEL_RE = re.compile('^\\[.*\\]$')
_NOT_FOUND = object()
class PsProcess(collections.namedtuple('PsProcess', ['ssh_client', class PsProcess(collections.namedtuple('PsProcess', ['ssh_client',
'pid', 'pid',
'command'])): 'command'])):
@ -46,6 +57,27 @@ class PsProcess(collections.namedtuple('PsProcess', ['ssh_client',
def is_kernel(self): def is_kernel(self):
return IS_KERNEL_RE.match(self.command) is not None return IS_KERNEL_RE.match(self.command) is not None
@property
def command_line(self) -> typing.Optional[_command.ShellCommand]:
command_line = self.__dict__.get('_command_line', _NOT_FOUND)
if command_line is _NOT_FOUND:
command_line = None
try:
output = _execute.execute(f'cat /proc/{self.pid}/cmdline',
ssh_client=self.ssh_client).stdout
except _exception.ShellCommandFailed as ex:
LOG.error(f"Unable to get process command line: {ex.stderr}")
else:
line = _command.ShellCommand(output.strip().split('\0')[:-1])
if line[0] != self.command:
LOG.error(f"Command line of process {self.pid} "
"doesn't match its command "
f"({self.command}): {line}")
else:
command_line = line
self.__dict__['command_line'] = command_line
return command_line
def list_kernel_processes(**list_params): def list_kernel_processes(**list_params):
return list_processes(is_kernel=True, **list_params) return list_processes(is_kernel=True, **list_params)
@ -55,8 +87,12 @@ def list_all_processes(**list_params):
return list_processes(is_kernel=None, **list_params) return list_processes(is_kernel=None, **list_params)
def list_processes(pid=None, command=None, is_kernel=False, ssh_client=None, def list_processes(pid=None,
**execute_params): command: typing.Optional[str] = None,
is_kernel=False,
ssh_client=None,
command_line: typing.Optional[str] = None,
**execute_params) -> tobiko.Selection[PsProcess]:
"""Returns the number of seconds passed since last host reboot """Returns the number of seconds passed since last host reboot
It reads and parses remote special file /proc/uptime and returns a floating It reads and parses remote special file /proc/uptime and returns a floating
@ -70,7 +106,7 @@ def list_processes(pid=None, command=None, is_kernel=False, ssh_client=None,
raise PsError(error=result.stderr) raise PsError(error=result.stderr)
# Extract a list of PsProcess instances from table body # Extract a list of PsProcess instances from table body
processes = tobiko.Selection() processes = tobiko.Selection[PsProcess]()
for process_data in parse_table(lines=output.splitlines(), for process_data in parse_table(lines=output.splitlines(),
schema=PS_TABLE_SCHEMA): schema=PS_TABLE_SCHEMA):
processes.append(PsProcess(ssh_client=ssh_client, **process_data)) processes.append(PsProcess(ssh_client=ssh_client, **process_data))
@ -83,15 +119,24 @@ def list_processes(pid=None, command=None, is_kernel=False, ssh_client=None,
if processes and command is not None: if processes and command is not None:
# filter processes by command # filter processes by command
command = re.compile(command) pattern = re.compile(command)
processes = tobiko.select(process processes = tobiko.Selection[PsProcess](
for process in processes process
if command.match(process.command)) for process in processes
if pattern.match(process.command))
if processes and is_kernel is not None: if processes and is_kernel is not None:
# filter kernel processes # filter kernel processes
processes = processes.with_attributes(is_kernel=bool(is_kernel)) processes = processes.with_attributes(is_kernel=bool(is_kernel))
if processes and command_line is not None:
pattern = re.compile(command_line)
processes = tobiko.Selection[PsProcess](
process
for process in processes
if (process.command_line is not None and
pattern.match(f"{process.command_line}")))
return processes return processes

View File

@ -31,6 +31,9 @@ ssh_command = _command.ssh_command
ssh_proxy_client = _client.ssh_proxy_client ssh_proxy_client = _client.ssh_proxy_client
SSHConnectFailure = _client.SSHConnectFailure SSHConnectFailure = _client.SSHConnectFailure
gather_ssh_connect_parameters = _client.gather_ssh_connect_parameters gather_ssh_connect_parameters = _client.gather_ssh_connect_parameters
SSHClientType = _client.SSHClientType
ssh_client_fixture = _client.ssh_client_fixture
reset_default_ssh_port_forward_manager = \ reset_default_ssh_port_forward_manager = \
_forward.reset_default_ssh_port_forward_manager _forward.reset_default_ssh_port_forward_manager

View File

@ -625,3 +625,17 @@ def check_ssh_connection(client):
transport.send_ignore() transport.send_ignore()
return True return True
return False return False
SSHClientType = typing.Union[None, bool, SSHClientFixture]
def ssh_client_fixture(obj: SSHClientType) -> \
typing.Optional[SSHClientFixture]:
if obj is None:
return ssh_proxy_client()
if obj is False:
return None
if isinstance(obj, SSHClientFixture):
return obj
raise TypeError(f"Can't get an SSHClientFixture from objeck {obj}")

View File

@ -4,7 +4,7 @@
name: tobiko-infrared name: tobiko-infrared
parent: tox parent: tox
nodeset: tobiko-infrared-centos nodeset: tobiko-infrared-centos
timeout: 1800 timeout: 3600
description: | description: |
Run test cases using tobiko infrared plugin Run test cases using tobiko infrared plugin
pre-run: playbooks/infrared/pre.yaml pre-run: playbooks/infrared/pre.yaml