@@ -26,7 +26,6 @@ import (
26
26
admissionv1 "k8s.io/api/admission/v1"
27
27
corev1 "k8s.io/api/core/v1"
28
28
apierrors "k8s.io/apimachinery/pkg/api/errors"
29
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30
29
"k8s.io/client-go/util/retry"
31
30
"k8s.io/klog/v2"
32
31
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -50,41 +49,70 @@ var _ admission.Handler = &ValidatingAdmission{}
50
49
type frqProcessOutcome struct {
51
50
validationMessages []string
52
51
updatedFRQCount int
53
- noFRQsFound bool // True if this attempt found no FRQs
54
- earlyExitResponse * admission. Response // Response if this attempt decided to exit early (denial, permanent error)
55
- ProcessError error // Error for RetryOnConflict (e.g., conflict error or nil for success/final decision)
52
+ noFRQsFound bool // True if this attempt found no FRQs
53
+ earlyExitError error // Response if this attempt decided to exit early (denial, permanent error)
54
+ ProcessError error // Error for RetryOnConflict (e.g., conflict error or nil for success/final decision)
56
55
}
57
56
58
57
// Handle implements admission.Handler interface.
59
58
func (v * ValidatingAdmission ) Handle (ctx context.Context , req admission.Request ) admission.Response {
59
+ rb , oldRB , err := v .decodeRBs (req )
60
+ if err != nil {
61
+ return admission .Errored (http .StatusBadRequest , err )
62
+ }
63
+ klog .V (2 ).Infof ("Processing ResourceBinding(%s/%s) for request: %s (%s)" , rb .Namespace , rb .Name , req .Operation , req .UID )
64
+
65
+ if err := v .validateFederatedResourceQuota (ctx , req , rb , oldRB ); err != nil {
66
+ if apierrors .IsInternalError (err ) {
67
+ klog .Errorf ("Internal error while processing ResourceBinding %s/%s: %v" , rb .Namespace , rb .Name , err )
68
+ return admission .Errored (http .StatusInternalServerError , err )
69
+ }
70
+ klog .Errorf ("Admission denied for ResourceBinding %s/%s: %v" , rb .Namespace , rb .Name , err )
71
+ return admission .Denied (err .Error ())
72
+ }
73
+
74
+ // further validation can be added here if needed
75
+
76
+ return admission .Allowed ("" )
77
+ }
78
+
79
+ // validateFederatedResourceQuota checks the FederatedResourceQuota for the ResourceBinding.
80
+ // It returns a list of errors if validation fails, or nil if it passes.
81
+ func (v * ValidatingAdmission ) validateFederatedResourceQuota (ctx context.Context , req admission.Request , rb , oldRB * workv1alpha2.ResourceBinding ) error {
60
82
if ! features .FeatureGate .Enabled (features .FederatedQuotaEnforcement ) {
61
83
klog .V (5 ).Infof ("FederatedQuotaEnforcement feature gate is disabled, skipping validation for ResourceBinding %s/%s" , req .Namespace , req .Name )
62
- return admission . Allowed ( "" )
84
+ return nil
63
85
}
64
86
65
- isDryRun := req .DryRun != nil && * req .DryRun
87
+ if req .Operation == admissionv1 .Create && len (rb .Spec .Clusters ) == 0 {
88
+ klog .V (4 ).Infof ("ResourceBinding %s/%s is being created but not yet scheduled, skipping quota validation." , rb .Namespace , rb .Name )
89
+ return nil
90
+ }
66
91
67
- rb , oldRB , extractResp := v .decodeAndValidateRBs (req )
68
- if extractResp != nil {
69
- return * extractResp
92
+ if req .Operation == admissionv1 .Update {
93
+ if ! isQuotaRelevantFieldChanged (oldRB , rb ) {
94
+ klog .V (4 ).Infof ("ResourceBinding %s/%s updated, but no quota relevant fields changed, skipping quota validation." , rb .Namespace , rb .Name )
95
+ return nil
96
+ }
70
97
}
71
98
72
- newRbTotalUsage , oldRbTotalUsage , respCalc := v .calculateRBUsages (rb , oldRB )
73
- if respCalc != nil {
74
- return * respCalc
99
+ isDryRun := req .DryRun != nil && * req .DryRun
100
+
101
+ newRbTotalUsage , oldRbTotalUsage , err := v .calculateRBUsages (rb , oldRB )
102
+ if err != nil {
103
+ return err
75
104
}
76
105
77
106
totalRbDelta := calculateDelta (newRbTotalUsage , oldRbTotalUsage )
78
107
klog .V (4 ).Infof ("Calculated total RB delta for %s/%s: %v" , rb .Namespace , rb .Name , totalRbDelta )
79
108
80
109
if len (totalRbDelta ) == 0 {
81
110
klog .V (2 ).Infof ("No effective resource quantity delta for ResourceBinding %s/%s. Skipping quota validation." , rb .Namespace , rb .Name )
82
- return admission . Allowed ( "No effective resource quantity delta for ResourceBinding, skipping quota validation." )
111
+ return nil
83
112
}
84
113
85
- frqOutcome := v .processFRQsWithRetries (ctx , rb , totalRbDelta , isDryRun )
86
-
87
- return v .finalizeResponse (rb , frqOutcome )
114
+ outcome := v .processFRQsWithRetries (ctx , rb , totalRbDelta , isDryRun )
115
+ return v .handleFRQOutcome (rb , outcome )
88
116
}
89
117
90
118
func (v * ValidatingAdmission ) processFRQsWithRetries (ctx context.Context , rb * workv1alpha2.ResourceBinding , totalRbDelta corev1.ResourceList , isDryRun bool ) frqProcessOutcome {
@@ -108,9 +136,9 @@ func (v *ValidatingAdmission) executeFRQProcessingAttempt(ctx context.Context, r
108
136
var currentFrqsToUpdateStatus []* policyv1alpha1.FederatedResourceQuota
109
137
var currentValidationMessages []string
110
138
111
- frqList , listResp := v .listFRQs (ctx , rb .Namespace )
112
- if listResp != nil {
113
- outcome .earlyExitResponse = listResp
139
+ frqList , listError := v .listFRQs (ctx , rb .Namespace )
140
+ if listError != nil {
141
+ outcome .earlyExitError = listError
114
142
return outcome // attemptError is nil, signaling non-retryable handled error
115
143
}
116
144
@@ -121,9 +149,9 @@ func (v *ValidatingAdmission) executeFRQProcessingAttempt(ctx context.Context, r
121
149
}
122
150
123
151
for _ , frqItem := range frqList .Items {
124
- newStatus , msg , denialResp := v .processSingleFRQ (frqItem , rb .Namespace , rb .Name , totalRbDelta )
125
- if denialResp != nil {
126
- outcome .earlyExitResponse = denialResp
152
+ newStatus , msg , denialError := v .processSingleFRQ (& frqItem , rb .Namespace , rb .Name , totalRbDelta )
153
+ if denialError != nil {
154
+ outcome .earlyExitError = denialError
127
155
return outcome // attemptError is nil, request denied
128
156
}
129
157
if newStatus != nil {
@@ -145,8 +173,7 @@ func (v *ValidatingAdmission) executeFRQProcessingAttempt(ctx context.Context, r
145
173
}
146
174
errMsg := fmt .Sprintf ("permanent error updating FRQ statuses for RB %s/%s: %v" , rb .Namespace , rb .Name , updateErr )
147
175
klog .Error (errMsg )
148
- resp := admission .Errored (http .StatusInternalServerError , errors .New (errMsg ))
149
- outcome .earlyExitResponse = & resp
176
+ outcome .earlyExitError = apierrors .NewInternalError (errors .New (errMsg ))
150
177
return outcome // attemptError is nil, non-retryable handled error
151
178
}
152
179
@@ -155,20 +182,20 @@ func (v *ValidatingAdmission) executeFRQProcessingAttempt(ctx context.Context, r
155
182
return outcome // attemptError is nil, successful attempt
156
183
}
157
184
158
- func (v * ValidatingAdmission ) finalizeResponse (rb * workv1alpha2.ResourceBinding , outcome frqProcessOutcome ) admission. Response {
185
+ func (v * ValidatingAdmission ) handleFRQOutcome (rb * workv1alpha2.ResourceBinding , outcome frqProcessOutcome ) error {
159
186
if outcome .ProcessError != nil {
160
187
errMsg := fmt .Sprintf ("failed to apply FederatedResourceQuota updates after multiple retries for RB %s/%s: %v" , rb .Namespace , rb .Name , outcome .ProcessError )
161
188
klog .Error (errMsg )
162
- return admission . Errored ( http . StatusInternalServerError , errors .New (errMsg ))
189
+ return apierrors . NewInternalError ( errors .New (errMsg ))
163
190
}
164
191
165
- if outcome .earlyExitResponse != nil {
166
- return * outcome .earlyExitResponse
192
+ if outcome .earlyExitError != nil {
193
+ return outcome .earlyExitError
167
194
}
168
195
169
196
if outcome .noFRQsFound {
170
197
klog .V (2 ).Infof ("No FederatedResourceQuotas found in namespace %s for ResourceBinding %s. Allowing operation." , rb .Namespace , rb .Name )
171
- return admission . Allowed ( "No FederatedResourceQuotas found in the namespace, skipping quota check." )
198
+ return nil
172
199
}
173
200
174
201
finalMessage := fmt .Sprintf ("All relevant FederatedResourceQuota checks passed for ResourceBinding %s/%s." , rb .Namespace , rb .Name )
@@ -181,7 +208,7 @@ func (v *ValidatingAdmission) finalizeResponse(rb *workv1alpha2.ResourceBinding,
181
208
finalMessage = fmt .Sprintf ("%s No FRQs required update." , finalMessage )
182
209
}
183
210
klog .V (2 ).Infof ("Admission allowed for ResourceBinding %s/%s: %s" , rb .Namespace , rb .Name , finalMessage )
184
- return admission . Allowed ( finalMessage )
211
+ return nil
185
212
}
186
213
187
214
// updateFRQStatusesWithRetrySignal attempts to update FRQ statuses.
@@ -218,53 +245,37 @@ func (v *ValidatingAdmission) updateFRQStatusesWithRetrySignal(ctx context.Conte
218
245
return nil
219
246
}
220
247
221
- // decodeAndValidateRBs decodes current and old (for updates) ResourceBindings,
222
- // performs essential preliminary checks, and checks for relevant changes in update operations.
223
- // Returns the RBs or an admission response for early exit.
224
- func (v * ValidatingAdmission ) decodeAndValidateRBs (req admission.Request ) (
248
+ // decodeRBs decodes current and old (for updates) ResourceBindings.
249
+ // Returns the RBs or an admission response for early exit on decoding failure.
250
+ func (v * ValidatingAdmission ) decodeRBs (req admission.Request ) (
225
251
currentRB * workv1alpha2.ResourceBinding ,
226
- oldRB * workv1alpha2.ResourceBinding , // Will be nil if not an update or if not applicable
227
- earlyExitResp * admission. Response ,
252
+ oldRB * workv1alpha2.ResourceBinding , // Will be nil if not an update
253
+ earlyExitResp error ,
228
254
) {
229
255
decodedRB := & workv1alpha2.ResourceBinding {}
230
256
if err := v .Decoder .Decode (req , decodedRB ); err != nil {
231
257
klog .Errorf ("Failed to decode ResourceBinding %s/%s: %v" , req .Namespace , req .Name , err )
232
- resp := admission .Errored (http .StatusBadRequest , err )
233
- return nil , nil , & resp
258
+ return nil , nil , err
234
259
}
235
- klog .V (2 ).Infof ("Processing ResourceBinding(%s/%s) for request: %s (%s)" , decodedRB .Namespace , decodedRB .Name , req .Operation , req .UID )
236
260
237
- if req .Operation == admissionv1 .Create && len (decodedRB .Spec .Clusters ) == 0 {
238
- klog .V (4 ).Infof ("ResourceBinding %s/%s is being created but not yet scheduled, skipping quota validation." , decodedRB .Namespace , decodedRB .Name )
239
- resp := admission .Allowed ("ResourceBinding not yet scheduled for Create operation." )
240
- return decodedRB , nil , & resp
261
+ if req .Operation != admissionv1 .Update {
262
+ return decodedRB , nil , nil
241
263
}
242
264
243
- var decodedOldRB * workv1alpha2.ResourceBinding
244
- if req .Operation == admissionv1 .Update {
245
- tempOldRB := & workv1alpha2.ResourceBinding {}
246
- if err := v .Decoder .DecodeRaw (req .OldObject , tempOldRB ); err != nil {
247
- klog .Errorf ("Failed to decode old ResourceBinding %s/%s: %v" , req .Namespace , req .Name , err )
248
- resp := admission .Errored (http .StatusBadRequest , err )
249
- return decodedRB , nil , & resp
250
- }
251
- decodedOldRB = tempOldRB
252
-
253
- if ! isQuotaRelevantFieldChanged (decodedOldRB , decodedRB ) {
254
- klog .V (4 ).Infof ("ResourceBinding %s/%s updated, but no quota relevant fields changed, skipping quota validation." , decodedRB .Namespace , decodedRB .Name )
255
- resp := admission .Allowed ("No quota relevant fields changed." )
256
- return decodedRB , decodedOldRB , & resp
257
- }
265
+ decodedOldRB := & workv1alpha2.ResourceBinding {}
266
+ if err := v .Decoder .DecodeRaw (req .OldObject , decodedOldRB ); err != nil {
267
+ klog .Errorf ("Failed to decode old ResourceBinding %s/%s: %v" , req .Namespace , req .Name , err )
268
+ return nil , nil , err
258
269
}
270
+
259
271
return decodedRB , decodedOldRB , nil
260
272
}
261
273
262
- func (v * ValidatingAdmission ) calculateRBUsages (rb , oldRB * workv1alpha2.ResourceBinding ) (corev1.ResourceList , corev1.ResourceList , * admission. Response ) {
274
+ func (v * ValidatingAdmission ) calculateRBUsages (rb , oldRB * workv1alpha2.ResourceBinding ) (corev1.ResourceList , corev1.ResourceList , error ) {
263
275
newRbTotalUsage , err := calculateResourceUsage (rb )
264
276
if err != nil {
265
277
klog .Errorf ("Error calculating resource usage for new ResourceBinding %s/%s: %v" , rb .Namespace , rb .Name , err )
266
- resp := admission .Errored (http .StatusInternalServerError , err )
267
- return nil , nil , & resp
278
+ return nil , nil , apierrors .NewInternalError (err )
268
279
}
269
280
klog .V (4 ).Infof ("Calculated total usage for incoming RB %s/%s: %v" , rb .Namespace , rb .Name , newRbTotalUsage )
270
281
@@ -273,26 +284,24 @@ func (v *ValidatingAdmission) calculateRBUsages(rb, oldRB *workv1alpha2.Resource
273
284
oldRbTotalUsage , err = calculateResourceUsage (oldRB )
274
285
if err != nil {
275
286
klog .Errorf ("Error calculating resource usage for old ResourceBinding %s/%s: %v" , oldRB .Namespace , oldRB .Name , err )
276
- resp := admission .Errored (http .StatusInternalServerError , err )
277
- return nil , nil , & resp
287
+ return nil , nil , apierrors .NewInternalError (err )
278
288
}
279
289
klog .V (4 ).Infof ("Calculated total usage for old RB %s/%s: %v" , oldRB .Namespace , oldRB .Name , oldRbTotalUsage )
280
290
}
281
291
return newRbTotalUsage , oldRbTotalUsage , nil
282
292
}
283
293
284
- func (v * ValidatingAdmission ) listFRQs (ctx context.Context , namespace string ) (* policyv1alpha1.FederatedResourceQuotaList , * admission. Response ) {
294
+ func (v * ValidatingAdmission ) listFRQs (ctx context.Context , namespace string ) (* policyv1alpha1.FederatedResourceQuotaList , error ) {
285
295
frqList := & policyv1alpha1.FederatedResourceQuotaList {}
286
296
if err := v .Client .List (ctx , frqList , client .InNamespace (namespace )); err != nil {
287
297
klog .Errorf ("Failed to list FederatedResourceQuotas in namespace %s: %v" , namespace , err )
288
- resp := admission .Errored (http .StatusInternalServerError , fmt .Errorf ("failed to list FederatedResourceQuotas: %w" , err ))
289
- return nil , & resp
298
+ return nil , apierrors .NewInternalError (fmt .Errorf ("failed to list FederatedResourceQuotas: %w" , err ))
290
299
}
291
300
klog .V (2 ).Infof ("Found %d FederatedResourceQuotas in namespace %s to evaluate." , len (frqList .Items ), namespace )
292
301
return frqList , nil
293
302
}
294
303
295
- func (v * ValidatingAdmission ) processSingleFRQ (frqItem policyv1alpha1.FederatedResourceQuota , rbNamespace , rbName string , totalRbDelta corev1.ResourceList ) (newStatusToSet corev1.ResourceList , validationMsg string , denialResp * admission. Response ) {
304
+ func (v * ValidatingAdmission ) processSingleFRQ (frqItem * policyv1alpha1.FederatedResourceQuota , rbNamespace , rbName string , totalRbDelta corev1.ResourceList ) (newStatusToSet corev1.ResourceList , validationMsg string , err error ) {
296
305
klog .V (4 ).Infof ("Evaluating FRQ %s/%s for RB %s/%s" , frqItem .Namespace , frqItem .Name , rbNamespace , rbName )
297
306
if frqItem .Spec .Overall == nil {
298
307
klog .V (4 ).Infof ("FRQ %s/%s has no spec.overall defined, skipping its evaluation." , frqItem .Namespace , frqItem .Name )
@@ -312,31 +321,16 @@ func (v *ValidatingAdmission) processSingleFRQ(frqItem policyv1alpha1.FederatedR
312
321
313
322
isAllowed , errMsg := isAllowed (potentialNewOverallUsedForThisFRQ , frqItem )
314
323
if ! isAllowed {
315
- klog .Warningf ("Quota exceeded for FederatedResourceQuota %s/%s. ResourceBinding %s/%s will be denied." ,
316
- frqItem .Namespace , frqItem .Name , rbNamespace , rbName )
317
- resp := buildDenyResponse (errMsg )
318
- return nil , "" , resp
324
+ klog .Warningf ("Quota exceeded for FederatedResourceQuota %s/%s. ResourceBinding %s/%s will be denied. %s" ,
325
+ frqItem .Namespace , frqItem .Name , rbNamespace , rbName , errMsg )
326
+ return nil , "" , errors .New (errMsg )
319
327
}
320
328
321
329
msg := fmt .Sprintf ("Quota check passed for FRQ %s/%s." , frqItem .Namespace , frqItem .Name )
322
330
klog .V (3 ).Infof ("FRQ %s/%s will be updated. New OverallUsed: %v" , frqItem .Namespace , frqItem .Name , potentialNewOverallUsedForThisFRQ )
323
331
return potentialNewOverallUsedForThisFRQ , msg , nil
324
332
}
325
333
326
- func buildDenyResponse (errMsg string ) * admission.Response {
327
- resp := admission.Response {
328
- AdmissionResponse : admissionv1.AdmissionResponse {
329
- Allowed : false ,
330
- Result : & metav1.Status {
331
- Message : errMsg ,
332
- Reason : util .QuotaExceededReason ,
333
- Code : int32 (http .StatusForbidden ),
334
- },
335
- },
336
- }
337
- return & resp
338
- }
339
-
340
334
func calculateResourceUsage (rb * workv1alpha2.ResourceBinding ) (corev1.ResourceList , error ) {
341
335
if rb == nil || rb .Spec .ReplicaRequirements == nil || len (rb .Spec .ReplicaRequirements .ResourceRequest ) == 0 || len (rb .Spec .Clusters ) == 0 {
342
336
return corev1.ResourceList {}, nil
@@ -452,7 +446,7 @@ func addResourceLists(list1, list2 corev1.ResourceList) corev1.ResourceList {
452
446
return result
453
447
}
454
448
455
- func isAllowed (requested corev1.ResourceList , frqItem policyv1alpha1.FederatedResourceQuota ) (bool , string ) {
449
+ func isAllowed (requested corev1.ResourceList , frqItem * policyv1alpha1.FederatedResourceQuota ) (bool , string ) {
456
450
allowedLimits := frqItem .Spec .Overall
457
451
if allowedLimits == nil {
458
452
return true , ""
0 commit comments