Skip to content

Commit ddaa9c3

Browse files
fix: improve deletion reliability in tests, harden GPU quota calculation against overflow, and expand error rate parsing test cases
1 parent 14cfebe commit ddaa9c3

3 files changed

Lines changed: 34 additions & 5 deletions

File tree

api/v1alpha1/error_rate_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ func TestParseErrorRate(t *testing.T) {
4040
{"abc%", 0, true},
4141
// trailing garbage — strconv.ParseFloat must reject these
4242
{"5x%", 0, true},
43-
// non-finite numeric input
43+
// non-finite numeric input — caught by math.IsNaN / math.IsInf guard
4444
{"NaN%", 0, true},
45+
{"Inf%", 0, true},
46+
{"-Inf%", 0, true},
4547
// leading whitespace — strconv.ParseFloat must reject " 5"
4648
{" 5%", 0, true},
4749
}

internal/controller/tenantquota_controller_test.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,30 @@ var _ = Describe("TenantQuota Controller", func() {
5353
adList := &agentraxv1alpha1.AgentDeploymentList{}
5454
Expect(k8sClient.List(ctx, adList, inNamespace(tqNS))).To(Succeed())
5555
for i := range adList.Items {
56-
// Remove finalizer so deletion doesn't block.
5756
ad := &adList.Items[i]
57+
// Remove finalizer so deletion is not blocked by the controller.
5858
ad.Finalizers = nil
59-
_ = k8sClient.Update(ctx, ad)
60-
_ = k8sClient.Delete(ctx, ad)
59+
if err := k8sClient.Update(ctx, ad); err != nil && !apierrors.IsNotFound(err) {
60+
Expect(err).NotTo(HaveOccurred(), "removing finalizer from AD %s", ad.Name)
61+
}
62+
if err := k8sClient.Delete(ctx, ad); err != nil && !apierrors.IsNotFound(err) {
63+
Expect(err).NotTo(HaveOccurred(), "deleting AD %s", ad.Name)
64+
}
65+
// Wait until the API server confirms the object is gone.
66+
Eventually(func() bool {
67+
err := k8sClient.Get(ctx, namespacedName(ad.Name, tqNS), &agentraxv1alpha1.AgentDeployment{})
68+
return apierrors.IsNotFound(err)
69+
}, timeout, interval).Should(BeTrue(), "AD %s should be fully deleted", ad.Name)
6170
}
6271

6372
By("deleting all TenantQuotas in the test namespace")
6473
tqList := &agentraxv1alpha1.TenantQuotaList{}
6574
Expect(k8sClient.List(ctx, tqList, inNamespace(tqNS))).To(Succeed())
6675
for i := range tqList.Items {
6776
tq := &tqList.Items[i]
68-
_ = k8sClient.Delete(ctx, tq)
77+
if err := k8sClient.Delete(ctx, tq); err != nil && !apierrors.IsNotFound(err) {
78+
Expect(err).NotTo(HaveOccurred(), "deleting TQ %s", tq.Name)
79+
}
6980
}
7081
})
7182

@@ -213,6 +224,9 @@ var _ = Describe("TenantQuota Controller", func() {
213224
// The reconciler calls RemoveStatusCondition, so the condition
214225
// must be fully absent once usage normalises.
215226
g.Expect(cond).To(BeNil())
227+
// Usage counters must reflect the one remaining AD (maxReplicas=2).
228+
g.Expect(f.Status.UsedAgents).To(BeNumerically("==", 1))
229+
g.Expect(f.Status.UsedTotalReplicas).To(BeNumerically("==", 2))
216230
}, timeout, interval).Should(Succeed())
217231
})
218232

internal/quota/enforcer.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,21 @@ func (e *Enforcer) extractGPUs(resources corev1.ResourceRequirements) int64 {
129129
// gpusForAD returns the total GPU units for one AgentDeployment:
130130
// gpuPerReplica × spec.replicas.max. The multiplication is performed in
131131
// int64 to prevent overflow, then clamped to the int32 range.
132+
// Fails closed (returns MaxInt32) on unexpected negative inputs or int64
133+
// overflow so the quota check rejects rather than under-counts.
132134
func (e *Enforcer) gpusForAD(ad agentraxv1alpha1.AgentDeploymentSpec) int32 {
133135
perReplica := e.extractGPUs(ad.Resources)
136+
// GPU quantities and replica counts must be non-negative. If either is
137+
// negative (should never happen given CRD validation), fail closed.
138+
if perReplica < 0 || ad.Replicas.Max < 0 {
139+
return math.MaxInt32
140+
}
141+
// Detect int64 multiplication overflow before computing total.
142+
// perReplica and Replicas.Max are both non-negative at this point, so
143+
// overflow can only occur in the positive direction.
144+
if perReplica > 0 && int64(ad.Replicas.Max) > math.MaxInt64/perReplica {
145+
return math.MaxInt32
146+
}
134147
total := perReplica * int64(ad.Replicas.Max)
135148
const maxInt32 = int64(math.MaxInt32)
136149
if total > maxInt32 {

0 commit comments

Comments
 (0)