From 5bdf803d10471162e0c6c844c359a3f210f55df9 Mon Sep 17 00:00:00 2001 From: Berkay Tekin Oz Date: Thu, 14 Nov 2024 18:28:57 +0000 Subject: [PATCH] Add pull through cache registry --- .../integration/templates/registry/hosts.toml | 2 + .../templates/registry/registry-config.yaml | 20 +++ .../templates/registry/registry.service | 15 ++ tests/integration/tests/conftest.py | 27 +++ tests/integration/tests/test_util/config.py | 12 ++ tests/integration/tests/test_util/registry.py | 158 ++++++++++++++++++ 6 files changed, 234 insertions(+) create mode 100644 tests/integration/templates/registry/hosts.toml create mode 100644 tests/integration/templates/registry/registry-config.yaml create mode 100644 tests/integration/templates/registry/registry.service create mode 100644 tests/integration/tests/test_util/registry.py diff --git a/tests/integration/templates/registry/hosts.toml b/tests/integration/templates/registry/hosts.toml new file mode 100644 index 000000000..416c9a642 --- /dev/null +++ b/tests/integration/templates/registry/hosts.toml @@ -0,0 +1,2 @@ +[host."http://$IP:$PORT"] +capabilities = ["pull", "resolve"] diff --git a/tests/integration/templates/registry/registry-config.yaml b/tests/integration/templates/registry/registry-config.yaml new file mode 100644 index 000000000..90202b543 --- /dev/null +++ b/tests/integration/templates/registry/registry-config.yaml @@ -0,0 +1,20 @@ +version: 0.1 +log: + fields: + service: registry +storage: + cache: + blobdescriptor: inmemory + filesystem: + rootdirectory: /var/lib/registry/$NAME +http: + addr: :$PORT + headers: + X-Content-Type-Options: [nosniff] +health: + storagedriver: + enabled: true + interval: 10s + threshold: 3 +proxy: + remoteurl: $REMOTE diff --git a/tests/integration/templates/registry/registry.service b/tests/integration/templates/registry/registry.service new file mode 100644 index 000000000..83de843a6 --- /dev/null +++ b/tests/integration/templates/registry/registry.service @@ -0,0 +1,15 @@ + [Unit] + Description=registry-$NAME + Documentation=https://github.com/distribution/distribution + + [Service] + Type=simple + Restart=always + RestartSec=5s + LimitNOFILE=40000 + TimeoutStartSec=0 + + ExecStart=/bin/registry serve /etc/distribution/$NAME.yaml + + [Install] + WantedBy=multi-user.target diff --git a/tests/integration/tests/conftest.py b/tests/integration/tests/conftest.py index 04efacd98..6a7354319 100644 --- a/tests/integration/tests/conftest.py +++ b/tests/integration/tests/conftest.py @@ -3,12 +3,14 @@ # import itertools import logging +from string import Template from pathlib import Path from typing import Generator, Iterator, List, Optional, Union import pytest from test_util import config, harness, util from test_util.etcd import EtcdCluster +from test_util.registry import Registry LOG = logging.getLogger(__name__) @@ -79,6 +81,11 @@ def h() -> harness.Harness: _harness_clean(h) +@pytest.fixture(scope="session") +def registry(h: harness.Harness) -> Registry: + yield Registry(h) + + def pytest_configure(config): config.addinivalue_line( "markers", @@ -141,6 +148,7 @@ def network_type(request) -> Union[str, None]: @pytest.fixture(scope="function") def instances( h: harness.Harness, + registry: Registry, node_count: int, tmp_path: Path, disable_k8s_bootstrapping: bool, @@ -166,6 +174,25 @@ def instances( if not no_setup: util.setup_k8s_snap(instance, tmp_path, snap) + for mirror in registry.mirrors: + + substitutes = { + "IP": registry.ip, + "PORT": mirror.port, + } + + instance.exec(["mkdir", "-p", f"/etc/containerd/hosts.d/{mirror.name}"]) + + with open(config.REGISTRY_DIR / "hosts.toml", "r") as registry_template: + src = Template(registry_template.read()) + instance.exec( + [ + "dd", + f"of=/etc/containerd/hosts.d/{mirror.name}/hosts.toml", + ], + input=str.encode(src.substitute(substitutes)), + ) + if not disable_k8s_bootstrapping and not no_setup: first_node, *_ = instances diff --git a/tests/integration/tests/test_util/config.py b/tests/integration/tests/test_util/config.py index 40e375c23..ce0c7a34e 100644 --- a/tests/integration/tests/test_util/config.py +++ b/tests/integration/tests/test_util/config.py @@ -21,6 +21,18 @@ # ETCD_VERSION is the version of etcd to use. ETCD_VERSION = os.getenv("ETCD_VERSION") or "v3.4.34" +# REGISTRY_DIR contains all templates required to setup an registry mirror. +REGISTRY_DIR = MANIFESTS_DIR / "registry" + +# REGISTRY_URL is the url from which the registry binary should be downloaded. +REGISTRY_URL = ( + os.getenv("REGISTRY_URL") + or "https://github.com/distribution/distribution/releases/download" +) + +# REGISTRY_VERSION is the version of registry to use. +REGISTRY_VERSION = os.getenv("REGISTRY_VERSION") or "v2.8.3" + # FLAVOR is the flavor of the snap to use. FLAVOR = os.getenv("TEST_FLAVOR") or "" diff --git a/tests/integration/tests/test_util/registry.py b/tests/integration/tests/test_util/registry.py new file mode 100644 index 000000000..ba76aa4a8 --- /dev/null +++ b/tests/integration/tests/test_util/registry.py @@ -0,0 +1,158 @@ +# +# Copyright 2024 Canonical, Ltd. +# +import logging +from string import Template +from typing import List + +from test_util import config +from test_util.harness import Harness, Instance +from test_util.util import get_default_ip + +LOG = logging.getLogger(__name__) + + +class Mirror: + def __init__(self, name: str, port: int, remote: str): + """ + Initialize the Mirror object. + + Args: + name (str): The name of the mirror. + port (int): The port of the mirror. + remote (str): The remote URL of the upstream registry. + """ + self._name = name + self._port = port + self._remote = remote + + @property + def name(self) -> str: + """ + Get the name of the mirror. + + Returns: + str: The name of the mirror. + """ + return self._name + + @property + def port(self) -> int: + """ + Get the port of the mirror. + + Returns: + int: The port of the mirror. + """ + return self._port + + @property + def remote(self) -> str: + """ + Get the remote URL of the upstream registry. + + Returns: + str: The remote URL of the upstream registry. + """ + return self._remote + + +class Registry: + + def __init__(self, h: Harness): + """ + Initialize the Registry object. + + Args: + h (Harness): The test harness object. + """ + self.registry_url = config.REGISTRY_URL + self.registry_version = config.REGISTRY_VERSION + self.instance: Instance = None + self.harness: Harness = h + self._mirrors: List[Mirror] = [ + Mirror("ghcr.io", 5000, "https://ghcr.io"), + Mirror("docker.io", 5001, "https://registry-1.docker.io"), + ] + self.instance = self.harness.new_instance() + + arch = self.instance.arch + self.instance.exec( + [ + "curl", + "-L", + f"{self.registry_url}/{self.registry_version}/registry_{self.registry_version[1:]}_linux_{arch}.tar.gz", + "-o", + f"/tmp/registry_{self.registry_version}_linux_{arch}.tar.gz", + ] + ) + + self.instance.exec( + [ + "tar", + "xzvf", + f"/tmp/registry_{self.registry_version}_linux_{arch}.tar.gz", + "-C", + "/bin/", + "registry", + ], + ) + + self._ip = get_default_ip(self.instance) + + self.add_mirrors() + + def add_mirrors(self): + for mirror in self._mirrors: + self.add_mirror(mirror) + + def add_mirror(self, mirror: Mirror): + + substitutes = { + "NAME": mirror.name, + "PORT": mirror.port, + "REMOTE": mirror.remote, + } + + self.instance.exec(["mkdir", "-p", "/etc/distribution"]) + self.instance.exec(["mkdir", "-p", f"/var/lib/registry/{mirror.name}"]) + + with open( + config.REGISTRY_DIR / "registry-config.yaml", "r" + ) as registry_template: + src = Template(registry_template.read()) + self.instance.exec( + ["dd", f"of=/etc/distribution/{mirror.name}.yaml"], + input=str.encode(src.substitute(substitutes)), + ) + + with open(config.REGISTRY_DIR / "registry.service", "r") as registry_template: + src = Template(registry_template.read()) + self.instance.exec( + ["dd", f"of=/etc/systemd/system/registry-{mirror.name}.service"], + input=str.encode(src.substitute(substitutes)), + ) + + self.instance.exec(["systemctl", "daemon-reload"]) + self.instance.exec(["systemctl", "enable", f"registry-{mirror.name}.service"]) + self.instance.exec(["systemctl", "start", f"registry-{mirror.name}.service"]) + + @property + def mirrors(self) -> List[Mirror]: + """ + Get the list of mirrors in the registry. + + Returns: + List[Mirror]: The list of mirrors. + """ + return self._mirrors + + @property + def ip(self) -> str: + """ + Get the IP address of the registry. + + Returns: + str: The IP address of the registry. + """ + return self._ip