
Add support for syncing and removing individual images from manifests alongside the existing bulk manifest processing. - Add sync_image_from_manifest() for individual image syncing - Add remove_image_from_manifest() for selective image removal - Add image_exists_in_local_registry() for sync confirmation - Expand test_docker_image_sync tests to illustrate usage - Add unit tests This maintains backward compatibility while enabling fine-grained control for test scenarios requiring individual image management. Change-Id: Ied9d1595d4c6b406fe0f029893ea84409191d3c2 Signed-off-by: Andrew Vaillancourt <andrew.vaillancourt@windriver.com>
311 lines
14 KiB
Python
311 lines
14 KiB
Python
"""
|
|
Docker Image Sync Tests Using Manifest-Based Configuration
|
|
|
|
This module implements foundational tests that verify Docker images listed
|
|
in YAML manifest files can be pulled from remote registries (e.g., DockerHub),
|
|
tagged, and pushed into the local StarlingX registry (registry.local:9001).
|
|
|
|
Tests validate both positive and negative scenarios using a config-driven
|
|
approach that resolves registries dynamically via ConfigurationManager.
|
|
|
|
Key Behaviors:
|
|
- Validates sync logic from manifest to local registry via SSH.
|
|
- Verifies registry resolution order: source_registry, then manifest_registry_map,
|
|
then default_source_registry.
|
|
- Supports flexible test-driven control over which manifests are synced.
|
|
- Logs missing images or partial sync failures for improved debugging.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
from pytest import FixtureRequest, fail, raises
|
|
|
|
from config.configuration_manager import ConfigurationManager
|
|
from framework.exceptions.keyword_exception import KeywordException
|
|
from framework.logging.automation_logger import get_logger
|
|
from framework.resources.resource_finder import get_stx_resource_path
|
|
from keywords.cloud_platform.ssh.lab_connection_keywords import LabConnectionKeywords
|
|
from keywords.docker.images.docker_images_keywords import DockerImagesKeywords
|
|
from keywords.docker.images.docker_sync_images_keywords import DockerSyncImagesKeywords
|
|
|
|
|
|
def run_manifest_sync_test(request: FixtureRequest, manifest_filename: str) -> None:
|
|
"""
|
|
Executes a manifest-based sync test, pulling Docker images from source registries
|
|
and pushing them to the local registry. Verifies that all expected images appear
|
|
in the local registry.
|
|
|
|
Args:
|
|
request (FixtureRequest): pytest request object used to register cleanup finalizer.
|
|
manifest_filename (str): Path to the manifest file in resources/.
|
|
|
|
Raises:
|
|
AssertionError: If any expected image is missing from the local registry.
|
|
"""
|
|
ssh_connection = LabConnectionKeywords().get_active_controller_ssh()
|
|
|
|
docker_config = ConfigurationManager.get_docker_config()
|
|
local_registry = docker_config.get_registry("local_registry")
|
|
manifest_paths = docker_config.get_image_manifest_files()
|
|
|
|
manifest_path = next((p for p in manifest_paths if Path(p).name == manifest_filename), None)
|
|
if not manifest_path:
|
|
raise FileNotFoundError(f"Manifest {manifest_filename} not found in docker config.")
|
|
|
|
DockerSyncImagesKeywords(ssh_connection).sync_images_from_manifest(manifest_path=manifest_path)
|
|
|
|
with open(manifest_path, "r") as f:
|
|
manifest = yaml.safe_load(f)
|
|
|
|
docker_image_keywords = DockerImagesKeywords(ssh_connection)
|
|
|
|
def cleanup():
|
|
get_logger().log_info(f"Cleaning up images listed in {manifest_filename}...")
|
|
ssh_connection = LabConnectionKeywords().get_active_controller_ssh()
|
|
DockerSyncImagesKeywords(ssh_connection).remove_images_from_manifest(manifest_path=manifest_path)
|
|
|
|
request.addfinalizer(cleanup)
|
|
|
|
images = docker_image_keywords.list_images()
|
|
actual_repos = [img.get_repository() for img in images]
|
|
validation_errors = []
|
|
|
|
for image in manifest["images"]:
|
|
name = image["name"]
|
|
tag = image["tag"]
|
|
expected_ref = f"{local_registry.get_registry_url()}/{name}"
|
|
|
|
get_logger().log_info(f"Checking local registry for: {expected_ref}:{tag}")
|
|
if expected_ref not in actual_repos:
|
|
msg = f"[{manifest_filename}] Expected image not found: {expected_ref}"
|
|
get_logger().log_warning(msg)
|
|
validation_errors.append(msg)
|
|
|
|
if validation_errors:
|
|
raise AssertionError("One or more expected images were not found:\n - " + "\n - ".join(validation_errors))
|
|
|
|
|
|
def test_sync_docker_images_valid_manifest_stx_dockerhub(request):
|
|
"""
|
|
Validates that all images from a well-formed manifest can be pulled and synced into the local registry.
|
|
"""
|
|
run_manifest_sync_test(request, "stx-test-images.yaml")
|
|
|
|
|
|
def test_sync_docker_images_invalid_manifest(request):
|
|
"""
|
|
Negative test: verifies that syncing an invalid manifest raises KeywordException.
|
|
|
|
This simulates real-world scenarios where image tags are missing or incorrectly referenced.
|
|
|
|
Very simple brittle string matching is used to verify the exception message.
|
|
"""
|
|
with raises(KeywordException, match="Image sync failed"):
|
|
run_manifest_sync_test(request, "stx-test-images-invalid.yaml")
|
|
|
|
|
|
def test_sync_all_manifests_from_config(request):
|
|
"""
|
|
Verifies that all manifest files listed in the Docker config can be successfully synced to local registry.
|
|
|
|
This test ensures that ConfigurationManager.get_docker_config().get_image_manifest_files()
|
|
is functional and can drive the sync logic dynamically.
|
|
|
|
Note: Expected to currently fail if any manifest is invalid or any image fail sync fails.
|
|
"""
|
|
manifest_paths = ConfigurationManager.get_docker_config().get_image_manifest_files()
|
|
|
|
get_logger().log_info("Found image manifest paths in config: " + ", ".join(manifest_paths))
|
|
|
|
for manifest_path in manifest_paths:
|
|
manifest_name = Path(manifest_path).name
|
|
run_manifest_sync_test(request, manifest_name)
|
|
|
|
get_logger().log_info(f"All manifests synced successfully. Manifests: {', '.join(manifest_paths)}")
|
|
|
|
|
|
def test_sync_explicit_manifests(request):
|
|
"""
|
|
Verifies that all manifest files listed in the test case can be successfully synced to local registry.
|
|
|
|
Note: Expected to currently fail if any manifest is invalid or any image sync fails.
|
|
"""
|
|
manifest_paths = [
|
|
"stx-test-images.yaml",
|
|
# "stx-test-images-invalid.yaml"
|
|
# Uncomment the above line to include the invalid manifest in the test (and expect failure).
|
|
]
|
|
|
|
get_logger().log_info("Found image manifest paths in config: " + ", ".join(manifest_paths))
|
|
|
|
for manifest_path in manifest_paths:
|
|
manifest_name = Path(manifest_path).name
|
|
run_manifest_sync_test(request, manifest_name)
|
|
|
|
get_logger().log_info(f"All manifests synced successfully. Manifests: {', '.join(manifest_paths)}")
|
|
|
|
|
|
def test_invalid_manifest_logging(request):
|
|
"""
|
|
Negative test: verifies that syncing an invalid manifest raises KeywordException.
|
|
|
|
Logs only the image references that actually failed during sync.
|
|
"""
|
|
manifest_path = "stx-test-images-invalid.yaml"
|
|
|
|
try:
|
|
run_manifest_sync_test(request, manifest_path)
|
|
except KeywordException as e:
|
|
# Parse individual failure lines from the exception message
|
|
failure_lines = [line.strip(" -") for line in str(e).splitlines() if line.strip().startswith("-")]
|
|
|
|
# Extract just the image reference (everything between 'image ' and ' from ')
|
|
failed_images = []
|
|
for line in failure_lines:
|
|
if "image " in line and " from " in line:
|
|
parts = line.split("image ", 1)[-1].split(" from ")[0].strip()
|
|
failed_images.append(parts)
|
|
else:
|
|
failed_images.append(line) # Fallback: use the whole line
|
|
|
|
formatted_images = "\n\t- " + "\n\t- ".join(failed_images)
|
|
get_logger().log_info(f"Negative image sync test passed.\n" f"\tManifest file: {manifest_path}\n" f"\tFailed images:{formatted_images}")
|
|
|
|
else:
|
|
fail("Expected KeywordException was not raised.")
|
|
|
|
|
|
# def test_sync_docker_images_valid_manifest_harbor(request):
|
|
# """
|
|
# Validates that all images from a well-formed manifest can be pulled and synced into the local registry from a harbor regsitry.
|
|
# """
|
|
# run_manifest_sync_test(request, "harbor-test-images.yaml")
|
|
|
|
|
|
# def test_sync_docker_images_mixed_registries(request):
|
|
# """
|
|
# Validates that images from a manifest with mixed registries (DockerHub and Harbor) can be pulled and synced into the local registry.
|
|
# """
|
|
# run_manifest_sync_test(request, "stx-test-images-mixed-registries.yaml")
|
|
|
|
|
|
def test_sync_single_busybox_image(request: FixtureRequest):
|
|
"""
|
|
Sync a single image (busybox:1.36.1) from the manifest to the local registry,
|
|
validate it exists, and clean up afterwards.
|
|
|
|
This test validates the sync_image_from_manifest() method which allows
|
|
selective syncing of individual images rather than entire manifests.
|
|
"""
|
|
manifest_path = get_stx_resource_path("resources/image_manifests/stx-third-party-test-images.yaml")
|
|
|
|
ssh_connection = LabConnectionKeywords().get_active_controller_ssh()
|
|
docker_sync_keywords = DockerSyncImagesKeywords(ssh_connection)
|
|
|
|
def cleanup():
|
|
get_logger().log_teardown_step("Removing busybox image from local registry")
|
|
docker_sync_keywords.remove_image_from_manifest(image_name="busybox", image_tag="1.36.1", manifest_path=manifest_path)
|
|
|
|
request.addfinalizer(cleanup)
|
|
|
|
get_logger().log_test_case_step("Syncing busybox image from manifest")
|
|
docker_sync_keywords.sync_image_from_manifest(image_name="busybox", image_tag="1.36.1", manifest_path=manifest_path)
|
|
|
|
get_logger().log_test_case_step("Validating busybox image exists in local registry")
|
|
assert docker_sync_keywords.image_exists_in_local_registry(image_name="busybox", image_tag="1.36.1")
|
|
|
|
|
|
def test_sync_third_party_images_to_local_registry(request: FixtureRequest):
|
|
"""
|
|
Sync required 3rd party Docker images from source registries to local registry.
|
|
|
|
This test ensures all common sanity test images are preloaded into registry.local.
|
|
Uses the stx-third-party-test-images.yaml manifest which contains public images
|
|
from DockerHub, k8s.gcr.io, and other public registries.
|
|
"""
|
|
manifest_path = get_stx_resource_path("resources/image_manifests/stx-third-party-test-images.yaml")
|
|
|
|
ssh_connection = LabConnectionKeywords().get_active_controller_ssh()
|
|
docker_keywords = DockerSyncImagesKeywords(ssh_connection)
|
|
|
|
def cleanup():
|
|
get_logger().log_teardown_step("Removing third-party images from manifest")
|
|
docker_keywords.remove_images_from_manifest(manifest_path)
|
|
|
|
request.addfinalizer(cleanup)
|
|
|
|
get_logger().log_test_case_step(f"Syncing Docker images from manifest: {manifest_path}")
|
|
docker_keywords.sync_images_from_manifest(manifest_path)
|
|
|
|
get_logger().log_test_case_step("Validating all images exist in local registry")
|
|
docker_keywords.manifest_images_exist_in_local_registry(manifest_path)
|
|
|
|
get_logger().log_info("Successfully synced all 3rd party test images")
|
|
|
|
|
|
def test_remove_third_party_images_from_local_registry():
|
|
"""
|
|
Remove third-party Docker images from registry.local that were synced from manifest.
|
|
|
|
This test ensures cleanup of common sanity/networking images after test execution.
|
|
Can be run independently or as part of test cleanup verification.
|
|
"""
|
|
manifest_path = get_stx_resource_path("resources/image_manifests/stx-third-party-test-images.yaml")
|
|
|
|
get_logger().log_test_case_step("Connecting to active controller")
|
|
ssh_connection = LabConnectionKeywords().get_active_controller_ssh()
|
|
|
|
get_logger().log_test_case_step(f"Removing Docker images listed in: {manifest_path}")
|
|
docker_keywords = DockerSyncImagesKeywords(ssh_connection=ssh_connection)
|
|
docker_keywords.remove_images_from_manifest(manifest_path)
|
|
|
|
get_logger().log_info("Successfully removed all third-party test images")
|
|
|
|
|
|
def test_single_image_sync_and_removal_workflow(request: FixtureRequest):
|
|
"""
|
|
Test the complete workflow of syncing and removing a single image.
|
|
|
|
This test validates:
|
|
1. Single image sync from manifest
|
|
2. Image validation in local registry
|
|
3. Single image removal from manifest
|
|
4. Cleanup verification
|
|
"""
|
|
manifest_path = get_stx_resource_path("resources/image_manifests/stx-third-party-test-images.yaml")
|
|
|
|
ssh_connection = LabConnectionKeywords().get_active_controller_ssh()
|
|
docker_sync_keywords = DockerSyncImagesKeywords(ssh_connection)
|
|
|
|
# Test image: calico/ctl:v3.27.0 (from the third-party manifest)
|
|
test_image_name = "calico/ctl"
|
|
test_image_tag = "v3.27.0"
|
|
|
|
def cleanup():
|
|
get_logger().log_teardown_step(f"Final cleanup: removing {test_image_name}:{test_image_tag}")
|
|
try:
|
|
docker_sync_keywords.remove_image_from_manifest(image_name=test_image_name, image_tag=test_image_tag, manifest_path=manifest_path)
|
|
except Exception as e:
|
|
get_logger().log_warning(f"Cleanup failed (expected if test passed): {e}")
|
|
|
|
request.addfinalizer(cleanup)
|
|
|
|
# Step 1: Sync the single image
|
|
get_logger().log_test_case_step(f"Syncing {test_image_name}:{test_image_tag} from manifest")
|
|
docker_sync_keywords.sync_image_from_manifest(image_name=test_image_name, image_tag=test_image_tag, manifest_path=manifest_path)
|
|
|
|
# Step 2: Validate it exists in local registry
|
|
get_logger().log_test_case_step(f"Validating {test_image_name}:{test_image_tag} exists in local registry")
|
|
assert docker_sync_keywords.image_exists_in_local_registry(image_name=test_image_name, image_tag=test_image_tag), f"Image {test_image_name}:{test_image_tag} should exist in local registry after sync"
|
|
|
|
# Step 3: Remove the single image
|
|
get_logger().log_test_case_step(f"Removing {test_image_name}:{test_image_tag} from local registry")
|
|
docker_sync_keywords.remove_image_from_manifest(image_name=test_image_name, image_tag=test_image_tag, manifest_path=manifest_path)
|
|
|
|
# Step 4: Validate it no longer exists (optional verification)
|
|
get_logger().log_test_case_step(f"Verifying {test_image_name}:{test_image_tag} was removed")
|
|
assert not docker_sync_keywords.image_exists_in_local_registry(image_name=test_image_name, image_tag=test_image_tag), f"Image {test_image_name}:{test_image_tag} should not exist in local registry after removal"
|
|
|
|
get_logger().log_info(f"Successfully completed single image sync/removal workflow for {test_image_name}:{test_image_tag}")
|