Skip to content

fix(e2e): deliver image via k3d direct mode (root cause of the tools-node race)#186

Merged
sunib merged 1 commit into
mainfrom
fix/e2e-image-import-direct-mode
Jul 2, 2026
Merged

fix(e2e): deliver image via k3d direct mode (root cause of the tools-node race)#186
sunib merged 1 commit into
mainfrom
fix/e2e-image-import-direct-mode

Conversation

@sunib

@sunib sunib commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #185, with the root cause now pinned down in the k3d v5.9.0 source.

Why the import was failing (the actual mechanism)

Two k3d defects combine in the default tools-node mode:

  1. Exec-status race (pkg/runtimes/docker/node.go, executeInNode): k3d polls ContainerExecInspect every 1s and treats Running=false, ExitCode=0 as success. Docker's exec lifecycle is asynchronous, so a poll can misread the save-image exec as complete before the tarball exists on the shared image volume. In the failing run (28610319679) the "save" appeared to finish within the same second (passing runs take ~2s), then server-0's ctr image import hit no such file or directory.
  2. Swallowed node errors (pkg/client/tools.go, importWithToolsNode): the per-node import failure is only Errorf-logged, never returned — k3d prints "Successfully imported" and exits 0. Only our pin/verify step told the truth.

What this changes

k3d image import -m direct: streams docker save straight into each node's ctr image import stdin (loadImageFromStream) — no tarball, no shared-volume timing window, and node import failures are propagated through an errgroup. The retry from #185 now also covers a loud nonzero k3d exit, and the pin step remains the final proof the image landed.

Verified on a live 4-node cluster

  • -m direct with the real project image: 5.4s, image present + pinned in all 4 nodes.
  • Full task test-e2e with the stamp cleared (forcing the new path, including the image-refresh rebuild→reload cycles): 53/53 specs passed, 8 skipped, 9m15s.
  • Gotcha discovered and documented in-script: direct mode + k3d's hardcoded ctr --all-platforms fails (content digest not found) for multi-arch images saved by a containerd-store docker — the stream lacks foreign-platform blobs (file-based import of the same tar fails identically, so it's the save format, not stdin). PROJECT_IMAGE is always the single-arch controller image, so this doesn't apply to us.

Validation

  • task lint: 0 issues
  • task test: pass (coverage 73.8%, within tolerance of 73.9% baseline)
  • task test-e2e: pass (53/53)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved image loading reliability during end-to-end setup, reducing transient failures when importing test images.
    • Added clearer retry handling so failed import attempts are detected and retried more consistently.

…ation

Root cause found in k3d v5.9.0 source: executeInNode polls
ContainerExecInspect and trusts Running=false/ExitCode=0, but Docker's
exec lifecycle is async — the poll can misread the tools-node save-image
exec as complete before the tar exists on the shared volume, and
importWithToolsNode only logs (never returns) the resulting per-node
ctr import failure, so k3d exits 0 claiming success.

Direct mode streams docker save into each node's ctr stdin: no tarball,
no shared-volume window, and node import failures are propagated.
Verified on a live 4-node cluster with the project image. Caveat
documented in-script: direct + ctr --all-platforms cannot handle
multi-arch images saved by a containerd-store docker; PROJECT_IMAGE is
always single-arch.

The retry now also covers a loud nonzero exit from k3d, and the pin
step remains the final verification that the image really landed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The import_image retry loop in hack/e2e/load-image.sh was changed to use k3d image import -m direct -c "${CLUSTER_NAME}". Per-node pin verification now runs only when the import command succeeds; failed imports are marked with a warning and skip pin verification for that attempt.

Changes

Image import script update

Layer / File(s) Summary
Direct import mode and conditional pin verification
hack/e2e/load-image.sh
import_image switches to k3d image import -m direct, only runs pin verification on successful import, and marks failed attempts with a warning while retaining the 3-attempt retry logic.

Estimated code review effort: 2 (Simple) | ~10 minutes

Related PRs: None found.

Suggested labels: None

Suggested reviewers: None

🐰 A hop, a skip, direct we go,
No more waiting on the tools-node's slow flow,
Pin each node, check twice, retry with care,
If import fails, a warning fills the air.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: switching E2E image delivery to k3d direct mode to fix the tools-node race.
Description check ✅ Passed The description is detailed and covers the change, rationale, and testing, though it omits some template sections like Type of Change and Related Issues.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/e2e-image-import-direct-mode

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
hack/e2e/load-image.sh (1)

165-204: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle an empty node list here. If cluster_node_names produces no matches, this returns success without pinning anything. Fail fast when no nodes are discovered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hack/e2e/load-image.sh` around lines 165 - 204, The import_image() retry loop
currently succeeds even when cluster_node_names returns no nodes, because the
pinning loop never runs and pinned stays true. Add an explicit check before
iterating nodes: if cluster_node_names yields an empty list, treat it as a
failure, log an error, and continue retrying or return nonzero instead of
succeeding. Keep the fix localized to import_image() and the
cluster_node_names/pin_imported_image flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@hack/e2e/load-image.sh`:
- Around line 165-204: The import_image() retry loop currently succeeds even
when cluster_node_names returns no nodes, because the pinning loop never runs
and pinned stays true. Add an explicit check before iterating nodes: if
cluster_node_names yields an empty list, treat it as a failure, log an error,
and continue retrying or return nonzero instead of succeeding. Keep the fix
localized to import_image() and the cluster_node_names/pin_imported_image flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 49328ea8-0909-43be-9c3b-a64ab4d2b584

📥 Commits

Reviewing files that changed from the base of the PR and between e426206 and cfa5378.

📒 Files selected for processing (1)
  • hack/e2e/load-image.sh

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@sunib
sunib merged commit 31abd14 into main Jul 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant