README
¶
gantry - end-to-end test suite
This directory holds the kind-based integration suite. It boots a real Kubernetes cluster on Docker, builds the gantry container image, and deploys the DaemonSet. The current smoke scenario asserts rollout readiness, installs containerd mirror config, pulls through Gantry on one worker, then verifies a second worker reuses warmed content through peer fetch.
Status
- ✅ Smoke test - DaemonSet rolls out, all pods reach
/readyz=200. - ✅ Pull-through + warm peer reuse - installs
hosts.toml, pullsregistry.k8s.io/e2e-test-images/agnhost:2.39on two workers, and asserts advertise + peer-fetch metrics increase. - ✅ Multi-arch index digest with missing media-type metadata - builds a local
amd64/arm64 scratch-image fixture, imports its OCI archive on one node,
removes image metadata, restarts the seed Gantry process, and verifies the
bare index response. A side-loaded HTTPS registry answers auth probes but
returns 404 for all image content, so origin fallback cannot hide a failure.
This scenario does not contact
registry.k8s.iofor setup or authentication. - ✅ Cold-start designated origin puller - two concurrent pulls
across two workers, asserts the per-digest HRW invariant: every
blob's
please_pull servedlog line appears on at most one pod (no digest is origin-pulled twice cluster-wide). HRW is per-digest, so work naturally distributes across nodes - different blobs of the same image can land on different pullers; what's prevented is N nodes pulling the same blob. - ✅ Eviction / stale-provider recovery - pull then evict on node A,
trigger pull on node B, assert peer fetch sees
outcome="notfound",gantry_stale_provider_filtered_totalincreases, origin fallback succeeds, and pod becomes ready. - ✅ Private / authenticated registry - htpasswd-protected
registry:2fixture proves credential flow end-to-end through the mirror. - ✅ Lease lifecycle - gantry-managed containerd leases visible after background pull.
- ✅ NetworkPolicy hardening - kind-friendly NP applied, pod still
reaches
/readyz=200. - ✅ Containerd socket access - log +
/readyzprobe confirms the socket is reachable on the kind node image. Real production node images must still validate this on their target node pool. - ✅ Containerd restart recovery - restarts containerd on a worker, verifies the socket inode changes, and confirms the same Gantry pod reconnects and serves a subsequent image pull without restarting.
Additional scenarios listed below should each land as their own commit.
Prereqs
The harness shells out to standard CLIs; no extra Go deps. Install:
Running
make e2e-gantry # boot kind, build+load image, deploy, run tests, tear down
The root Makefile defaults CONTAINER_ENGINE to podman. Set
CONTAINER_ENGINE=docker to use Docker instead.
To keep the cluster running after the test (for debugging), set
E2E_KEEP=1:
E2E_KEEP=1 make e2e-gantry
# ...
kind delete cluster --name gantry-e2e
Test logs land in e2e/.artifacts/<test-name>.log. The harness also
dumps kubectl describe pods + container logs on failure.
How it works
The harness (harness_e2e.go) is a small Go-driven wrapper over the
prereq CLIs:
| Step | What it does |
|---|---|
bootCluster() |
kind create cluster --config kind-config.yaml |
buildAndLoadImage() |
builds images/gantry/Containerfile as docker.io/library/gantry:e2e, saves an image archive, then loads the archive into kind |
applyManifests() |
renders deploy/gantry/chart with the pinned Helm binary, rewrites the DaemonSet image policy for the side-loaded image, and applies the core manifests (NetworkPolicy is intentionally not applied) |
waitForRollout() |
polls kubectl rollout status ds/gantry -n unbounded-system |
checkReadyz() |
port-forwards one Gantry pod and curls /readyz on port 9095 |
| pull-through check | installs hosts.toml on each kind node, removes the test image from node-local containerd, schedules a pull on worker A, waits for advertise metrics, evicts the seed copy that cold start left on worker B, then schedules the same image on worker B and waits for p2p_peer_fetch_total{outcome="hit"} on worker B's Gantry pod |
| multi-arch cache-miss check | builds static pause binaries for amd64 and arm64, creates a deterministic OCI archive, imports it only on the seed, labels its graph as GC roots, removes the image record, and restarts the seed Gantry pod. Verifies the index response, then pulls from another node through a local HTTPS registry that serves auth probes but never image content |
teardown() |
kind delete cluster (skipped when E2E_KEEP=1) |
The smoke test also installs a hosts.toml mirror entry for
registry.k8s.io, removes the test image from kind nodes, pulls
registry.k8s.io/e2e-test-images/agnhost:2.39 on one worker, waits
for Gantry advertisement, then pulls the same image on a second worker
and asserts peer-fetch metrics increase.
The kind config (kind-config.yaml) declares one control-plane + two
worker nodes - enough to exercise multi-peer coord paths in future
scenarios.
Local multi-arch fixtures
fixture/ builds OCI indexes, manifests, configs, and executable layers without
public base images. Tar timestamps, JSON encoding, and architecture ordering are
fixed; Go binaries are built with CGO_ENABLED=0, -trimpath, and an empty build
ID. The same source and Go toolchain produce the same image digests.
The workload archive is imported directly into the seed's containerd. The local
registry uses a separately side-loaded scratch image with a disjoint content
graph, so loading the registry cannot warm the requester. Its generated TLS
certificate is trusted by both Gantry and node-local containerd; production
authentication behavior is unchanged. It answers /v2/ with anonymous success
and rejects every content request with 404.
The multi-arch scenario is registry-independent after normal kind/Gantry image setup. Other scenarios still use the public agnhost image. Cold setup still needs the normal kind node image, Gantry build dependencies, and image bases.
Pure fixture tests do not require a cluster:
go test ./e2e/gantry/fixture/...
Build tag
The integration harness and cluster scenarios carry //go:build e2e.
Default go test ./... skips the cluster suite but runs the pure fixture tests.
Run the integration suite with:
go test -tags=e2e -count=1 -timeout=120m -v ./e2e/gantry
The make e2e-gantry target sets -tags=e2e and a generous timeout for you.
Future scenarios
The scenarios below are still gaps. Each should land as a focused commit.
- Origin failure -> cluster-wide circuit (§5.8). Inject 401/429 via a mock upstream and assert peers honor the propagated cooldown.
- NF5 pod-kill simulation.
kubectl delete podmid-pull and assert the designated-puller takeover metric increments (p2p_designated_puller_takeover_total). - DHT-empty cold-start hold (§7.7). Start with one node, pull, and
assert no ungated origin fallback before
bootstrap_windowelapses.
Non-goals
- Canary / mixed rollout / rollback. Running a subset of nodes on
the new image while the rest run the previous release is
unsupported and there is no rollback path.
storage_mode=containerdis the only accepted storage mode (plan §Phase 8 removed the alternativegantry-cachehostPath backend); a mixed-version rollout that mixes incompatible coord / advertise / cdsub semantics is left to the operator's pre-deploy validation rather than an in-cluster compatibility envelope.
Caveats
- The kind cluster boot takes ~60–120 s. The Makefile target reserves a 10-minute test timeout to absorb that.
- The harness pins
kindest/node:v1.34.0, which carries containerd v2.1. Its containerd uses namespacek8s.io, matching the gantrycontainerd_namespacedefault - no extra config is needed. - Containerd socket access is mandatory. The default DaemonSet runs with
UID 65532 and primary GID 0 to work with common
root:root 0660containerd sockets, and the kind smoke covers that path. Clusters that use a dedicated socket group should patchrunAsGroup/fsGroup; clusters that forbid GID 0 need a site-specific ownership or privilege strategy. Because readiness pings the containerd content store, misconfigured permissions surface as a permanent 503 -TestE2E_ ContainerdSocketAccessproves the kind node image, but the target production node pool must be validated separately before broad prod.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package fixture builds local OCI images for Gantry integration tests.
|
Package fixture builds local OCI images for Gantry integration tests. |
|
cmd/pause
command
The fixture workload only needs to remain running after containerd unpacks it.
|
The fixture workload only needs to remain running after containerd unpacks it. |
|
cmd/registry
command
|