Skip to content

fix(nvca): preserve MiniService spec when saving workload config - #626

Open
sbaum1994 wants to merge 3 commits into
mainfrom
sbaum/fix/miniservice-workload-config-patch
Open

fix(nvca): preserve MiniService spec when saving workload config#626
sbaum1994 wants to merge 3 commits into
mainfrom
sbaum/fix/miniservice-workload-config-patch

Conversation

@sbaum1994

@sbaum1994 sbaum1994 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Customer Summary

NVCA now reads and saves per-workload configuration without removing required MiniService fields, allowing affected Helm workloads to continue installation.

TL;DR

Persist spec.workloadConfig through the MiniService compatibility serializer and apply only that field through a serializer-independent server-side apply payload owned by miniservice-controller.

Additional Details

MiniServiceSpec has custom JSON compatibility logic for canonical and legacy ICMS request names. The newer WorkloadConfig field was missing from that wire representation, so NVCA could neither serialize nor deserialize it.

saveWorkloadConfig also constructed a partial typed MiniService and force-applied it with the controller's existing field manager. The compatibility serializer emitted zero values for required fields such as spec.namespace, spec.icmsRequestName, and spec.helmChartConfig, while omitting the intended workload config. This left reconciliation terminally stuck.

This change:

  • Adds WorkloadConfig to the compatibility JSON representation in both directions.
  • Constructs an unstructured SSA payload containing only apiVersion, kind, metadata.name, and spec.workloadConfig.
  • Retains client.Apply, FieldOwner("miniservice-controller"), and deliberate force ownership while omitting resourceVersion, so unrelated concurrent spec updates are outside the patch and do not cause whole-object conflicts.
  • Adds regression coverage for JSON round trips, exact SSA payload contents and options, and preservation of namespace, request name, and Helm configuration.

No dependencies or generated API types changed.

Why serializer-independent targeted SSA

A typed partial MiniService is not a targeted payload in this codebase: its custom MiniServiceSpec.MarshalJSON method serializes zero-valued namespace, request-name, and Helm fields. Merely adding WorkloadConfig to that serializer would persist the flag but would still send those unrelated zero values.

A JSON merge patch could safely update only spec.workloadConfig, but it would replace the original declarative ownership model. Building an unstructured apply object preserves the intended Kubernetes server-side apply contract: miniservice-controller declares ownership only of spec.workloadConfig, force ownership resolves conflicts on that controller-owned field, and unrelated spec fields remain managed independently. The payload intentionally omits resourceVersion so field-level SSA semantics, rather than whole-object optimistic locking, govern concurrency. See Kubernetes Server-Side Apply.

Observed test sequence

The AWS BYOC test exercised this sequence:

  1. Create a Helm function whose deployment configuration sets nvcfWorkloadConfig.featureFlags.StatusByWorkerReadiness=true.
  2. Render the chart successfully, including the nvcf-workload-config control ConfigMap plus the workload objects.
  3. Decode and remove that control ConfigMap from the objects that will be applied to the workload namespace.
  4. Persist the decoded flag into MiniService.spec.workloadConfig before applying the remaining workload objects.
  5. Wait for the function to become ACTIVE, then establish a successful inference baseline.
  6. Cordon and drain one decode-worker node and verify serving and health behavior throughout the degraded window.

The observed failure occurred at step 4, before workload application, baseline inference, or cordon/drain. The workload-config patch emptied spec.namespace and spec.icmsRequestName while failing to persist spec.workloadConfig. The next reconcile returned miniservice <name> has no namespace, the MiniService remained Installing, and the function remained DEPLOYING until the test timeout.

For the Reviewer

Please focus on:

  • pkg/apis/nvca/v1alpha1/miniservice_json.go for compatibility serialization completeness.
  • internal/miniservice/reconcile.go for the serializer-independent targeted SSA payload and retained field ownership.
  • TestSaveWorkloadConfigUsesTargetedSSA for the exact request assertion. The test proves that namespace, icmsRequestName, helmChartConfig, status, and resourceVersion are absent because it compares the complete decoded patch against the expected target-only object.

For QA

Local verification completed:

go test ./internal/miniservice -run '^TestSaveWorkloadConfigUsesTargetedSSA$' -count=1 -ldflags '...version=v25.8.0'
go test ./internal/miniservice/... ./pkg/apis/nvca/v1alpha1/... -count=1 -ldflags '...version=v25.8.0'
golangci-lint run -c .golangci.yml --new-from-rev=origin/main

The full affected package run used Kubernetes 1.34.1 envtest assets and passed. Changed-code lint reported 0 issues. The unfiltered full-tree lint currently reports 36 pre-existing findings on origin/main; none are on changed lines.

Runtime QA is still needed before this PR is ready to merge. Build and deploy the patched NVCA, install a Helm workload that renders nvcf-workload-config with StatusByWorkerReadiness: true, and verify:

  • spec.workloadConfig contains the flag.
  • Existing MiniService spec fields remain unchanged.
  • miniservice-controller owns spec.workloadConfig without owning unrelated spec fields in metadata.managedFields.
  • Reconciliation proceeds past installation and applies the workload objects.
  • Function cleanup removes its namespace.

Issues

Fixes #625

Checklist

  • I am familiar with the Contributing Guidelines.
  • I have signed off my commits for Developer Certificate of Origin (DCO) compliance.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

Serialize workload config through the MiniService compatibility wire type and persist it with a merge patch so unrelated spec fields retain their values and field ownership.

Closes #625

Signed-off-by: Stephanie Baum <sbaum@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds WorkloadConfig to MiniService compatibility JSON handling and updates saveWorkloadConfig to apply only spec.workloadConfig with server-side apply. Tests cover JSON round trips and preservation of existing specification fields.

Changes

Workload configuration persistence

Layer / File(s) Summary
WorkloadConfig compatibility serialization
src/compute-plane-services/nvca/pkg/apis/nvca/v1alpha1/miniservice_json.go, src/compute-plane-services/nvca/pkg/apis/nvca/v1alpha1/miniservice_json_test.go
The compatibility JSON payload now includes WorkloadConfig. Marshal and unmarshal operations preserve workload feature flags.
Targeted workload configuration apply
src/compute-plane-services/nvca/internal/miniservice/reconcile.go, src/compute-plane-services/nvca/internal/miniservice/reconcile_test.go
saveWorkloadConfig now applies a payload containing only spec.workloadConfig with forced ownership. It deep-copies the applied configuration and updates the caller resource version. Tests verify preservation of existing specification fields.
Shared MiniService owner kind
src/compute-plane-services/nvca/internal/miniservice/revision.go, src/compute-plane-services/nvca/internal/miniservice/transport_tls.go
Owner reference creation and recognition now use the shared miniServiceKind constant.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: kristinapathak, estroz

Sequence Diagram(s)

sequenceDiagram
  participant Reconciler
  participant KubernetesAPI
  participant MiniService
  Reconciler->>KubernetesAPI: Apply payload containing spec.workloadConfig
  KubernetesAPI->>MiniService: Persist targeted field with forced ownership
  KubernetesAPI-->>Reconciler: Return applied object and resource version
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title uses valid Conventional Commits syntax and accurately describes the MiniService workload-config preservation fix.
Linked Issues check ✅ Passed The implementation persists WorkloadConfig, preserves existing MiniService spec fields, and adds regression coverage for the required behavior [#625].
Out of Scope Changes check ✅ Passed All changes support workload-config persistence or centralize the MiniService kind identifier used by related owner-reference logic.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sbaum/fix/miniservice-workload-config-patch

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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

@sbaum1994
sbaum1994 marked this pull request as ready for review August 3, 2026 22:37
@sbaum1994
sbaum1994 requested a review from a team as a code owner August 3, 2026 22:37
Apply workload config through a serializer-independent unstructured SSA payload so miniservice-controller owns only spec.workloadConfig. Assert that the exact payload omits unrelated spec fields, status, and resourceVersion.

Refs #625

Signed-off-by: Stephanie Baum <sbaum@nvidia.com>

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

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 `@src/compute-plane-services/nvca/internal/miniservice/reconcile.go`:
- Around line 481-487: Add outbound tracing around the r.Client.Patch call in
the reconciliation flow, using the existing context and preserving propagation
through the patch operation. Ensure the span is ended and records an error
status when Patch returns an error, while retaining the current error-handling
behavior.
🪄 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: 381a9a6e-f364-47e3-8d00-ac595c1eda39

📥 Commits

Reviewing files that changed from the base of the PR and between ebe8cc1 and 8c35d15.

📒 Files selected for processing (4)
  • src/compute-plane-services/nvca/internal/miniservice/reconcile.go
  • src/compute-plane-services/nvca/internal/miniservice/reconcile_test.go
  • src/compute-plane-services/nvca/internal/miniservice/revision.go
  • src/compute-plane-services/nvca/internal/miniservice/transport_tls.go

Comment on lines +481 to +487
if err := r.Client.Patch(
ctx,
applyPatch,
client.Apply,
client.FieldOwner(managedByValue),
client.ForceOwnership,
); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect local tracing patterns and determine whether Reconciler.Client is already instrumented.
rg -n -C 3 'saveWorkloadConfig|r\.Client\.Patch|otel\.Tracer|tracer\.Start|span\.RecordError' \
  src/compute-plane-services/nvca/internal/miniservice

rg -n -C 3 'Reconciler\{|Client:' src/compute-plane-services/nvca --glob '*.go'

Repository: NVIDIA/nvcf

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- miniservice tracing and patch call ---'
rg -n -C 12 'func .*saveWorkloadConfig|r\.Client\.Patch|tracer\.Start|span\.RecordError|span\.SetStatus' \
  src/compute-plane-services/nvca/internal/miniservice/reconcile.go

printf '%s\n' '--- client instrumentation definition and uses ---'
rg -n -C 12 'type InstrumentedCRClient|func NewInstrumentedCRClient|InstrumentedCRClient|otelhttp|otel\.Tracer|tracer\.Start|RecordError|SetStatus' \
  src/compute-plane-services/nvca/internal/metrics src/compute-plane-services/nvca/internal/miniservice \
  --glob '*.go' | head -n 400

printf '%s\n' '--- client and tracer fields ---'
rg -n -C 8 'type Reconciler struct|tracer[[:space:]]+|Client[[:space:]]+client\.Client|NewInstrumentedCRClient' \
  src/compute-plane-services/nvca/internal/miniservice --glob '*.go'

Repository: NVIDIA/nvcf

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- complete instrumented client implementation ---'
cat -n src/compute-plane-services/nvca/internal/metrics/instrumented_crclient.go | sed -n '1,260p'

printf '%s\n' '--- all Kubernetes client wrappers and tracing setup ---'
rg -n -C 8 'GetClient\(\)|NewInstrumentedCRClient|otel.*(client|kube|k8s)|kube.*otel|otel.*kube|tracing.*client|WithTracer|TracerProvider|oteltrace' \
  src/compute-plane-services/nvca --glob '*.go' | head -n 500

printf '%s\n' '--- dependency versions ---'
rg -n 'controller-runtime|opentelemetry|otel' go.mod src/compute-plane-services/nvca/go.mod 2>/dev/null || true

Repository: NVIDIA/nvcf

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- saveWorkloadConfig call sites and surrounding spans ---'
rg -n -C 20 'saveWorkloadConfig\(' src/compute-plane-services/nvca/internal/miniservice --glob '*.go'

printf '%s\n' '--- Kubernetes REST transport tracing configuration ---'
rg -n -C 8 'otelhttp\.NewTransport|WrapTransport|Transport:[[:space:]]*|HTTPClient|NewInstrumentedCRClient|mgr\.GetConfig\(\)' \
  src/compute-plane-services/nvca --glob '*.go' --glob '!vendor/**' | head -n 400

printf '%s\n' '--- controller-runtime client tracing references in vendored code ---'
rg -n 'opentelemetry|otelhttp|Tracer|trace\.Span|SpanFromContext' \
  src/compute-plane-services/nvca/vendor/sigs.k8s.io/controller-runtime/pkg/client \
  src/compute-plane-services/nvca/vendor/k8s.io/client-go \
  --glob '*.go' | head -n 200

Repository: NVIDIA/nvcf

Length of output: 50367


Add an outbound span for the workload-config patch.

metrics.NewInstrumentedCRClient records metrics only. Add a span around r.Client.Patch, propagate ctx, and record an error status when the patch fails.

🤖 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 `@src/compute-plane-services/nvca/internal/miniservice/reconcile.go` around
lines 481 - 487, Add outbound tracing around the r.Client.Patch call in the
reconciliation flow, using the existing context and preserving propagation
through the patch operation. Ensure the span is ended and records an error
status when Patch returns an error, while retaining the current error-handling
behavior.

Sources: Coding guidelines, Path instructions

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.

NVCA workload config persistence prunes required MiniService spec fields

2 participants