forked from avocado-framework/avocado
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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: avocado-framework#6043 Signed-off-by: Harvey Lynden <[email protected]>
- Loading branch information
1 parent
2337945
commit 6c63dbd
Showing
9 changed files
with
363 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import hashlib | ||
import os | ||
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.utils import vmimage | ||
|
||
|
||
class VMImageRunner(BaseRunner): | ||
"""Runner for dependencies of type vmimage | ||
This runner handles downloading and caching of VM images. | ||
Runnable attributes usage: | ||
* kind: 'vmimage' | ||
* uri: not used | ||
* args: not used | ||
* kwargs: | ||
- provider: VM image provider (e.g., 'Fedora') | ||
- version: version of the image | ||
- arch: architecture of the image | ||
""" | ||
|
||
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: | ||
# First get the image object | ||
yield messages.StdoutMessage.get( | ||
f"Getting VM image for {provider} {version} {arch}".encode() | ||
) | ||
|
||
# Download the image | ||
yield messages.StdoutMessage.get( | ||
f"Downloading VM image for: distro={provider}, version={version}, arch={arch}".encode() | ||
) | ||
try: | ||
# Get the image using vmimage utility with configured cache dir | ||
from avocado.core.settings import settings | ||
|
||
cache_dir = settings.as_dict().get("datadir.paths.cache_dirs")[0] | ||
|
||
# Validate provider exists | ||
provider = provider.lower() # Normalize case | ||
available_providers = [ | ||
p.name.lower() for p in vmimage.IMAGE_PROVIDERS | ||
] | ||
if provider not in available_providers: | ||
raise ValueError( | ||
f"Provider '{provider}' not found. Available providers: {', '.join(available_providers)}" | ||
) | ||
|
||
image = vmimage.Image.from_parameters( | ||
name=provider, version=version, arch=arch, cache_dir=cache_dir | ||
) | ||
|
||
# Download and get the image path | ||
image_path = image.get() | ||
if not image_path or not os.path.exists(image_path): | ||
raise RuntimeError( | ||
f"Downloaded image not found at {image_path}" | ||
) | ||
|
||
# Verify the downloaded image | ||
if not os.path.isfile(image_path): | ||
raise RuntimeError(f"Image path is not a file: {image_path}") | ||
if os.path.getsize(image_path) == 0: | ||
raise RuntimeError(f"Image file is empty: {image_path}") | ||
if not image_path.endswith((".qcow2", ".raw")): | ||
raise RuntimeError(f"Unexpected image format: {image_path}") | ||
|
||
# Verify checksum if available | ||
checksum_file = image_path + ".CHECKSUM" | ||
if os.path.exists(checksum_file): | ||
with open(checksum_file, "r", encoding="utf-8") as f: | ||
expected_checksum = ( | ||
f.read().strip().split()[0] | ||
) # Handle 'sha256:...' format | ||
hasher = hashlib.sha256() | ||
with open(image_path, "rb") as f: | ||
while chunk := f.read(4096): | ||
hasher.update(chunk) | ||
actual_checksum = hasher.hexdigest() | ||
if actual_checksum != expected_checksum: | ||
raise RuntimeError( | ||
f"Checksum mismatch for {image_path}\n" | ||
f"Expected: {expected_checksum}\n" | ||
f"Actual: {actual_checksum}" | ||
) | ||
|
||
yield messages.StdoutMessage.get( | ||
f"Successfully downloaded VM image to: {image_path}".encode() | ||
) | ||
yield messages.FinishedMessage.get("pass") | ||
|
||
except Exception as e: | ||
raise RuntimeError(f"Failed to download image: {str(e)}") | ||
|
||
except Exception as e: | ||
yield messages.StderrMessage.get( | ||
f"Failed to get/download VM image: {str(e)}".encode() | ||
) | ||
yield messages.FinishedMessage.get( | ||
"error", | ||
fail_reason=str(e), | ||
fail_class=e.__class__.__name__, | ||
traceback=traceback.format_exc(), | ||
) | ||
|
||
except Exception as e: | ||
yield messages.StderrMessage.get(traceback.format_exc().encode()) | ||
yield messages.FinishedMessage.get( | ||
"error", | ||
fail_reason=str(e), | ||
fail_class=e.__class__.__name__, | ||
traceback=traceback.format_exc(), | ||
) | ||
|
||
|
||
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") | ||
app = RunnerApp(print) | ||
app.run() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"kind": "vmimage", "kwargs": {"provider": "fedora", "version": "41", "arch": "x86_64"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import glob | ||
import os | ||
|
||
from avocado import Test | ||
|
||
|
||
class VmimageTest(Test): | ||
""" | ||
Example test using vmimage dependency | ||
:avocado: dependency={"type": "vmimage", "provider": "Fedora", "version": "41", "arch": "s390x"} | ||
""" | ||
|
||
def test(self): | ||
""" | ||
A test that requires a Fedora VM image | ||
""" | ||
# Reconstruct cache path using same logic as vmimage runner | ||
from avocado.core.settings import settings | ||
|
||
# Get settings | ||
cache_dir = settings.as_dict().get("datadir.paths.cache_dirs")[0] | ||
# Match runner's normalization exactly | ||
version = "41" | ||
arch = "x86_64" | ||
|
||
# Match actual filename pattern with build number | ||
image_filename = f"Fedora-Cloud-Base-Generic-{version}-*.{arch}.qcow2" | ||
|
||
# Search all hash directories for matching image | ||
cache_base = os.path.join(cache_dir, "by_location") | ||
found_files = [] | ||
searched_dirs = [] | ||
|
||
if os.path.exists(cache_base): | ||
for dir_name in os.listdir(cache_base): | ||
dir_path = os.path.join(cache_base, dir_name) | ||
if os.path.isdir(dir_path): | ||
pattern = os.path.join(dir_path, image_filename) | ||
matches = glob.glob(pattern) | ||
found_files.extend(matches) | ||
searched_dirs.append(dir_path) | ||
|
||
self.log.info( | ||
"Searched %d cache directories: %s", | ||
len(searched_dirs), | ||
", ".join(searched_dirs), | ||
) | ||
|
||
self.assertTrue( | ||
len(found_files) > 0, | ||
f"No VM images found matching pattern: {image_filename}\n" | ||
f"Searched directories: {searched_dirs}\n" | ||
f"Cache base path: {cache_base}", | ||
) | ||
self.log.info("VM image validation successful") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.