Skip to content

test(e2e): improve reliability, parallelism, and CI caching - #995

Open
bennyz wants to merge 6 commits into
jumpstarter-dev:mainfrom
bennyz:e2e-reliability
Open

test(e2e): improve reliability, parallelism, and CI caching#995
bennyz wants to merge 6 commits into
jumpstarter-dev:mainfrom
bennyz:e2e-reliability

Conversation

@bennyz

@bennyz bennyz commented Aug 10, 2026

Copy link
Copy Markdown
Member
  • Pin nip.io hostnames in /etc/hosts so DNS flakes don't burn the connect budget
  • Fix kubectl query helpers to not treat stderr as output (was ending Eventually polls early)
  • Add retry, continue-on-failure, and broader hook failure matching to reduce spurious failures
  • Cut fixture overhead: single kubectl apply for pagination, reuse hooks exporter when config matches
  • Enable ginkgo --procs=2: graceful SIGTERM lifecycle, argv-scoped orphan sweep, --client instead of config client use
  • Cache Go modules, controller tools, and load kind images concurrently

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e5f03a6a-2fb8-4257-ac96-e3ea32aecdf5

📥 Commits

Reviewing files that changed from the base of the PR and between 52dc410 and f33d4fa.

📒 Files selected for processing (3)
  • .github/actions/read-tool-versions/action.yaml
  • .github/workflows/e2e.yaml
  • e2e/setup-e2e.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/e2e.yaml
  • e2e/setup-e2e.sh

📝 Walkthrough

Walkthrough

This PR updates CI caching and E2E execution controls. It adds retries and parallelism, improves environment setup and fixture isolation, strengthens Kubernetes polling, and adds tracked exporter process cleanup.

Changes

E2E reliability and CI execution

Layer / File(s) Summary
CI caching and test execution controls
.github/workflows/*, e2e/lib/common.sh, e2e/test/*_test.go
CI jobs cache Go and controller tools. E2E runs support retries, parallel processes, serial suites, and continued failure handling.
E2E environment and image setup
controller/hack/deploy_with_operator.sh, e2e/setup-e2e.sh
Image loading runs concurrently with aggregate failure handling. Supported nip.io domains are added to /etc/hosts.
Explicit clients and bulk fixtures
e2e/test/e2e_test.go
Lease and exporter tests use explicit client arguments and bulk Kubernetes fixture operations.
Polling and Kubernetes query handling
e2e/test/utils.go, e2e/test/exporterset_qemu_test.go
The tests add manifest application and error-safe query helpers, separate polling intervals, and improved readiness checks.
Exporter reuse and process lifecycle
e2e/test/hooks_test.go, e2e/test/utils.go
Exporter configurations are tracked and reused. Shutdown uses graceful termination, process reaping, and selective orphan cleanup.
Controller tool version discovery
.github/actions/read-tool-versions/action.yaml
A composite action reads pinned controller tool versions from controller/Makefile and exports them to GitHub Actions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant E2ETest
  participant ProcessTracker
  participant KubectlQuery
  participant Exporter
  E2ETest->>ProcessTracker: Start or reuse exporter
  ProcessTracker->>Exporter: Track process identity and PID
  E2ETest->>KubectlQuery: Check exporter status and lease
  KubectlQuery-->>E2ETest: Return readiness or offline state
  E2ETest->>ProcessTracker: Stop exporter
  ProcessTracker->>Exporter: Send SIGTERM, then SIGKILL if required
Loading

Poem

I hop through caches, quick and bright,
Two test paths run beneath the light.
Exporters stop with careful grace,
Tracked processes leave no trace.
The rabbit checks each lease with care.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary E2E reliability, parallelism, and CI caching changes.
Description check ✅ Passed The description directly explains the E2E reliability, parallelism, performance, and CI caching changes.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
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 unit tests (beta)
  • Create PR with unit tests

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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
e2e/test/utils.go (1)

353-362: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Return only stdout from MustKubectlApply.

MustKubectlApply writes stdout and stderr into one buffer and returns that buffer. Any kubectl warning on stderr becomes part of the returned value. This is the same failure mode that the KubectlQuery comment below describes. Keep the streams separate, return stdout, and put stderr in the failure message.

♻️ Proposed refactor
 func MustKubectlApply(manifest string) string {
 	cmd := exec.Command("kubectl", "-n", Namespace(), "apply", "-f", "-")
 	cmd.Stdin = strings.NewReader(manifest)
-	var out bytes.Buffer
+	var out, errOut bytes.Buffer
 	cmd.Stdout = &out
-	cmd.Stderr = &out
+	cmd.Stderr = &errOut
 	err := cmd.Run()
-	ExpectWithOffset(1, err).NotTo(HaveOccurred(), "kubectl apply failed: %s", out.String())
+	ExpectWithOffset(1, err).NotTo(HaveOccurred(),
+		"kubectl apply failed: %s%s", out.String(), errOut.String())
 	return strings.TrimSpace(out.String())
 }
🤖 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 `@e2e/test/utils.go` around lines 353 - 362, Update MustKubectlApply to capture
stdout and stderr in separate buffers, return only trimmed stdout, and include
the trimmed stderr alongside the existing kubectl apply failure message when
cmd.Run fails.
🤖 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.

Inline comments:
In `@e2e/setup-e2e.sh`:
- Around line 297-300: Update the /etc/hosts guard in the setup function to
verify grpc.${basedomain}, router.${basedomain}, and login.${basedomain} are all
present before returning. If any required alias is missing, append only the
missing entries while preserving the existing log and success behavior when all
are available.

In `@e2e/test/hooks_test.go`:
- Around line 50-68: Update startHooksExporter so runningConfig is cleared
before tracker.StopAll() when replacing the exporter, ensuring failed startup or
readiness waits cannot leave the previous configuration marked as active; keep
the existing reuse branch and assign the new config only after WaitForExporter
succeeds.

In `@e2e/test/utils.go`:
- Around line 470-488: Add a mutex field to ProcessTracker and use it to
synchronize all accesses to pids and specs: lock track while appending, guard
the reads in TrackedPIDs and sweepOrphans, and lock around StopAll’s pids read
and clear. Ensure each critical section is protected consistently without
holding the lock across unrelated process operations.

---

Nitpick comments:
In `@e2e/test/utils.go`:
- Around line 353-362: Update MustKubectlApply to capture stdout and stderr in
separate buffers, return only trimmed stdout, and include the trimmed stderr
alongside the existing kubectl apply failure message when cmd.Run fails.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f51af15d-2e02-417b-bc32-11419274aed6

📥 Commits

Reviewing files that changed from the base of the PR and between c18ac85 and 796aed7.

📒 Files selected for processing (17)
  • .github/workflows/build-images.yaml
  • .github/workflows/e2e.yaml
  • .github/workflows/lint.yaml
  • .github/workflows/release-operator-installer.yaml
  • controller/hack/deploy_with_operator.sh
  • e2e/lib/common.sh
  • e2e/setup-e2e.sh
  • e2e/test/auth_logging_test.go
  • e2e/test/compat_old_client_test.go
  • e2e/test/compat_old_controller_test.go
  • e2e/test/direct_listener_test.go
  • e2e/test/dut_network_test.go
  • e2e/test/e2e_test.go
  • e2e/test/exit_on_lease_end_test.go
  • e2e/test/exporterset_qemu_test.go
  • e2e/test/hooks_test.go
  • e2e/test/utils.go

Comment thread e2e/setup-e2e.sh Outdated
Comment thread e2e/test/hooks_test.go
Comment thread e2e/test/utils.go
# Retry a failed spec once before failing the job. Retries are reported as
# flakes in the ginkgo summary, so this hides nothing; it only stops a single
# infrastructure hiccup from failing an unrelated PR.
E2E_FLAKE_ATTEMPTS: "2"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nice addition! :)

Comment thread .github/workflows/e2e.yaml Outdated
Comment on lines +347 to +349
env:
KIND_VERSION: v0.27.0
KUSTOMIZE_VERSION: v5.4.1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we somehow define this once in the file?, we have some duplication of it and it will be better in case the version changes to do it once insted track all places.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added Makefile extraction

Comment thread e2e/setup-e2e.sh Outdated
return 0
fi

if grep -q "grpc.${basedomain}" /etc/hosts 2>/dev/null; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since we're matching a literal hostname here, mayve we should we use grep -Fq instead? -F avoids treating characters like . as regex wildcards and makes the match more precise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added

Comment thread e2e/lib/common.sh
# nothing to do with the code under test (a slow DNS answer, a pod scheduled
# late, a router connection dropped). A retried spec is still reported as
# flaky in the summary, so genuine instability stays visible.
local flake_attempts="${E2E_FLAKE_ATTEMPTS:-1}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should set it to 2, since ginkgo treats it as the total number of runs rather than the number of retries, otherwise we'll end up with the same behavior as before.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

we have it set to 2 in the workflow yaml, it will default to 1 only for local testing and such

Comment thread e2e/test/utils.go
Comment on lines +761 to +762
if proc, err := os.FindProcess(pid); err == nil {
go func() { _, _ = proc.Wait() }()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need another Wait() here? It looks like StartExporter* already waits for these processes, so we might end up with two goroutines trying to reap the same process. Could we rely on the existing one instead?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

it's addressed in the comment on L#756

bennyz added 6 commits August 12, 2026 17:11
Host-side clients (jmp, curl) resolve <prefix>.jumpstarter.<IP>.nip.io
against a public resolver on every invocation. A slow or rate-limited
lookup burns the client's entire connect budget and surfaces as
"Timeout connecting to grpc....:8082", which reads like a server fault.

The IP is already embedded in the nip.io name, so serve the same answer
locally. Mirrors the existing dex /etc/hosts handling. Non-nip.io base
domains are left alone.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
RunCmd folds stderr into the returned string. For a value polled inside
Eventually that is wrong: `-o jsonpath={.items[0].metadata.name}` against
an empty list exits non-zero and prints "array index out of bounds", so
Eventually(...).ShouldNot(BeEmpty()) accepts the error text as a result
and stops waiting on the first attempt. A five-minute wait satisfied
itself in 122ms, and the captured error string was then passed to
`kubectl wait exporters.jumpstarter.dev/<error text>`, turning a clean
RBAC denial into an unreadable failure.

Add KubectlQuery, which returns "" when kubectl fails, and use it for the
polled queries. Guard WaitForExporter against being handed a non-name.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Retry a failed spec once before failing the job (E2E_FLAKE_ATTEMPTS),
add ContinueOnFailure to independent suites so a single failure does
not skip all remaining specs, and accept every known beforeLease hook
failure outcome to stop spurious failures from race conditions.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Create pagination fixtures with one kubectl apply instead of ten
spawned jmp processes, and reuse the hooks exporter when the overlay
config is unchanged instead of restarting it every spec.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
…ifecycle

Send SIGTERM instead of SIGKILL so the exporter child unregisters from
the controller. Scope the orphan sweep to argv-matched processes so
parallel ginkgo workers don't reap each other's exporters. Remove the
Serial marker from the core suite by using --client instead of config
client use. Enable E2E_PROCS=2 in CI.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Point setup-go cache-dependency-path at the actual go.sum files so
module downloads are restored. Cache controller/bin (kind, kustomize,
grpcurl) so go-install-tool skips recompilation on cache hit. Load
all container images into the kind cluster concurrently.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
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.

2 participants