fix: consistently reconcile quotas from rules (#2083)

* fix: do not mutate on update and bound pvcs

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: do not mutate on update and bound pvcs

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: consistently reconcile quotas from rules

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
This commit is contained in:
Oliver Bähler
2026-08-17 10:49:56 +02:00
committed by GitHub
parent bddb97644a
commit 305cabd4f6
8 changed files with 511 additions and 62 deletions
@@ -128,15 +128,15 @@ func (r *Controller) reconcile(
return nil, false, err
}
if err := r.syncResourceQuotas(ctx, instance, namespaces); err != nil {
return nil, false, err
}
status, initialized, err := r.observeUsage(ctx, instance, namespaces)
if err != nil {
return nil, false, err
}
if err := r.syncResourceQuotas(ctx, instance, namespaces, status); err != nil {
return status, false, err
}
ledger, err := r.ensureLedger(ctx, instance)
if err != nil {
return status, false, err
@@ -160,6 +160,7 @@ func (r *Controller) syncResourceQuotas(
ctx context.Context,
instance *capsulev1beta2.GlobalResourceQuota,
namespaces []corev1.Namespace,
status *capsulev1beta2.GlobalResourceQuotaStatus,
) error {
selected := make(map[string]struct{}, len(namespaces))
@@ -184,7 +185,7 @@ func (r *Controller) syncResourceQuotas(
targetLabels[meta.NewManagedByCapsuleLabel] = meta.ValueController
targetLabels[meta.GlobalResourceQuotaLabel] = instance.Name
target.SetLabels(targetLabels)
target.Spec = *instance.Spec.Quota.DeepCopy()
target.Spec = projectedResourceQuotaSpec(instance.Spec.Quota, status, namespace.Name)
return controllerutil.SetControllerReference(instance, target, r.Scheme())
})
@@ -221,6 +222,40 @@ func (r *Controller) syncResourceQuotas(
return nil
}
// projectedResourceQuotaSpec gives every selected namespace access to the
// quota which is still available globally, while retaining that namespace's
// already-observed usage in its native ResourceQuota hard limit. Consequently
// Spec.Hard-Status.Used exposes the same remaining capacity in every
// namespace. When the global quota is exhausted or over limit, Hard is pinned
// to the namespace's current usage so native ResourceQuota admission blocks
// further consumption.
func projectedResourceQuotaSpec(
quota corev1.ResourceQuotaSpec,
status *capsulev1beta2.GlobalResourceQuotaStatus,
namespace string,
) corev1.ResourceQuotaSpec {
desired := *quota.DeepCopy()
desired.Hard = make(corev1.ResourceList, len(quota.Hard))
var namespaceUsed corev1.ResourceList
if status != nil {
namespaceUsed = status.NamespaceUsage[namespace].Used
}
for name, hard := range quota.Hard {
available := hard.DeepCopy()
if status != nil {
available = status.Total.Available[name].DeepCopy()
}
projected := namespaceUsed[name].DeepCopy()
projected.Add(available)
desired.Hard[name] = projected
}
return desired
}
func (r *Controller) observeUsage(
ctx context.Context,
instance *capsulev1beta2.GlobalResourceQuota,
@@ -149,6 +149,47 @@ func TestMatchingNamespaceSelectorsUseOR(t *testing.T) {
}
}
func TestProjectedResourceQuotaSpecExposesGlobalRemainingCapacity(t *testing.T) {
t.Parallel()
quota := corev1.ResourceQuotaSpec{
Hard: corev1.ResourceList{
corev1.ResourceRequestsCPU: resource.MustParse("10"),
corev1.ResourceRequestsMemory: resource.MustParse("4Gi"),
},
Scopes: []corev1.ResourceQuotaScope{corev1.ResourceQuotaScopeNotTerminating},
}
status := &capsulev1beta2.GlobalResourceQuotaStatus{
Total: capsulev1beta2.GlobalResourceQuotaUsage{
Available: corev1.ResourceList{
corev1.ResourceRequestsCPU: resource.MustParse("4"),
corev1.ResourceRequestsMemory: resource.MustParse("0"),
},
},
NamespaceUsage: capsulev1beta2.GlobalResourceQuotaNamespaceUsage{
"team-a": {
Used: corev1.ResourceList{
corev1.ResourceRequestsCPU: resource.MustParse("2"),
corev1.ResourceRequestsMemory: resource.MustParse("5Gi"),
},
},
},
}
projected := projectedResourceQuotaSpec(quota, status, "team-a")
assertResource(t, projected.Hard, corev1.ResourceRequestsCPU, "6")
assertResource(t, projected.Hard, corev1.ResourceRequestsMemory, "5Gi")
if len(projected.Scopes) != 1 || projected.Scopes[0] != corev1.ResourceQuotaScopeNotTerminating {
t.Fatalf("projected scopes = %#v, want NotTerminating", projected.Scopes)
}
// The desired shared limit remains immutable while projecting a namespace's
// native hard values.
assertResource(t, quota.Hard, corev1.ResourceRequestsCPU, "10")
assertResource(t, quota.Hard, corev1.ResourceRequestsMemory, "4Gi")
}
func observedResourceQuota(
quota *capsulev1beta2.GlobalResourceQuota,
namespace string,