Skip to content

feat(e2e): Run e2e tests in kube - #470

Merged
matthewgrossman merged 21 commits into
mainfrom
mgrossman/aircore-844-validate-and-adapt-data-designer-e2e-tests-for-minikubek8s
Jun 26, 2026
Merged

feat(e2e): Run e2e tests in kube#470
matthewgrossman merged 21 commits into
mainfrom
mgrossman/aircore-844-validate-and-adapt-data-designer-e2e-tests-for-minikubek8s

Conversation

@matthewgrossman

@matthewgrossman matthewgrossman commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Validate and adapt the data designer and jobs e2e tests for Kubernetes, fix a launcher log-loss bug, and add a CI job that runs the full e2e suite against a Kind cluster on every PR.

Data Designer K8s fix

  • DD's bridge reads NEMO_JOB_PERSISTENT_JOB_STORAGE_PATH from the environment, but on K8s the env var is only injected when the step declares it in compile(). DD never declared it, so the bridge crashed with KeyError on every K8s run.
  • Rather than adding the env var to DD's compile() (which would mount a PVC that DD never uses), we made StoragePaths.persistent a guarding property that raises a clear RuntimeError when accessed without being provisioned. The bridge and dispatcher now tolerate the missing env var by passing None.
  • This eliminates the footgun: forgetting the env var in compile() no longer crashes the pod — it raises a descriptive error only if the job actually tries to use persistent storage.

Jobs launcher log reliability fix

Two bugs in the Go jobs-launcher caused ~10-20% of fast-exiting K8s job pods to silently lose their OTLP logs:

  1. Pipe read race: cmd.Wait() was called before stdout/stderr reader goroutines finished. Go's exec.Cmd.Wait() closes pipes on return, so readers got "file already closed" and missed the output entirely. Fixed by calling wg.Wait() before cmd.Wait().
  2. os.Exit skipping defers: The cobra Run callback called os.Exit(exitCode), which skipped the deferred otelShutdown that flushes the OTLP batch processor. Fixed by stashing the exit code and calling os.Exit from Execute() after cobra returns.

E2e test K8s compatibility

  • test_job_passing_data_between_steps: Added persistent storage env var to step environment (K8s backend requires explicit declaration).
  • Container-backend tests (pause/resume, invalid_image): Changed from unconditional @pytest.mark.skip to @pytest.mark.container_only — now run on K8s, skip on subprocess.
  • test_job_pause_resume: Cancel after verifying resume works instead of waiting for sleep 300 (K8s restarts the timer on pod re-create).
  • Smoke health tests: Use /status (works on both subprocess and K8s) instead of /health/ready (internal-only on K8s).
  • Added subprocess_only and container_only pytest markers with auto-skip logic in conftest.
  • Auth tests marked subprocess_only (require auth-enabled platform config).
  • Artifact download timeout increased from 60s to 300s for K8s pod scheduling.

CI: kind-cpu-e2e job + setup-kind-cluster composite action

  • New kind-cpu-e2e workflow job runs the full e2e/ directory against a Kind cluster (not just hardcoded files).
  • Extracted setup-kind-cluster composite action from kind-cpu-smoke to deduplicate ~165 lines of cluster setup (kind/kubectl/helm install, cluster creation, gateway verification, image pre-pull, helm deploy, API readiness).
  • Added to ci-status so it gates PRs.

Test plan

  • 14/14 jobs + data designer tests pass on minikube (1 skipped: additional_volumes)
  • 62/66 full e2e suite passes on minikube (4 skipped: 3 auth, 1 additional_volumes)
  • Subprocess mode: no regressions (container_only tests properly skipped)
  • Launcher log fix: 0/50 log misses on minikube (previously 4-7/30)
  • Go launcher unit tests pass
  • Python unit tests pass (dispatcher, DD create job)
  • kind-cpu-e2e CI job passes
  • kind-cpu-smoke CI job passes
  • python-e2e-test CI job passes

Closes AIRCORE-844
Closes AIRCORE-845

🤖 Generated with Claude Code

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@matthewgrossman
matthewgrossman requested review from a team as code owners June 25, 2026 19:24
@github-actions github-actions Bot added the fix label Jun 25, 2026
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a composite Kind setup action, rewires Kind CPU CI jobs to use it, updates E2E storage and timeout behavior, and changes jobs-launcher exit handling.

Changes

Kind CI setup

Layer / File(s) Summary
Action interface and toolchain
.github/actions/setup-kind-cluster/action.yaml
The composite action declares inputs, frees disk space, installs Kind and kubectl, and wires Helm and uv setup.
Cluster bootstrap and readiness
.github/actions/setup-kind-cluster/action.yaml
The action starts Kind, sets the namespace, verifies Gateway API resources, and pre-pulls NMP images.
Platform install and API wait
.github/actions/setup-kind-cluster/action.yaml
The action installs NeMo Platform with image overrides, prints namespace diagnostics on failure, and waits for /cluster-info on NMP_E2E_CLUSTER_URL.
Smoke job wiring
.github/workflows/ci.yaml
The kind-cpu-smoke job delegates setup to the composite action, runs the smoke pytest against NMP_E2E_CLUSTER_URL, and keeps log collection, artifact upload, and cluster cleanup.
E2E job wiring and status
.github/workflows/ci.yaml
The kind-cpu-e2e job uses the composite action, runs the jobs and data-designer suite against NMP_E2E_CLUSTER_URL, uploads report-kubernetes-e2e.xml, and adds the job to ci-status needs.

E2E harness updates

Layer / File(s) Summary
Persistent job storage path
e2e/test_jobs.py, plugins/nemo-data-designer/src/.../jobs/create.py
DEFAULT_JOB_STORAGE_PATH is added, CreateJob.compile() sets PERSISTENT_JOB_STORAGE_PATH_ENVVAR, and the shared-storage E2E test passes the same path to both steps.
Subprocess gating and pause/resume
e2e/test_jobs.py
The test module adds _skip_subprocess from NMP_BASE_URL, updates test_job_pause_resume to cancel the job after resume, revises the additional-volumes skip reason, and replaces the invalid-image skip.
Data-designer timeout handling
e2e/test_data_designer.py
pytestmark adds a 600-second timeout, and _download_artifacts_when_ready uses a configurable timeout parameter.

Jobs launcher OTEL and exec flow

Layer / File(s) Summary
Exit coordination
services/core/jobs/jobs-launcher/cmd/*.go
runCmd stores the subprocess exit code in launcherExitCode, and Execute() exits with that code after rootCmd.Execute() returns.
Output draining and tests
services/core/jobs/jobs-launcher/cmd/*.go
The launcher removes logger-provider flushing, drops the runExec loggerProvider parameter, waits for stdout/stderr tailers before cmd.Wait(), and updates the runExec test call sites.

Sequence Diagram(s)

sequenceDiagram
  participant "setup-kind-cluster action" as SetupKind
  participant "setup_local_kind_cpu.sh" as SetupLocalKind
  participant kubectl as Kubectl
  participant "prepull_kind_images.sh" as Prepull
  participant "install_helm_e2e.sh" as InstallHelm
  participant "wait_for_api.sh" as WaitForApi
  SetupKind->>SetupLocalKind: start Kind cluster
  SetupKind->>Kubectl: set namespace and verify Gateway API
  SetupKind->>Prepull: pre-pull NMP images
  SetupKind->>InstallHelm: install NeMo Platform
  SetupKind->>WaitForApi: wait for /cluster-info
Loading

Possibly related PRs

Suggested labels

ci

Suggested reviewers

  • svvarom
  • a2bondar
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title matches the main change: running E2E tests against Kubernetes/Kind.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mgrossman/aircore-844-validate-and-adapt-data-designer-e2e-tests-for-minikubek8s

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: 1

🤖 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/test_jobs.py`:
- Around line 380-381: The subprocess skip marker in test_jobs.py is using
NMP_BASE_URL as a proxy for backend mode, but that setting is only for
external-vs-local platform selection. Update the _is_subprocess_mode /
_skip_subprocess logic to check the real backend or cluster configuration used
by the test setup in e2e/conftest.py, so container-backed Kind/Docker runs are
not incorrectly skipped. Use the existing test configuration symbols around the
subprocess/container backend selection to locate the right signal.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3279eb63-e499-4810-912c-982644780ea6

📥 Commits

Reviewing files that changed from the base of the PR and between dc86134 and 69c80c7.

📒 Files selected for processing (6)
  • .github/actions/setup-kind-cluster/action.yaml
  • .github/workflows/ci.yaml
  • e2e/k8s/scripts/install_nmp_e2e.sh
  • e2e/test_data_designer.py
  • e2e/test_jobs.py
  • plugins/nemo-data-designer/src/nemo_data_designer_plugin/jobs/create.py

Comment thread e2e/test_jobs.py Outdated
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 21330/27927 76.4% 61.4%
Integration Tests 12357/26696 46.3% 19.8%

matthewgrossman and others added 3 commits June 25, 2026 12:40
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…iteration

All wait_for_job_logs calls now use a consistent 240s timeout to handle
K8s OTLP log batching latency (previously ranged 30-120s, causing flakes).

Temporarily pins kind-cpu-e2e to a known image tag to skip the ~10min
image build while iterating on test changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…pods

Two bugs caused ~10-20% of short-lived K8s job pods to lose their logs:

1. **Pipe read race**: cmd.Wait() was called before the stdout/stderr
   reader goroutines finished. Go's exec.Cmd.Wait() closes pipes on
   return, so the readers would get "file already closed" and miss the
   output entirely. Fixed by calling wg.Wait() before cmd.Wait().

2. **Async batch export**: The BatchProcessor queued log records and
   exported them asynchronously. ForceFlush triggered the export but
   returned before the HTTP request completed, and os.Exit killed the
   in-flight request. Switched to SimpleProcessor which exports each
   record synchronously — appropriate for the launcher's short-lived
   single-job use case.

Verified: 0/50 log misses on minikube (previously 4-7/30).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

@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: 1

🤖 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 `@services/core/jobs/jobs-launcher/cmd/run.go`:
- Around line 254-262: The current wait order in the command launcher can hang
when a child keeps stdout/stderr open, so adjust the flow in run.go around
cmd.Wait and wg.Wait. Wait for the process to exit first using cmd.Wait to
capture the main process exit code, then drain the output readers, and add a
bounded fallback so the launcher cannot block forever if EOF never arrives. Keep
the change localized to the launcher logic that coordinates cmd.Wait, wg.Wait,
and the log reader goroutines.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8e26e3ff-d1a1-4f92-b63c-58a8d9333ec7

📥 Commits

Reviewing files that changed from the base of the PR and between d9d8b37 and 8c13776.

📒 Files selected for processing (3)
  • services/core/jobs/jobs-launcher/cmd/otel.go
  • services/core/jobs/jobs-launcher/cmd/run.go
  • services/core/jobs/jobs-launcher/cmd/run_test.go

Comment thread services/core/jobs/jobs-launcher/cmd/run.go
Comment thread plugins/nemo-data-designer/src/nemo_data_designer_plugin/jobs/create.py Outdated
matthewgrossman and others added 4 commits June 26, 2026 09:34
- Lower log wait timeouts from 240s to 60s (launcher fix makes logs
  available immediately)
- Remove install_nmp_e2e.sh (K8s Developer Guide already covers this)
- Remove pinned image tag from kind-cpu-e2e, restore
  build-cpu-smoke-images dependency
- Remove unused cluster_url output from setup-kind-cluster action
  (consumers read NMP_E2E_CLUSTER_URL from env directly)
- Update K8s Developer Guide known issues to reflect fixes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…rocessor

Revert to BatchProcessor (better throughput for jobs with many log lines)
now that the defer actually runs. The root cause of lost logs was two-fold:

1. os.Exit in the cobra Run callback skipped deferred OTEL shutdown
2. cmd.Wait() closed stdout/stderr pipes before readers finished

Fix (1) by stashing the exit code and calling os.Exit from Execute()
after cobra returns and all defers complete. Fix (2) was already in
place (wg.Wait before cmd.Wait from prior commit).

Verified: 0/50 log misses on minikube with BatchProcessor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Revert all wait_for_job_logs timeouts to their original values since the
launcher log fix makes them unnecessary. Fix stale comment referencing
the synchronous processor (we're using BatchProcessor).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
- Add pytest markers: subprocess_only (skipped when NMP_BASE_URL set)
  and container_only (skipped unless NMP_BASE_URL set)
- CI kind-cpu-e2e now runs all e2e/ tests instead of hardcoded file list
- Fix smoke health tests to use /status (works on both subprocess and K8s)
  instead of /health/ready which is internal-only on K8s
- Mark auth tests subprocess_only (require auth-enabled platform config)
- Replace _skip_subprocess pattern in test_jobs.py with container_only marker

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@matthewgrossman matthewgrossman changed the title fix: validate and adapt data designer + jobs e2e tests for K8s feat(e2e): Run e2e tests in kube Jun 26, 2026
@github-actions github-actions Bot added the feat label Jun 26, 2026
matthewgrossman and others added 9 commits June 26, 2026 10:20
…a-designer-e2e-tests-for-minikubek8s

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
The K8s backend now unconditionally provisions persistent job storage
(PVC mount + env var) when the cluster has a PVC configured, matching
the subprocess backend which always creates a persistent directory.

This eliminates the need for plugins to declare
NEMO_JOB_PERSISTENT_JOB_STORAGE_PATH in their compile() environment
list — the backend handles it as infrastructure, not application
concern. DD's compile() reverts to environment=[] since it never used
persistent storage at runtime.

Plugins that previously declared the env var (evaluator, agents,
anonymizer, etc.) continue to work — their explicit declaration
overrides the auto-provisioned default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Instead of auto-provisioning PVC mounts for every job (wasteful) or
crashing with KeyError when the env var is missing (footgun),
StoragePaths.persistent is now a property that raises a clear
RuntimeError if accessed when not provisioned.

Changes:
- StoragePaths: dataclass → class with property that guards access
- dispatcher.py: tolerate missing PERSISTENT env var, pass None
- bridge.py (DD): use os.environ.get(), pass None when absent
- K8s backend: revert auto-provisioning, add TODO for first-class
  requires_persistent_storage field on job spec
- test_dispatcher.py: update test to verify property raises on access

Jobs that declare the env var in compile() (evaluator, agents, etc.)
work unchanged. Jobs that don't (DD) no longer crash — they just
can't access ctx.storage.persistent without getting a descriptive
error telling them what to do.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…name CI job

- ngc_api_key fixture: skip test when key is missing or a CI placeholder
  (previously asserted and passed with "not-used-for-*" values that
  caused 400 errors at runtime)
- test_job_pause_resume: mark flaky with reruns=2 (pod can error before
  pause request arrives on Kind due to scheduling timing)
- Rename CI job from "Kind CPU e2e (jobs + data-designer)" to
  "Kind CPU e2e" since it now runs the full e2e/ suite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Comment thread .github/workflows/ci.yaml
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Comment thread .github/workflows/ci.yaml Outdated

@crookedstorm crookedstorm 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.

LGTM from the standpoint of the github segments with concerns mentioned inline for discussion and thought, not blocking.

@mikeknep mikeknep 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.

StoragePaths changes LGTM!

Comment thread services/core/jobs/jobs-launcher/cmd/run.go
…a-designer-e2e-tests-for-minikubek8s

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@matthewgrossman
matthewgrossman added this pull request to the merge queue Jun 26, 2026
Merged via the queue into main with commit 470971a Jun 26, 2026
52 checks passed
@matthewgrossman
matthewgrossman deleted the mgrossman/aircore-844-validate-and-adapt-data-designer-e2e-tests-for-minikubek8s branch June 26, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants