Skip to content

feat: implement AgentDeployment admission webhooks and resource quota… - #5

Merged
gitcommitankit merged 4 commits into
mainfrom
phase-2
Aug 7, 2026
Merged

feat: implement AgentDeployment admission webhooks and resource quota…#5
gitcommitankit merged 4 commits into
mainfrom
phase-2

Conversation

@gitcommitankit

@gitcommitankit gitcommitankit commented Aug 7, 2026

Copy link
Copy Markdown
Owner

… enforcement

Summary by CodeRabbit

  • New Features

    • Added tenant quota management with GPU-, replica-, and agent-based limits.
    • Added automatic quota usage tracking and over-quota status updates.
    • Added admission validation for quota usage, rollout settings, MCP tools, and resource configuration.
    • Added default values for ports, rollout strategies, and resource requests.
    • Added Kubernetes webhook deployment configuration.
    • Added percentage parsing for error-rate settings.
  • Bug Fixes

    • Prevented image changes during active rollouts.
    • Improved handling of missing or exceeded tenant quotas.
  • Documentation

    • Updated setup requirements and package documentation.

@coderabbitai

coderabbitai Bot commented Aug 7, 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 05af0874-f705-4cbb-b145-119ec3dfc7a1

📥 Commits

Reviewing files that changed from the base of the PR and between 14cfebe and ddaa9c3.

📒 Files selected for processing (3)
  • api/v1alpha1/error_rate_test.go
  • internal/controller/tenantquota_controller_test.go
  • internal/quota/enforcer.go

📝 Walkthrough

Walkthrough

Added GPU-aware tenant quota enforcement, AgentDeployment admission webhooks, TenantQuota reconciliation, Kubernetes webhook manifests, RBAC updates, and integration tests.

Changes

Tenant quota flow

Layer / File(s) Summary
Quota parsing and enforcement
api/v1alpha1/error_rate.go, api/v1alpha1/error_rate_test.go, internal/quota/*
Added percentage parsing and synchronized GPU-aware quota enforcement with usage calculation, admission checks, reservations, expiry, and over-quota reporting.
AgentDeployment admission webhooks
internal/webhook/*, config/webhook/*, go.mod
Added AgentDeployment defaulting and validation for tenant references, replica limits, Canary rollouts, MCP tools, rollout updates, and short-lived quota reservations.
TenantQuota reconciliation
internal/controller/tenantquota_controller.go, internal/controller/enqueue_handlers.go, internal/controller/*test.go
Implemented quota status reconciliation, AgentDeployment event enqueueing, usage recomputation, OverQuota transitions, reservation release, and periodic requeue behavior.
Controller and deployment integration
cmd/main.go, config/rbac/role.yaml, internal/controller/suite_test.go, .agents/*, internal/controller/agentdeployment_controller_test.go
Wired the shared enforcer, reconciler, and webhook into the manager. Configured GPU resource selection, RBAC, envtest webhook serving, and existing AgentDeployment test setup.

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

Sequence Diagram(s)

sequenceDiagram
  participant APIServer
  participant AgentDeploymentCustomValidator
  participant Enforcer
  participant TenantQuotaReconciler
  participant AgentDeployment
  APIServer->>AgentDeploymentCustomValidator: Validate AgentDeployment
  AgentDeploymentCustomValidator->>Enforcer: Check quota and reserve usage
  Enforcer-->>AgentDeploymentCustomValidator: Admission result
  AgentDeploymentCustomValidator-->>APIServer: Admission response
  AgentDeployment->>TenantQuotaReconciler: AgentDeployment event
  TenantQuotaReconciler->>Enforcer: Compute committed usage
  Enforcer-->>TenantQuotaReconciler: TenantQuota usage
  TenantQuotaReconciler-->>APIServer: Update TenantQuota status
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.44% 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 clearly summarizes the pull request's primary changes: AgentDeployment admission webhooks and resource quota enforcement.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-2

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

Caution

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

⚠️ Outside diff range comments (1)
internal/controller/suite_test.go (1)

156-161: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

BeforeSuite returns before the webhook server accepts connections.

The manager starts in a goroutine and BeforeSuite ends immediately. The webhook server binds asynchronously. The first spec that creates an AgentDeployment can reach the API server before the endpoint listens.

The outcome depends on the failurePolicy in config/webhook/manifests.yaml. With Fail, the create returns a connection error and the spec fails. With Ignore, the object is admitted without validation, and the quota rejection tests in internal/controller/tenantquota_controller_test.go pass for the wrong reason.

Poll the serving address before returning.

💚 Proposed fix
 	mgrDone = make(chan struct{})
 	go func() {
 		defer GinkgoRecover()
 		defer close(mgrDone)
 		Expect(mgr.Start(ctx)).To(Succeed())
 	}()
+
+	// Wait until the webhook server accepts connections. Without this the
+	// first AgentDeployment create can race the webhook listener.
+	wio := &testEnv.WebhookInstallOptions
+	addr := net.JoinHostPort(wio.LocalServingHost, strconv.Itoa(wio.LocalServingPort))
+	Eventually(func() error {
+		conn, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true}) //nolint:gosec // envtest self-signed cert
+		if err != nil {
+			return err
+		}
+		return conn.Close()
+	}, timeout, interval).Should(Succeed())
 })

Add "crypto/tls", "net", and "strconv" to the imports.

🤖 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 `@internal/controller/suite_test.go` around lines 156 - 161, Update BeforeSuite
around mgr.Start and mgrDone to wait until the webhook serving address accepts
TLS connections before returning. Use crypto/tls, net, and strconv to construct
and poll the configured host/port, preserving the existing GinkgoRecover and
manager lifecycle handling.
🤖 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 `@api/v1alpha1/error_rate_test.go`:
- Around line 32-41: Add table cases in the error-rate parser tests for
percentage strings with trailing garbage and for non-finite numeric input such
as NaN or infinity, marking each as invalid and asserting the expected zero
value. Use the existing table-driven test cases alongside the entries for
malformed values like "abc%" and "101%".

In `@api/v1alpha1/error_rate.go`:
- Around line 31-37: The error-rate parser in api/v1alpha1/error_rate.go lines
31-37 must use strconv.ParseFloat on the numeric portion so trailing characters
and leading whitespace are rejected, and must reject NaN or infinite values via
math.IsNaN/math.IsInf before the existing [0,100] range check. Extend the table
in api/v1alpha1/error_rate_test.go lines 32-41 with invalid cases for "5x%",
"NaN%", and " 5%", each expecting an error and zero.

In `@internal/controller/suite_test.go`:
- Line 140: Update the suite teardown in AfterSuite to call testEnforcer.Stop()
exactly once for the enforcer initialized by NewEnforcer. Ensure the stop occurs
during cleanup and avoid any additional Stop calls, since repeated invocation
panics.

In `@internal/controller/tenantquota_controller_test.go`:
- Around line 209-217: Update the TenantQuota status assertion in the Eventually
block to require that FindStatusCondition returns nil after usage normalizes.
Replace the conditional status check with a direct g.Expect(cond).To(BeNil())
assertion, preserving the existing resource lookup and retry behavior.
- Around line 259-273: Make the “rejects an AgentDeployment that would exceed
maxAgents” test wait until the tenant quota’s committed usage reflects the first
AgentDeployment before creating ad2. After creating ad1, poll the tq-reject
TenantQuota via the existing test client until status.usedAgents equals 1, then
create ad2 and retain the existing invalid/forbidden assertion.

In `@internal/controller/tenantquota_controller.go`:
- Around line 74-82: In internal/controller/tenantquota_controller.go#L74-L82,
update the tenant quota reconciliation loop to collect committed reservation
keys instead of releasing them immediately, then release those keys only after
Status().Update succeeds or on the no-change path when persisted status already
includes the ADs. In
internal/controller/tenantquota_controller_test.go#L259-L273, add an Eventually
assertion waiting for status.usedAgents to reach 1 after creating ad-reject-1,
before asserting rejection of ad-reject-2.
- Around line 67-70: Register a field index for AgentDeployment.spec.tenantRef
in SetupWithManager, then update the AgentDeployment list in the reconcile flow
to use client.MatchingFields with the tenant reference instead of listing the
entire namespace for in-memory filtering. Preserve the existing tenant-specific
behavior and error handling.

In `@internal/controller/test_helpers_test.go`:
- Around line 36-38: Update the namespacedName helper to accept arguments in
namespace, name order, matching types.NamespacedName and the package’s
namespace/name convention; update every call site accordingly while preserving
the resulting Name and Namespace fields.

In `@internal/quota/enforcer_test.go`:
- Around line 346-348: Update the ParseErrorRate test-section comment to
reference api/v1alpha1/error_rate_test.go, and clean up the quota arithmetic
comments near the relevant assertions by replacing the duplicated, unfinished
reasoning with one concise statement.
- Around line 232-260: Add a true concurrent-create test alongside
TestReservation_BlocksConcurrentCreate that launches two goroutines against the
combined TryAdmit path with exactly one available slot, synchronizes their
start, and asserts exactly one succeeds; also cover concurrent reservation
release. Make the requested quota scenarios table-driven, including at-limit,
over-limit, GPU extraction edge cases, and concurrent reservation release, using
the existing quota test helpers and running safely under the race detector.
- Around line 364-373: Replace the hand-rolled logic in containsSubstring with
strings.Contains, adding the strings import as needed; alternatively remove
containsSubstring and update its call sites to use strings.Contains directly.

In `@internal/quota/enforcer.go`:
- Around line 159-227: The quota check and reservation are not atomic, allowing
concurrent admissions to exceed capacity. In internal/quota/enforcer.go lines
159-227, add exported TryAdmit that holds mu through evaluation and reservation,
refactor CanAdmit, sumInflight, and Reserve into *Locked helpers, and retain
existing exported methods as lock-and-delegate wrappers; update the admission
webhook to use TryAdmit. In internal/quota/enforcer_test.go lines 232-260, add
concurrent two-goroutine one-slot coverage asserting exactly one admission, plus
concurrent reservation-release coverage; run both with -race -count=100.
- Around line 81-83: Make Enforcer.Stop idempotent so repeated calls cannot
panic when closing e.done. Update the Stop method to guard the channel close
using the existing synchronization mechanism or an equivalent one-time shutdown
guard, while preserving the first-call shutdown behavior.
- Around line 128-131: Update Enforcer.gpusForAD to perform the per-replica GPU
multiplication in int64 without converting to int32 first, then clamp the result
to the int32 range before returning it; preserve the existing resource
extraction and replica calculation while preventing truncation and overflow.

In `@internal/webhook/agentdeployment_webhook.go`:
- Line 174: Make spec.tenantRef immutable in the validation flow around
validateSpec and CanAdmit by comparing the new and old AgentDeployment specs and
rejecting updates when the tenant reference changes. Add a regression test
covering transfer into a TenantQuota already at its limit, verifying the update
is denied.

---

Outside diff comments:
In `@internal/controller/suite_test.go`:
- Around line 156-161: Update BeforeSuite around mgr.Start and mgrDone to wait
until the webhook serving address accepts TLS connections before returning. Use
crypto/tls, net, and strconv to construct and poll the configured host/port,
preserving the existing GinkgoRecover and manager lifecycle handling.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9900a7e2-54ca-4151-aa19-991860656a05

📥 Commits

Reviewing files that changed from the base of the PR and between 17c4678 and 6c5c2da.

📒 Files selected for processing (21)
  • .agents/AGENTS.md
  • .agents/skills/agentrax-context/SKILL.md
  • api/v1alpha1/error_rate.go
  • api/v1alpha1/error_rate_test.go
  • cmd/main.go
  • config/rbac/role.yaml
  • config/webhook/kustomization.yaml
  • config/webhook/manifests.yaml
  • config/webhook/service.yaml
  • go.mod
  • internal/controller/agentdeployment_controller_test.go
  • internal/controller/enqueue_handlers.go
  • internal/controller/suite_test.go
  • internal/controller/tenantquota_controller.go
  • internal/controller/tenantquota_controller_test.go
  • internal/controller/test_helpers_test.go
  • internal/quota/enforcer.go
  • internal/quota/enforcer_test.go
  • internal/webhook/agentdeployment_validator_test.go
  • internal/webhook/agentdeployment_webhook.go
  • internal/webhook/agentdeployment_webhook_test.go

Comment thread api/v1alpha1/error_rate_test.go
Comment thread api/v1alpha1/error_rate.go Outdated
Comment thread internal/controller/suite_test.go
Comment thread internal/controller/tenantquota_controller_test.go
Comment thread internal/controller/tenantquota_controller_test.go
Comment thread internal/quota/enforcer_test.go Outdated
Comment thread internal/quota/enforcer.go
Comment thread internal/quota/enforcer.go
Comment thread internal/quota/enforcer.go
Comment thread internal/webhook/agentdeployment_webhook.go

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

Caution

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

⚠️ Outside diff range comments (3)
internal/controller/tenantquota_controller_test.go (3)

96-105: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Assert owner references on reconciled children.

This test creates an AgentDeployment through the live envtest manager but only checks TenantQuota.Status. Add or reuse assertions for every child resource produced by the AgentDeployment reconciler, including Deployment, Service, ServiceMonitor, and HPA when applicable. Apply the helper to the other AgentDeployment fixtures in this file.

As per path instructions, tests must assert owner references on every created child resource because missing owner references can orphan resources.

🤖 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 `@internal/controller/tenantquota_controller_test.go` around lines 96 - 105,
Extend the AgentDeployment fixtures in the tenant quota controller tests to
verify owner references for every reconciled child resource: Deployment,
Service, ServiceMonitor, and applicable HPA. Add or reuse a helper that checks
each child is owned by its AgentDeployment, invoke it for the ad-count-1 fixture
and all other AgentDeployment fixtures, while preserving the existing status
assertions.

Source: Path instructions


51-70: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Make AfterEach cleanup deterministic.

AfterEach ignores Update and Delete errors and does not wait for resources to disappear. Because all specs reuse tqNS, a terminating AgentDeployment or TenantQuota can affect later quota counts and list assertions. Treat NotFound as success, fail on other errors, and wait until test resources are deleted.

As per path instructions, errors must not be swallowed silently.

🤖 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 `@internal/controller/tenantquota_controller_test.go` around lines 51 - 70,
Make the AfterEach cleanup for AgentDeployments and TenantQuotas fail on any
Update or Delete error except NotFound, and wait until each resource is
confirmed deleted before cleanup completes. Update the loops around adList.Items
and tqList.Items to handle errors explicitly, then use the test client’s
existing polling or Eventually mechanism to verify deletion, preserving the
finalizer removal step for AgentDeployments.

Source: Path instructions


209-216: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert normalized usage with condition removal.

The test only checks that OverQuota is absent. It can pass if the condition is removed while Status.UsedAgents remains 2. Also assert UsedAgents == 1 and the expected replica total in the same Eventually block.

Proposed assertion
 				cond := apimeta.FindStatusCondition(f.Status.Conditions, agentraxv1alpha1.ConditionOverQuota)
+				g.Expect(f.Status.UsedAgents).To(BeNumerically("==", 1))
+				g.Expect(f.Status.UsedTotalReplicas).To(BeNumerically("==", 2))
 				// The reconciler calls RemoveStatusCondition, so the condition
🤖 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 `@internal/controller/tenantquota_controller_test.go` around lines 209 - 216,
Extend the `Eventually` block for the normalized `tq-clearoq` TenantQuota to
assert that `f.Status.UsedAgents` equals 1 and that the status reports the
expected replica total, in addition to verifying the `ConditionOverQuota`
condition is absent.
♻️ Duplicate comments (2)
api/v1alpha1/error_rate_test.go (1)

41-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover infinity inputs.

The table covers NaN%, but it does not cover the math.IsInf branch. Add Inf% and -Inf% invalid cases.

🤖 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 `@api/v1alpha1/error_rate_test.go` around lines 41 - 46, Add table-driven
invalid cases for both "Inf%" and "-Inf%" in the existing error-rate parsing
tests, alongside the existing "NaN%" case, with the expected zero value and
error result matching the other non-finite inputs.
internal/quota/enforcer.go (1)

130-139: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Check multiplication overflow before the conversion.

perReplica * int64(ad.Replicas.Max) can overflow int64 before the upper-bound check. A wrapped value can then reach int32(total) and undercount GPU usage. Reject negative operands or fail closed, check multiplication overflow, and convert only after validation.

Suggested guard
 func (e *Enforcer) gpusForAD(ad agentraxv1alpha1.AgentDeploymentSpec) int32 {
 	perReplica := e.extractGPUs(ad.Resources)
-	total := perReplica * int64(ad.Replicas.Max)
+	maxReplicas := int64(ad.Replicas.Max)
+	if perReplica < 0 || maxReplicas < 0 {
+		return math.MaxInt32
+	}
+	if perReplica != 0 && maxReplicas > math.MaxInt64/perReplica {
+		return math.MaxInt32
+	}
+	total := perReplica * maxReplicas
🤖 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 `@internal/quota/enforcer.go` around lines 130 - 139, Update Enforcer.gpusForAD
to validate perReplica and ad.Replicas.Max are non-negative, detect int64
multiplication overflow before calculating total, and fail closed with the
maximum GPU value when validation fails. Perform the existing int32 upper-bound
clamp only after safe multiplication, then convert total to int32.

Source: Linters/SAST tools

🤖 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 `@internal/controller/tenantquota_controller_test.go`:
- Around line 96-105: Extend the AgentDeployment fixtures in the tenant quota
controller tests to verify owner references for every reconciled child resource:
Deployment, Service, ServiceMonitor, and applicable HPA. Add or reuse a helper
that checks each child is owned by its AgentDeployment, invoke it for the
ad-count-1 fixture and all other AgentDeployment fixtures, while preserving the
existing status assertions.
- Around line 51-70: Make the AfterEach cleanup for AgentDeployments and
TenantQuotas fail on any Update or Delete error except NotFound, and wait until
each resource is confirmed deleted before cleanup completes. Update the loops
around adList.Items and tqList.Items to handle errors explicitly, then use the
test client’s existing polling or Eventually mechanism to verify deletion,
preserving the finalizer removal step for AgentDeployments.
- Around line 209-216: Extend the `Eventually` block for the normalized
`tq-clearoq` TenantQuota to assert that `f.Status.UsedAgents` equals 1 and that
the status reports the expected replica total, in addition to verifying the
`ConditionOverQuota` condition is absent.

---

Duplicate comments:
In `@api/v1alpha1/error_rate_test.go`:
- Around line 41-46: Add table-driven invalid cases for both "Inf%" and "-Inf%"
in the existing error-rate parsing tests, alongside the existing "NaN%" case,
with the expected zero value and error result matching the other non-finite
inputs.

In `@internal/quota/enforcer.go`:
- Around line 130-139: Update Enforcer.gpusForAD to validate perReplica and
ad.Replicas.Max are non-negative, detect int64 multiplication overflow before
calculating total, and fail closed with the maximum GPU value when validation
fails. Perform the existing int32 upper-bound clamp only after safe
multiplication, then convert total to int32.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0c297b81-066a-4685-9567-f68aab43a9cc

📥 Commits

Reviewing files that changed from the base of the PR and between 6c5c2da and 14cfebe.

📒 Files selected for processing (6)
  • api/v1alpha1/error_rate.go
  • api/v1alpha1/error_rate_test.go
  • internal/controller/suite_test.go
  • internal/controller/tenantquota_controller_test.go
  • internal/quota/enforcer.go
  • internal/quota/enforcer_test.go

…ion against overflow, and expand error rate parsing test cases
@gitcommitankit
gitcommitankit merged commit f9a16f1 into main Aug 7, 2026
4 checks passed
@gitcommitankit
gitcommitankit deleted the phase-2 branch August 15, 2026 19:03
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