diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index bee55931..4a0989a0 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -1,4 +1,3 @@ - name: Integration tests on: @@ -28,9 +27,9 @@ jobs: outputs: channel: ${{ steps.charmcraft.outputs.channel }} steps: - - uses: actions/checkout@v4 - - id: charmcraft - run: echo "channel=$(cat .charmcraft-channel)" >> $GITHUB_OUTPUT + - uses: actions/checkout@v4 + - id: charmcraft + run: echo "channel=$(cat .charmcraft-channel)" >> $GITHUB_OUTPUT integration-tests: uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@a23cb8af2877316bd87af472b1732318386bd05c @@ -44,7 +43,7 @@ jobs: builder-label: ubuntu-22.04 tester-arch: AMD64 tester-size: xlarge - modules: '["test_k8s", "test_etcd", "test_ceph", "test_upgrade", "test_external_certs"]' + modules: '["test_k8s", "test_etcd", "test_ceph", "test_upgrade", "test_external_certs", "test_registry"]' # built and test on on self-hosted - id: arm64 builder-label: ARM64 diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 596843ae..5b1ba79f 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -10,7 +10,7 @@ import shlex import string from pathlib import Path -from typing import Optional +from typing import AsyncGenerator, Optional import juju.controller import juju.utils @@ -224,7 +224,9 @@ async def deploy_model( @pytest_asyncio.fixture(scope="module") -async def kubernetes_cluster(request: pytest.FixtureRequest, ops_test: OpsTest): +async def kubernetes_cluster( + request: pytest.FixtureRequest, ops_test: OpsTest +) -> AsyncGenerator[Model, None]: """Deploy kubernetes charms according to the bundle_marker.""" model = "main" bundle, markings = await Bundle.create(ops_test) @@ -232,6 +234,7 @@ async def kubernetes_cluster(request: pytest.FixtureRequest, ops_test: OpsTest): with ops_test.model_context(model) as the_model: if await is_deployed(the_model, bundle.path): log.info("Using existing model=%s.", the_model.uuid) + assert ops_test.model, "Model must be present" yield ops_test.model return diff --git a/tests/integration/data/test_registries/pod.yaml b/tests/integration/data/test_registries/pod.yaml new file mode 100644 index 00000000..f926894b --- /dev/null +++ b/tests/integration/data/test_registries/pod.yaml @@ -0,0 +1,12 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +apiVersion: v1 +kind: Pod +metadata: + name: test-pod +spec: + containers: + - name: test-container + #image: set by test + command: ["sleep", "100"] diff --git a/tests/integration/data/test_registries/test-bundle-docker-registry.yaml b/tests/integration/data/test_registries/test-bundle-docker-registry.yaml new file mode 100644 index 00000000..4f2e78ff --- /dev/null +++ b/tests/integration/data/test_registries/test-bundle-docker-registry.yaml @@ -0,0 +1,16 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +name: integration-test-docker-registry +description: |- + Used to deploy or refresh within an integration test model +series: jammy +applications: + k8s: + charm: k8s + constraints: cores=2 mem=8G root-disk=16G + num_units: 1 + docker-registry: + charm: docker-registry + channel: latest/edge + num_units: 1 diff --git a/tests/integration/test_registry.py b/tests/integration/test_registry.py new file mode 100644 index 00000000..80dd361a --- /dev/null +++ b/tests/integration/test_registry.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +# pylint: disable=duplicate-code +"""Integration tests.""" + +import json +import logging +import random +import string +from pathlib import Path +from typing import List + +import pytest +import yaml +from juju import model +from kubernetes.utils import create_from_yaml + +from . import helpers + +# This pytest mark configures the test environment to use the Canonical Kubernetes +# bundle with ceph, for all the test within this module. +pytestmark = [ + pytest.mark.bundle(file="test_registries/test-bundle-docker-registry.yaml", apps_local=["k8s"]) +] + +log = logging.getLogger(__name__) + + +def _get_data_file_path(name) -> Path: + """Retrieve the full path of the specified test data file.""" + return Path(__file__).parent / "data" / "test_registries" / name + + +@pytest.mark.abort_on_fail +async def test_custom_registry(kubernetes_cluster: model.Model, api_client): + """Test that the charm configures the correct directory and can access a custom registry.""" + # List of resources created during the test + created: List = [] + + docker_registry_unit = kubernetes_cluster.applications["docker-registry"].units[0] + docker_registry_ip = await docker_registry_unit.get_public_address() + + config_string = json.dumps( + [{"url": f"http://{docker_registry_ip}:5000", "host": f"{docker_registry_ip}:5000"}] + ) + + custom_registry_config = {"containerd-custom-registries": config_string} + + await kubernetes_cluster.applications["k8s"].set_config(custom_registry_config) + await kubernetes_cluster.wait_for_idle(status="active") + + action = await docker_registry_unit.run_action("push", image="busybox:latest", pull=True) + await action.wait() + + # Create a pod that uses the busybox image from the custom registry + # Image: {docker_registry_ip}:5000/busybox:latest + test_pod_manifest = list(yaml.safe_load_all(_get_data_file_path("pod.yaml").open())) + + random_pod_name = "test-pod-" + "".join( + random.choices(string.ascii_lowercase + string.digits, k=5) + ) + test_pod_manifest[0]["metadata"]["name"] = random_pod_name + + test_pod_manifest[0]["spec"]["containers"][0]["image"] = ( + f"{docker_registry_ip}:5000/busybox:latest" + ) + + k8s_unit = kubernetes_cluster.applications["k8s"].units[0] + try: + created.extend(*create_from_yaml(api_client, yaml_objects=test_pod_manifest)) + await helpers.wait_pod_phase(k8s_unit, random_pod_name, "Running") + finally: + # Cleanup + for resource in created: + kind = resource.kind + name = resource.metadata.name + event = await k8s_unit.run(f"k8s kubectl delete {kind} {name}") + _ = await event.wait()