Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spawner: require runtime operation suitability #5649

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions avocado/core/plugin_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,16 @@ async def update_requirement_cache(runtime_task, result):
:type result: `avocado.core.teststatus.STATUSES`
"""

@abc.abstractmethod
def is_operational(self):
"""Checks whether this spawner is operationally capable to perform.
clebergnu marked this conversation as resolved.
Show resolved Hide resolved

:result: whether or not this spawner is operational on this system,
that is, whether it has all its requirements set up and
should be ready to operate successfully.
clebergnu marked this conversation as resolved.
Show resolved Hide resolved
:rtype: bool
"""


class DeploymentSpawner(Spawner):
"""Spawners that needs basic deployment are based on this class.
Expand Down
3 changes: 3 additions & 0 deletions avocado/core/spawners/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class MockSpawner(Spawner):
def __init__(self): # pylint: disable=W0231
self._known_tasks = {}

def is_operational(self):
return True

def is_task_alive(self, runtime_task): # pylint: disable=W0221
alive = self._known_tasks.get(runtime_task, None)
# task was never spawned
Expand Down
2 changes: 1 addition & 1 deletion avocado/plugins/human.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def post_tests(self, job):
return

if job.interrupted_reason is not None:
LOG_UI.info(job.interrupted_reason)
LOG_UI.warning(job.interrupted_reason)

if job.status == "PASS":
LOG_UI.info(
Expand Down
12 changes: 10 additions & 2 deletions avocado/plugins/runner_nrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,16 @@ def run_suite(self, job, test_suite):
job.interrupted_reason = f"Suite {test_suite.name} is disabled."
return summary

spawner_name = test_suite.config.get("run.spawner")
spawner = SpawnerDispatcher(test_suite.config, job)[spawner_name].obj
if not spawner.is_operational():
suite_name = f" {test_suite.name}" if test_suite.name else ""
msg = f'Spawner "{spawner_name}" is not operational, aborting execution of suite {suite_name}. Please check the logs for more information.'
LOG_JOB.error(msg)
job.interrupted_reason = msg
summary.add("INTERRUPTED")
return summary

test_suite.tests, missing_requirements = check_runnables_runner_requirements(
test_suite.tests
)
Expand Down Expand Up @@ -325,8 +335,6 @@ def run_suite(self, job, test_suite):
if rt.task.category == "test"
]
self.tsm = TaskStateMachine(self.runtime_tasks, self.status_repo)
spawner_name = test_suite.config.get("run.spawner")
spawner = SpawnerDispatcher(test_suite.config, job)[spawner_name].obj
max_running = min(
test_suite.config.get("run.max_parallel_tasks"), len(self.runtime_tasks)
)
Expand Down
11 changes: 6 additions & 5 deletions avocado/plugins/spawners/lxc.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ class LXCSpawner(Spawner, SpawnerMixin):
METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
slots_cache = {}

def is_operational(self):
if not LXC_AVAILABLE:
LOG.error("LXC python bindings not available on the system")
return False
return True
clebergnu marked this conversation as resolved.
Show resolved Hide resolved

@staticmethod
def run_container_cmd(container, command):
with LXCStreamsFile() as tmp_out, LXCStreamsFile() as tmp_err:
Expand Down Expand Up @@ -209,11 +215,6 @@ async def spawn_task(self, runtime_task):
arch = self.config.get("spawner.lxc.arch")
create_hook = self.config.get("spawner.lxc.create_hook")

if not LXC_AVAILABLE:
msg = "LXC python bindings not available on the system"
runtime_task.status = msg
return False

container_id = runtime_task.spawner_handle
container = lxc.Container(container_id)
if not container.defined:
Expand Down
14 changes: 14 additions & 0 deletions avocado/plugins/spawners/podman.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,20 @@ def _get_podman_version(self):
f'output received "{out}" does not match expected output'
)

def is_operational(self):
try:
_ = self.podman_version
except PodmanException as ex:
LOG.error(ex)
return False

if self.podman_version[0] >= 3:
return True
LOG.error(
f"The podman binary f{self.podman_bin} did not report a suitable version (>= 3.0)"
)
return False

@property
def podman_version(self):
if self._podman_version == (None, None, None):
Expand Down
3 changes: 3 additions & 0 deletions avocado/plugins/spawners/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class ProcessSpawner(Spawner, SpawnerMixin):
description = "Process based spawner"
METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]

def is_operational(self):
return True

@staticmethod
def is_task_alive(runtime_task):
if runtime_task.spawner_handle is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ class RemoteSpawner(Spawner, SpawnerMixin):
METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
slots_cache = {}

def is_operational(self):
return True

@staticmethod
async def run_remote_cmd_async(session, command, timeout):
loop = asyncio.get_event_loop()
Expand Down
2 changes: 1 addition & 1 deletion selftests/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"nrunner-requirement": 16,
"unit": 667,
"jobs": 11,
"functional-parallel": 297,
"functional-parallel": 298,
"functional-serial": 4,
"optional-plugins": 0,
"optional-plugins-golang": 2,
Expand Down
4 changes: 2 additions & 2 deletions selftests/functional/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def test_runner_failfast_fail(self):
f"--max-parallel-tasks=1"
)
result = process.run(cmd_line, ignore_status=True)
self.assertIn(b"Interrupting job (failfast).", result.stdout)
self.assertIn(b"Interrupting job (failfast).", result.stderr)
self.assertIn(b"PASS 1 | ERROR 0 | FAIL 1 | SKIP 1", result.stdout)
expected_rc = exit_codes.AVOCADO_TESTS_FAIL | exit_codes.AVOCADO_JOB_INTERRUPTED
self.assertEqual(
Expand All @@ -230,7 +230,7 @@ def test_runner_failfast_error(self):
f"--max-parallel-tasks=1"
)
result = process.run(cmd_line, ignore_status=True)
self.assertIn(b"Interrupting job (failfast).", result.stdout)
self.assertIn(b"Interrupting job (failfast).", result.stderr)
self.assertIn(b"PASS 1 | ERROR 1 | FAIL 0 | SKIP 1", result.stdout)
expected_rc = exit_codes.AVOCADO_TESTS_FAIL | exit_codes.AVOCADO_JOB_INTERRUPTED
self.assertEqual(
Expand Down
16 changes: 16 additions & 0 deletions selftests/functional/plugin/spawners/podman.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os

from avocado import Test
from avocado.core import exit_codes
from avocado.core.job import Job
from avocado.utils import process, script
from selftests.utils import AVOCADO, BASEDIR
Expand Down Expand Up @@ -116,3 +117,18 @@ def test_asset_files(self):
self.assertEqual(result.exit_status, 0)
self.assertIn("use_data.sh: STARTED", result.stdout_text)
self.assertIn("use_data.sh: PASS", result.stdout_text)


class OperationalTest(Test):
def test_not_operational(self):
fake_podman_bin = os.path.join(BASEDIR, "examples", "tests", "false")
result = process.run(
f"{AVOCADO} run "
f"--job-results-dir {self.workdir} "
f"--disable-sysinfo --spawner=podman "
f"--spawner-podman-bin={fake_podman_bin} "
f"-- examples/tests/true",
ignore_status=True,
)
self.assertEqual(result.exit_status, exit_codes.AVOCADO_JOB_INTERRUPTED)
self.assertIn('Spawner "podman" is not operational', result.stderr_text)
Loading