Skip to content

Add default resource limits for MCPRemoteProxy - #5998

Open
clxstart wants to merge 2 commits into
stacklok:mainfrom
clxstart:openmeta/3131-add-default-resource-limits-to-mcpremoteproxy-1785037451518
Open

Add default resource limits for MCPRemoteProxy#5998
clxstart wants to merge 2 commits into
stacklok:mainfrom
clxstart:openmeta/3131-add-default-resource-limits-to-mcpremoteproxy-1785037451518

Conversation

@clxstart

Copy link
Copy Markdown

Summary

  • Apply proxy-runner default CPU/memory requests and limits when MCPRemoteProxy omits spec.resources
  • Merge user-provided resource values over defaults so partial overrides keep missing default fields
  • Use the same effective resource calculation in deployment drift detection

Context

Fixes #3131

Without defaults, remote proxy containers can run unbounded when spec.resources is empty. This mirrors the existing VirtualMCPServer/MCPServer defaulting intent and keeps create/update paths consistent.

Default values

  • CPU request/limit: 50m / 200m
  • Memory request/limit: 64Mi / 256Mi

Test plan

  • go test ./cmd/thv-operator/pkg/controllerutil -run 'TestBuildDefaultProxyRunnerResourceRequirements|TestMergeResourceRequirements|TestEnsureRequiredEnvVars'
  • go test ./cmd/thv-operator/controllers -run 'TestDeploymentForMCPRemoteProxy|TestBuildResourceRequirements|TestResourceRequirementsForRemoteProxy|TestMCPRemoteProxyDeploymentNeedsUpdate_Resources'
  • Reviewer: verify empty spec.resources gets defaults
  • Reviewer: verify partial overrides merge correctly
  • Reviewer: verify deployments without resources are marked for update

Apply proxy-runner defaults when MCPRemoteProxy omits resource requests/limits,
merge user overrides on top of those defaults, and keep deployment drift detection
aligned with the same effective resource calculation.

Closes stacklok#3131
@github-actions github-actions Bot added the size/S Small PR: 100-299 lines changed label Jul 27, 2026
@github-actions github-actions Bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Jul 27, 2026
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.87879% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.19%. Comparing base (e837d69) to head (42da122).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
cmd/thv-operator/pkg/controllerutil/resources.go 85.18% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5998      +/-   ##
==========================================
+ Coverage   72.16%   72.19%   +0.03%     
==========================================
  Files         721      721              
  Lines       75069    75100      +31     
==========================================
+ Hits        54174    54222      +48     
+ Misses      17033    17018      -15     
+ Partials     3862     3860       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ChrisJBurns

ChrisJBurns commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

@clxstart Thanks for the PR!

You'll need to resolve the lint failures first before we can merge

@ChrisJBurns ChrisJBurns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated multi-agent review — PR #5998

Thanks for this — the field-level merge is a genuine improvement over the VirtualMCPServer precedent it mirrors (which ignores user overrides entirely), the merge helper deep-copies quantities safely, and the happy-path test coverage is good.

This is a COMMENT review (no HIGH-severity design blockers). The inline comments below are the findings that scored consensus ≥ 7 across three specialist reviewers (Kubernetes operator, Go conventions, security). In order of value:

  1. gofmt fails CI todaytask lint-fix fixes it.
  2. Drift comparison should use equality.Semantic.DeepEqual — one-line, in-scope fix that avoids a reconcile loop on non-canonical user overrides.
  3. Upgrade behavior (silent rollout + memory cap on existing proxies) deserves a user-facing-change note.
  4. Partial-override request > limit edge case.
  5. Idempotency test doesn't cover the canonicalization path.

Lower-severity items (defaults 2× smaller than vMCP, generic-helper doc comment, unreachable nil-ing, ephemeral-storage/PID scope, stale doc comment) were surfaced in the full terminal review but omitted here per the ≥ 7 consensus filter.

Comment on lines +1644 to 1645
expectedResources := resourceRequirementsForRemoteProxy(proxy)
if !reflect.DeepEqual(container.Resources, expectedResources) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM · consensus 8/10] Drift comparison uses reflect.DeepEqual on resource.Quantity.

Semantically-equal quantities built from different source strings are not reflect.DeepEqualresource.Quantity carries an unexported cached-string field. A valid user override like limits.cpu: "0.2" (canonicalized by the apiserver to 200m), or 1024Mi1Gi, 1000m1, makes generatedContainerNeedsUpdate report perpetual drift → a reconcile/patch loop that hammers the apiserver. The default values themselves survive a round-trip, so the empty-spec path this PR adds is safe; the loop triggers on non-canonical user overrides.

The sibling remoteProxyContainerFieldsNeedUpdate (this file) and mcpserver_controller.go both already use equality.Semantic.DeepEqual. Since this PR's own comment says drift detection should "stay consistent," switching here is in-scope (equality is already imported).

Suggested change
expectedResources := resourceRequirementsForRemoteProxy(proxy)
if !reflect.DeepEqual(container.Resources, expectedResources) {
expectedResources := resourceRequirementsForRemoteProxy(proxy)
if !equality.Semantic.DeepEqual(container.Resources, expectedResources) {

}
}


Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM · consensus 9/10] gofmt will fail CI.

gofmt -d against the committed head reports this file (this extra blank line — two consecutive blanks before TestResourceRequirementsForRemoteProxy) and pkg/controllerutil/resources_test.go (a trailing blank line at EOF) both WOULD REFORMAT, so task lint will fail. .claude/rules/go-style.md requires task lint-fix before submitting — running it fixes both.

Suggested change

assert.False(t, reconciler.deploymentNeedsUpdate(t.Context(), deployment, proxy, "test-checksum"))

// Simulate a deployment created before defaults existed.
deployment.Spec.Template.Spec.Containers[0].Resources = corev1.ResourceRequirements{}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM · consensus 8/10] Undocumented user-facing change: existing proxies roll and gain a hard memory cap on upgrade.

This case (empty Resources → needs update) correctly encodes the intended migration, but it also means every pre-existing MCPRemoteProxy deployment will roll once after the operator upgrade and gain a 256Mi memory / 200m CPU cap it never had before. A proxy whose steady-state working set exceeds 256Mi can be silently OOMKilled post-upgrade with no operator-facing signal.

Please call this out in the PR's "Does this introduce a user-facing change?" section / release notes, and consider advising operators to audit heavy proxies' memory usage (and pre-set spec.resources) before upgrading.


// MergeResourceRequirements merges user-provided resource requirements on top of
// defaults. User values take precedence for any field that is explicitly set.
func MergeResourceRequirements(defaults, user corev1.ResourceRequirements) corev1.ResourceRequirements {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM · consensus 7/10] Partial override can synthesize request > limit.

Because each side is filled independently from defaults, a user who sets only requests.memory: 512Mi (and no memory limit) inherits the default limits.memory: 256Mirequest (512Mi) > limit (256Mi), which Kubernetes rejects at Deployment admission. Same for CPU (requests.cpu: 500m + default limits.cpu: 200m).

Per .claude/rules/operator.md ("Separate terminal from transient errors"), a bad spec should surface as a Valid=False condition with return nil — but as written this fails the Deployment write and requeues with exponential backoff, root cause buried. Consider detecting request > limit after the merge and surfacing a terminal condition, and/or adding a CEL +kubebuilder:validation:XValidation on the CRD ResourceRequirements type to reject it at admission.

}

// TestMCPRemoteProxyDeploymentNeedsUpdate_Resources detects resource drift with defaults
func TestMCPRemoteProxyDeploymentNeedsUpdate_Resources(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM · consensus 7/10] Idempotency test doesn't exercise the path that can break.

This test passes the just-built deployment straight into deploymentNeedsUpdate, so the comparison holds trivially (identical in-memory Quantity objects) and only the empty-spec case is covered — it would keep passing even with the reflect.DeepEqual issue in generatedContainerNeedsUpdate present.

Add a subtest with a non-canonical override (e.g. limits.cpu: "0.2") whose quantities are re-parsed via resource.MustParse(q.String()) to simulate an apiserver round-trip, then assert deploymentNeedsUpdate == false. It fails under reflect.DeepEqual and passes under equality.Semantic.DeepEqual, load-bearing the fix above. An envtest that reads the Deployment back through the client would be the most faithful (matches the team's envtest-over-chainsaw preference).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/S Small PR: 100-299 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add default resource limits to MCPRemoteProxy

2 participants