@@ -83,6 +83,19 @@ func (r *ResourceQuotaReconciler) Reconcile(ctx context.Context, req ctrl.Reques
8383 }
8484
8585 isSingleton := utils .IsSingletonRQ (inst )
86+ isLegacyScoped := utils .IsLegacyScopedRQ (inst )
87+
88+ if isLegacyScoped {
89+ // Ignore legacy scoped RQs
90+ // It will be deleted in the reconcile loop for the new RQ.
91+ return ctrl.Result {}, nil
92+ }
93+
94+ if ! notFound && ! isSingleton { // scoped RQ exists
95+ if err := r .deleteLegacyScopedRQ (ctx , log , inst ); err != nil {
96+ return ctrl.Result {}, err
97+ }
98+ }
8699
87100 r .Forest .Lock ()
88101 ns := r .Forest .Get (inst .ObjectMeta .Namespace )
@@ -100,43 +113,31 @@ func (r *ResourceQuotaReconciler) Reconcile(ctx context.Context, req ctrl.Reques
100113
101114 log .Info ("Reconciling ResourceQuota" , "name" , fmt .Sprintf ("%s/%s" , inst .GetNamespace (), inst .GetName ()), "limits" , inst .Spec .Hard , "usages" , inst .Status .Used , "updated" , updated )
102115
116+ var hrq * api.HierarchicalResourceQuota
117+
103118 // Delete the obsolete singleton and early exit if the new limits are empty.
104119 if inst .Spec .Hard == nil {
105120 return ctrl.Result {}, r .deleteRQ (ctx , log , inst )
106121 } else if ! isSingleton && notFound {
107- hrq := & api.HierarchicalResourceQuota {}
108- hrqName , err := utils .ScopedHRQNameFromHRQName (inst .Name )
122+ hrqnnm , err := r .findHRQNameForRQ (inst )
109123 if err != nil {
110- return ctrl.Result {}, fmt .Errorf ("while getting hrq name: %w" , err )
124+ return ctrl.Result {}, fmt .Errorf ("while finding hrq name for new RQ : %w" , err )
111125 }
112126
113- cursorNm := ns
114- var found bool
115- for {
116- if cursorNm == nil {
117- break
118- }
119-
120- hrqnnm := types.NamespacedName {Namespace : cursorNm .Name (), Name : hrqName }
121- err := r .Get (ctx , hrqnnm , hrq )
122- if err == nil {
123- found = true
124- break
125- }
127+ hrq = & api.HierarchicalResourceQuota {}
128+ err = r .Get (ctx , hrqnnm , hrq )
129+ if err != nil {
126130 if apierrors .IsNotFound (err ) {
127- cursorNm = cursorNm .Parent ()
128- continue
131+ return ctrl.Result {}, fmt .Errorf ("the parent hrq not found: %s" , hrqnnm )
132+ } else {
133+ return ctrl.Result {}, fmt .Errorf ("getting hrq %s: %w" , hrqnnm , err )
129134 }
130-
131- return ctrl.Result {}, fmt .Errorf ("while getting hrq: %w" , err )
132- }
133- if ! found {
134- return ctrl.Result {}, fmt .Errorf ("the parent hrq not found: %s" , hrqName )
135135 }
136136
137137 log .Info ("Found the parent HRQ" , "namespace" , hrq .Namespace , "name" , hrq .Name )
138138
139139 inst .Spec .ScopeSelector = hrq .Spec .ScopeSelector
140+ utils .SetLabelsAnnotationsForScopedRQ (inst , hrq .Namespace , hrq .Name )
140141 }
141142
142143 // We only need to write back to the apiserver if the spec has changed
@@ -229,6 +230,38 @@ func (r *ResourceQuotaReconciler) getAncestorHRQs(inst *v1.ResourceQuota) []type
229230 return names
230231}
231232
233+ // findHRQNameForRQ finds HRQ name and namespace for a new RQ by searching through namespace and ancestor HRQs
234+ func (r * ResourceQuotaReconciler ) findHRQNameForRQ (inst * v1.ResourceQuota ) (types.NamespacedName , error ) {
235+ hrqnnm , err := utils .ScopedHRQNameFromRQ (inst )
236+ if err == nil {
237+ return hrqnnm , nil
238+ }
239+
240+ // New RQs that don't have the labels yet, so we need to search through the forest
241+ r .Forest .Lock ()
242+ defer r .Forest .Unlock ()
243+
244+ ns := r .Forest .Get (inst .Namespace )
245+
246+ // Check current namespace and all ancestors
247+ namespaces := []string {inst .Namespace }
248+ namespaces = append (namespaces , ns .AncestryNames ()... )
249+
250+ for _ , nsnm := range namespaces {
251+ ancestorNS := r .Forest .Get (nsnm )
252+ for _ , hrqName := range ancestorNS .HRQNames () {
253+ expectedRQName , err := utils .ScopedRQName (nsnm , hrqName )
254+ if err != nil {
255+ continue
256+ }
257+ if expectedRQName == inst .Name {
258+ return types.NamespacedName {Namespace : nsnm , Name : hrqName }, nil
259+ }
260+ }
261+ }
262+ return types.NamespacedName {}, fmt .Errorf ("no matching HRQ found for RQ: %s" , inst .Name )
263+ }
264+
232265// deleteRQ deletes a resource quota on the apiserver and a quota in on-memory if it exists. Otherwise,
233266// do nothing.
234267func (r * ResourceQuotaReconciler ) deleteRQ (ctx context.Context , log logr.Logger , inst * v1.ResourceQuota ) error {
@@ -313,6 +346,28 @@ func (r *ResourceQuotaReconciler) syncResourceLimits(ns *forest.Namespace, inst
313346 return true
314347}
315348
349+ // deleteLegacyScopedRQ deletes the legacy scoped RQ if it exists.
350+ func (r * ResourceQuotaReconciler ) deleteLegacyScopedRQ (ctx context.Context , log logr.Logger , newRQ * v1.ResourceQuota ) error {
351+ hrqnnm , err := utils .ScopedHRQNameFromRQ (newRQ )
352+ if err != nil {
353+ return fmt .Errorf ("get HRQ name from RQ: %w" , err )
354+ }
355+
356+ legacyRQName := utils .LegacyScopedRQName (hrqnnm .Name )
357+
358+ legacyRQ , err := r .getRQ (ctx , newRQ .Namespace , legacyRQName )
359+ if err != nil {
360+ if apierrors .IsNotFound (err ) {
361+ return nil
362+ } else {
363+ return err
364+ }
365+ }
366+
367+ log .Info ("Deleting legacy scoped RQ" , "legacyRQ" , legacyRQ .Name , "namespace" , legacyRQ .Namespace )
368+ return r .deleteRQ (ctx , log , legacyRQ )
369+ }
370+
316371// OnChangeNamespace enqueues the singleton in a specific namespace to trigger the reconciliation of
317372// the singleton for a given reason . This occurs in a goroutine so the caller doesn't block; since
318373// the reconciler is never garbage-collected, this is safe.
0 commit comments