Skip to content

Commit e0127dd

Browse files
committed
fix(globalcustomqota): object was not added to quota when namespace was not reconciled
Signed-off-by: sandert-k8s <sandert98@gmail.com>
1 parent a14db27 commit e0127dd

5 files changed

Lines changed: 59 additions & 10 deletions

File tree

internal/webhook/customquota/calculation.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"sort"
1111
"time"
1212

13+
corev1 "k8s.io/api/core/v1"
1314
apierrors "k8s.io/apimachinery/pkg/api/errors"
1415
"k8s.io/apimachinery/pkg/api/resource"
1516
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -697,11 +698,44 @@ func (h *objectCalculationHandler) matchGlobalCustomQuotas(
697698

698699
objLabels := labels.Set(u.GetLabels())
699700

701+
// Fetch the namespace once so that per-GCQ namespace-selector
702+
// checks below are a pure in-memory label
703+
// evaluation rather than a repeated cache lookup.
704+
var nsLabels labels.Set
705+
706+
if req.Namespace != "" {
707+
ns := &corev1.Namespace{}
708+
if err := c.Get(ctx, types.NamespacedName{Name: req.Namespace}, ns); err != nil {
709+
if apierrors.IsNotFound(err) {
710+
return nil, nil
711+
}
712+
713+
return nil, fmt.Errorf("get namespace %s: %w", req.Namespace, err)
714+
}
715+
716+
nsLabels = labels.Set(ns.GetLabels())
717+
}
718+
700719
out := make([]quota.MatchedQuota, 0)
701720

702721
for _, gcq := range list.Items {
703-
if !gcq.Status.NamespacePresent("*") && !gcq.Status.NamespacePresent(req.Namespace) {
704-
continue
722+
// Evaluate namespace selectors directly from Spec against the live
723+
// namespace labels (informer-cached) rather than checking
724+
// Status.Namespaces, which is only updated after a controller reconcile.
725+
// This closes the window where newly-labelled namespaces bypass the quota
726+
// before the controller has had a chance to reconcile.
727+
if len(gcq.Spec.NamespaceSelectors) > 0 {
728+
nsLabelSelectors := make([]metav1.LabelSelector, 0, len(gcq.Spec.NamespaceSelectors))
729+
730+
for _, nsSel := range gcq.Spec.NamespaceSelectors {
731+
if nsSel.LabelSelector != nil {
732+
nsLabelSelectors = append(nsLabelSelectors, *nsSel.LabelSelector)
733+
}
734+
}
735+
736+
if !selectors.MatchesSelectors(nsLabels, nsLabelSelectors) {
737+
continue
738+
}
705739
}
706740

707741
if !selectors.MatchesSelectors(objLabels, gcq.Spec.ScopeSelectors) {

pkg/runtime/indexers/customquota/customquota_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
1111
"github.com/projectcapsule/capsule/pkg/api/meta"
12+
capruntime "github.com/projectcapsule/capsule/pkg/api/runtime"
1213
"github.com/projectcapsule/capsule/pkg/runtime/indexers/customquota"
1314
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1415
"k8s.io/apimachinery/pkg/types"
@@ -20,6 +21,9 @@ func TestCustomQuotaIndexers(t *testing.T) {
2021
target := capsulev1beta2.CustomQuotaStatusTarget{
2122
GroupVersionKind: metav1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
2223
}
24+
globalSource := capsulev1beta2.CustomQuotaSpecSource{
25+
VersionKind: capruntime.VersionKind{APIVersion: "apps/v1", Kind: "Deployment"},
26+
}
2327
claim := capsulev1beta2.CustomQuotaClaimItem{
2428
NamespacedObjectWithUIDReference: meta.NamespacedObjectWithUIDReference{UID: types.UID("claim-uid")},
2529
}
@@ -50,8 +54,10 @@ func TestCustomQuotaIndexers(t *testing.T) {
5054
name: "global target",
5155
field: customquota.GlobalTargetReference{}.Field(),
5256
got: customquota.GlobalTargetReference{}.Func()(&capsulev1beta2.GlobalCustomQuota{
53-
Status: capsulev1beta2.GlobalCustomQuotaStatus{
54-
CustomQuotaStatus: capsulev1beta2.CustomQuotaStatus{Targets: []capsulev1beta2.CustomQuotaStatusTarget{target}},
57+
Spec: capsulev1beta2.GlobalCustomQuotaSpec{
58+
CustomQuotaSpec: capsulev1beta2.CustomQuotaSpec{
59+
Sources: []capsulev1beta2.CustomQuotaSpecSource{globalSource},
60+
},
5561
},
5662
}),
5763
want: []string{target.String()},

pkg/runtime/indexers/customquota/global_target.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package customquota
55

66
import (
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
78
"sigs.k8s.io/controller-runtime/pkg/client"
89

910
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
@@ -23,10 +24,18 @@ func (o GlobalTargetReference) Func() client.IndexerFunc {
2324
return func(object client.Object) []string {
2425
tr := object.(*capsulev1beta2.GlobalCustomQuota) //nolint:forcetypeassert
2526

26-
targets := make([]string, 0, len(tr.Status.Targets))
27-
28-
for _, t := range tr.Status.Targets {
29-
targets = append(targets, t.String())
27+
// Index on Spec.Sources (declared intent) rather than Status.Targets
28+
// (reconciled state) so the webhook can match quotas immediately after
29+
// creation, before the controller has had a chance to reconcile.
30+
targets := make([]string, 0, len(tr.Spec.Sources))
31+
32+
for _, src := range tr.Spec.Sources {
33+
gvk := src.GroupVersionKind()
34+
targets = append(targets, metav1.GroupVersionKind{
35+
Group: gvk.Group,
36+
Version: gvk.Version,
37+
Kind: gvk.Kind,
38+
}.String())
3039
}
3140

3241
return targets

pkg/runtime/indexers/customquota/global_uid.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func (o GlobalObjectUIDReference) Field() string {
1919
return ObjectUIDIndexerFieldName
2020
}
2121

22-
func (o GlobalObjectUIDReference) Func() client.IndexerFunc {
22+
func (o GlobalObjectUIDReference) Func() client.IndexerFunc { //nolint:dupl
2323
return func(object client.Object) []string {
2424
tr := object.(*capsulev1beta2.GlobalCustomQuota) //nolint:forcetypeassert
2525

pkg/runtime/indexers/customquota/namespaces_uid.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func (o NamespacedObjectUIDReference) Field() string {
1919
return ObjectUIDIndexerFieldName
2020
}
2121

22-
func (o NamespacedObjectUIDReference) Func() client.IndexerFunc {
22+
func (o NamespacedObjectUIDReference) Func() client.IndexerFunc { //nolint:dupl
2323
return func(object client.Object) []string {
2424
tr := object.(*capsulev1beta2.CustomQuota) //nolint:forcetypeassert
2525

0 commit comments

Comments
 (0)