Skip to content

Commit

Permalink
Add vmimage dependency runner
Browse files Browse the repository at this point in the history
 VM image dependencies in tests

 * Comprehensive functional tests in `runner_vmimage.py`
 * Documentation section in dependencies guide
 * Example test and recipe JSON
 * Integration with resolver and check systems
 * Setup.py entry points for plugin discovery

Reference: #6043
Signed-off-by: Harvey Lynden <[email protected]>
  • Loading branch information
harvey0100 committed Jan 29, 2025
1 parent 0e6226f commit a725e50
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 3 deletions.
114 changes: 114 additions & 0 deletions avocado/plugins/runners/vmimage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import sys
import traceback
from multiprocessing import set_start_method

from avocado.core.nrunner.app import BaseRunnerApp
from avocado.core.nrunner.runner import BaseRunner
from avocado.core.utils import messages
from avocado.plugins.vmimage import download_image
from avocado.utils import vmimage


class VMImageRunner(BaseRunner):
"""
Runner for dependencies of type vmimage.
This runner uses the vmimage plugin's download_image function which handles:
1. Checking if the image exists in cache
2. Downloading the image if not in cache
3. Storing the image in the configured cache directory
4. Returning the cached image path
"""

name = "vmimage"
description = "Runner for dependencies of type vmimage"

def run(self, runnable):
try:
yield messages.StartedMessage.get()

provider = runnable.kwargs.get("provider")
version = runnable.kwargs.get("version")
arch = runnable.kwargs.get("arch")

if not all([provider, version, arch]):
stderr = "Missing required parameters: provider, version, and arch"
yield messages.StderrMessage.get(stderr.encode())
yield messages.FinishedMessage.get("error")
return

try:
yield messages.StdoutMessage.get(

Check warning on line 40 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L39-L40

Added lines #L39 - L40 were not covered by tests
f"Getting VM image for {provider} {version} {arch}".encode()
)

try:
provider_normalized = provider.lower()
available_providers = [

Check warning on line 46 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L44-L46

Added lines #L44 - L46 were not covered by tests
p.name.lower() for p in vmimage.IMAGE_PROVIDERS
]
if provider_normalized not in available_providers:
raise ValueError(

Check warning on line 50 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L49-L50

Added lines #L49 - L50 were not covered by tests
f"Provider '{provider}' not found. Available providers: {', '.join(available_providers)}"
)

# download_image will use cache if available, otherwise download
image = download_image(provider_normalized, version, arch)
if not image:
raise RuntimeError("Failed to get image")

Check warning on line 57 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L55-L57

Added lines #L55 - L57 were not covered by tests

yield messages.StdoutMessage.get(

Check warning on line 59 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L59

Added line #L59 was not covered by tests
f"Successfully retrieved VM image from cache or downloaded to: {image['file']}".encode()
)
yield messages.FinishedMessage.get("pass")
return

Check warning on line 63 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L62-L63

Added lines #L62 - L63 were not covered by tests

except Exception as e:
yield messages.StderrMessage.get(

Check warning on line 66 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L65-L66

Added lines #L65 - L66 were not covered by tests
f"Failed to download image: {str(e)}".encode()
)
yield messages.FinishedMessage.get(

Check warning on line 69 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L69

Added line #L69 was not covered by tests
"error",
fail_reason=str(e),
fail_class=e.__class__.__name__,
traceback=traceback.format_exc(),
)
return

Check warning on line 75 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L75

Added line #L75 was not covered by tests

except Exception as e:
yield messages.StderrMessage.get(

Check warning on line 78 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L77-L78

Added lines #L77 - L78 were not covered by tests
f"Failed to get/download VM image: {str(e)}".encode()
)
yield messages.FinishedMessage.get(

Check warning on line 81 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L81

Added line #L81 was not covered by tests
"error",
fail_reason=str(e),
fail_class=e.__class__.__name__,
traceback=traceback.format_exc(),
)
return

Check warning on line 87 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L87

Added line #L87 was not covered by tests

except Exception as e:
yield messages.StderrMessage.get(traceback.format_exc().encode())
yield messages.FinishedMessage.get(

Check warning on line 91 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L89-L91

Added lines #L89 - L91 were not covered by tests
"error",
fail_reason=str(e),
fail_class=e.__class__.__name__,
traceback=traceback.format_exc(),
)
return

Check warning on line 97 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L97

Added line #L97 was not covered by tests


class RunnerApp(BaseRunnerApp):
PROG_NAME = "avocado-runner-vmimage"
PROG_DESCRIPTION = "nrunner application for dependencies of type vmimage"
RUNNABLE_KINDS_CAPABLE = ["vmimage"]


def main():
if sys.platform == "darwin":
set_start_method("fork")

Check warning on line 108 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L108

Added line #L108 was not covered by tests
app = RunnerApp(print)
app.run()


if __name__ == "__main__":
main()

Check warning on line 114 in avocado/plugins/runners/vmimage.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/vmimage.py#L114

Added line #L114 was not covered by tests
39 changes: 39 additions & 0 deletions docs/source/guides/user/chapters/dependencies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,45 @@ effect on the spawner.
* `uri`: the image reference, in any format supported by ``podman
pull`` itself.

VM Image
++++++++

Support downloading virtual machine images ahead of test execution time.
This allows tests to have their required VM images downloaded and cached
before the test execution begins, preventing timeout issues during the
actual test run.

* `type`: `vmimage`
* `provider`: the VM image provider (e.g., 'Fedora')
* `version`: version of the image
* `arch`: architecture of the image

Following is an example of tests using the VM Image dependency that demonstrates
different use cases including multiple dependencies and different providers:

.. literalinclude:: ../../../../../examples/tests/dependency_vmimage.py

To test the VM Image dependency:

1. Clear the cache first::

$ avocado cache clear

2. Run the tests::

$ avocado run examples/tests/dependency_vmimage.py

3. Check the cache to see downloaded images::

$ avocado cache list

4. Run again to verify cache is used::

$ avocado run examples/tests/dependency_vmimage.py

The vmimage runner will download required images on first run and use cached
images on subsequent runs.

Ansible Module
++++++++++++++

Expand Down
1 change: 1 addition & 0 deletions examples/nrunner/recipes/runnable/vmimage_fedora.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"kind": "vmimage", "kwargs": {"provider": "fedora", "version": "41", "arch": "x86_64"}}
85 changes: 85 additions & 0 deletions examples/tests/dependency_vmimage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import os

from avocado import Test


class VmimageTest(Test):
"""
Test demonstrating VM image dependency usage.
The vmimage dependency runner will ensure the required VM image
is downloaded and cached before the test execution begins.
:avocado: dependency={"type": "vmimage", "provider": "fedora", "version": "41", "arch": "s390x"}
"""

def test_vmimage_exists(self):
"""
Verify that the VM image was downloaded by the vmimage runner.
"""
from avocado.core.settings import settings

# Get cache directory from settings
cache_dir = settings.as_dict().get("datadir.paths.cache_dirs")[0]
cache_base = os.path.join(cache_dir, "by_location")

# The image should be in the cache since the runner downloaded it
self.assertTrue(
os.path.exists(cache_base), f"Cache directory {cache_base} does not exist"
)

# Log cache contents for debugging
self.log.info("Cache directory contents:")
for root, _, files in os.walk(cache_base):
for f in files:
if f.endswith((".qcow2", ".raw")):
self.log.info("Found image: %s", os.path.join(root, f))


class MultiArchVmimageTest(Test):
"""
Test demonstrating multiple VM image dependencies with different architectures.
:avocado: dependency={"type": "vmimage", "provider": "fedora", "version": "41", "arch": "s390x"}
:avocado: dependency={"type": "vmimage", "provider": "fedora", "version": "41", "arch": "x86_64"}
"""

def test_multiple_images(self):
"""
Verify that multiple VM images can be handled by the runner.
"""
self.log.info("Test with multiple VM image dependencies")


class UbuntuVmimageTest(Test):
"""
Test demonstrating VM image dependency with a different provider.
:avocado: dependency={"type": "vmimage", "provider": "ubuntu", "version": "22.04", "arch": "x86_64"}
"""

def test_ubuntu_image(self):
"""
Verify that Ubuntu images can be handled by the runner.
"""
self.log.info("Test with Ubuntu VM image dependency")


# Testing instructions:
#
# 1. Clear the cache first:
# $ avocado cache clear
#
# 2. Run the tests:
# $ avocado run examples/tests/dependency_vmimage.py
#
# 3. Check the cache to see downloaded images:
# $ avocado cache list
#
# 4. Run again to verify cache is used:
# $ avocado run examples/tests/dependency_vmimage.py
#
# The vmimage runner should:
# - Download the required images on first run
# - Use cached images on subsequent runs
# - Handle multiple dependencies per test
# - Support different providers and architectures
1 change: 1 addition & 0 deletions python-avocado.spec
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ PATH=%{buildroot}%{_bindir}:%{buildroot}%{_libexecdir}/avocado:$PATH \
%{_bindir}/avocado-runner-pip
%{_bindir}/avocado-runner-podman-image
%{_bindir}/avocado-runner-sysinfo
%{_bindir}/avocado-runner-vmimage
%{_bindir}/avocado-software-manager
%{_bindir}/avocado-external-runner
%{python3_sitelib}/avocado*
Expand Down
7 changes: 5 additions & 2 deletions selftests/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
"job-api-check-file-exists": 11,
"job-api-check-output-file": 4,
"job-api-check-tmp-directory-exists": 1,
"nrunner-interface": 80,
"nrunner-interface": 90,
"nrunner-requirement": 28,
"unit": 682,
"jobs": 11,
"functional-parallel": 318,
"functional-parallel": 325,
"functional-serial": 7,
"optional-plugins": 0,
"optional-plugins-golang": 2,
Expand Down Expand Up @@ -630,6 +630,9 @@ def create_suites(args): # pylint: disable=W0621
{
"runner": "avocado-runner-pip",
},
{
"runner": "avocado-runner-vmimage",
},
],
}

Expand Down
3 changes: 2 additions & 1 deletion selftests/functional/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ def test_runnables_recipe(self):
package: 1
pip: 1
python-unittest: 1
sysinfo: 1"""
sysinfo: 1
vmimage: 1"""
cmd_line = f"{AVOCADO} -V list {runnables_recipe_path}"
result = process.run(cmd_line)
self.assertIn(
Expand Down
Loading

0 comments on commit a725e50

Please sign in to comment.