@@ -19,9 +19,9 @@ package controllers
1919import (
2020 "context"
2121 "fmt"
22+ "time"
2223
2324 "k8s.io/apimachinery/pkg/api/errors"
24- "k8s.io/apimachinery/pkg/api/meta"
2525 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2626 "k8s.io/apimachinery/pkg/runtime"
2727 utilerrors "k8s.io/apimachinery/pkg/util/errors"
@@ -35,6 +35,7 @@ import (
3535
3636 s3v1alpha1 "github.com/InseeFrLab/s3-operator/api/v1alpha1"
3737 "github.com/InseeFrLab/s3-operator/controllers/s3/factory"
38+ "github.com/InseeFrLab/s3-operator/controllers/utils"
3839)
3940
4041// BucketReconciler reconciles a Bucket object
@@ -57,8 +58,7 @@ const bucketFinalizer = "s3.onyxia.sh/finalizer"
5758// For more details, check Reconcile and its Result here:
5859// - https://pkg.go.dev/sigs.k8s.io/[email protected] /pkg/reconcile 5960func (r * BucketReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
60- errorLogger := log .FromContext (ctx )
61- logger := ctrl .Log .WithName ("bucketReconcile" )
61+ logger := log .FromContext (ctx )
6262
6363 // Checking for bucket resource existence
6464 bucketResource := & s3v1alpha1.Bucket {}
@@ -68,7 +68,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
6868 logger .Info ("The Bucket custom resource has been removed ; as such the Bucket controller is NOOP." , "req.Name" , req .Name )
6969 return ctrl.Result {}, nil
7070 }
71- errorLogger .Error (err , "An error occurred when attempting to read the Bucket resource from the Kubernetes cluster" )
71+ logger .Error (err , "An error occurred when attempting to read the Bucket resource from the Kubernetes cluster" )
7272 return ctrl.Result {}, err
7373 }
7474
@@ -82,7 +82,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
8282 // that we can retry during the next reconciliation.
8383 if err := r .finalizeBucket (bucketResource ); err != nil {
8484 // return ctrl.Result{}, err
85- errorLogger .Error (err , "an error occurred when attempting to finalize the bucket" , "bucket" , bucketResource .Spec .Name )
85+ logger .Error (err , "an error occurred when attempting to finalize the bucket" , "bucket" , bucketResource .Spec .Name )
8686 // return ctrl.Result{}, err
8787 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "BucketFinalizeFailed" ,
8888 fmt .Sprintf ("An error occurred when attempting to delete bucket [%s]" , bucketResource .Spec .Name ), err )
@@ -93,7 +93,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
9393 controllerutil .RemoveFinalizer (bucketResource , bucketFinalizer )
9494 err := r .Update (ctx , bucketResource )
9595 if err != nil {
96- errorLogger .Error (err , "an error occurred when removing finalizer from bucket" , "bucket" , bucketResource .Spec .Name )
96+ logger .Error (err , "an error occurred when removing finalizer from bucket" , "bucket" , bucketResource .Spec .Name )
9797 // return ctrl.Result{}, err
9898 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "BucketFinalizerRemovalFailed" ,
9999 fmt .Sprintf ("An error occurred when attempting to remove the finalizer from bucket [%s]" , bucketResource .Spec .Name ), err )
@@ -107,7 +107,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
107107 controllerutil .AddFinalizer (bucketResource , bucketFinalizer )
108108 err = r .Update (ctx , bucketResource )
109109 if err != nil {
110- errorLogger .Error (err , "an error occurred when adding finalizer from bucket" , "bucket" , bucketResource .Spec .Name )
110+ logger .Error (err , "an error occurred when adding finalizer from bucket" , "bucket" , bucketResource .Spec .Name )
111111 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "BucketFinalizerAddFailed" ,
112112 fmt .Sprintf ("An error occurred when attempting to add the finalizer from bucket [%s]" , bucketResource .Spec .Name ), err )
113113 }
@@ -118,7 +118,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
118118 // Check bucket existence on the S3 server
119119 found , err := r .S3Client .BucketExists (bucketResource .Spec .Name )
120120 if err != nil {
121- errorLogger .Error (err , "an error occurred while checking the existence of a bucket" , "bucket" , bucketResource .Spec .Name )
121+ logger .Error (err , "an error occurred while checking the existence of a bucket" , "bucket" , bucketResource .Spec .Name )
122122 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "BucketExistenceCheckFailed" ,
123123 fmt .Sprintf ("Checking existence of bucket [%s] from S3 instance has failed" , bucketResource .Spec .Name ), err )
124124 }
@@ -129,15 +129,15 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
129129 // Bucket creation
130130 err = r .S3Client .CreateBucket (bucketResource .Spec .Name )
131131 if err != nil {
132- errorLogger .Error (err , "an error occurred while creating a bucket" , "bucket" , bucketResource .Spec .Name )
132+ logger .Error (err , "an error occurred while creating a bucket" , "bucket" , bucketResource .Spec .Name )
133133 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "BucketCreationFailed" ,
134134 fmt .Sprintf ("Creation of bucket [%s] on S3 instance has failed" , bucketResource .Spec .Name ), err )
135135 }
136136
137137 // Setting quotas
138138 err = r .S3Client .SetQuota (bucketResource .Spec .Name , bucketResource .Spec .Quota .Default )
139139 if err != nil {
140- errorLogger .Error (err , "an error occurred while setting a quota on a bucket" , "bucket" , bucketResource .Spec .Name , "quota" , bucketResource .Spec .Quota .Default )
140+ logger .Error (err , "an error occurred while setting a quota on a bucket" , "bucket" , bucketResource .Spec .Name , "quota" , bucketResource .Spec .Quota .Default )
141141 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "SetQuotaOnBucketFailed" ,
142142 fmt .Sprintf ("Setting a quota of [%v] on bucket [%s] has failed" , bucketResource .Spec .Quota .Default , bucketResource .Spec .Name ), err )
143143 }
@@ -146,7 +146,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
146146 for _ , v := range bucketResource .Spec .Paths {
147147 err = r .S3Client .CreatePath (bucketResource .Spec .Name , v )
148148 if err != nil {
149- errorLogger .Error (err , "an error occurred while creating a path on a bucket" , "bucket" , bucketResource .Spec .Name , "path" , v )
149+ logger .Error (err , "an error occurred while creating a path on a bucket" , "bucket" , bucketResource .Spec .Name , "path" , v )
150150 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "CreatingPathOnBucketFailed" ,
151151 fmt .Sprintf ("Creating the path [%s] on bucket [%s] has failed" , v , bucketResource .Spec .Name ), err )
152152 }
@@ -163,7 +163,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
163163 // Checking effectiveQuota existence on the bucket
164164 effectiveQuota , err := r .S3Client .GetQuota (bucketResource .Spec .Name )
165165 if err != nil {
166- errorLogger .Error (err , "an error occurred while getting the quota for a bucket" , "bucket" , bucketResource .Spec .Name )
166+ logger .Error (err , "an error occurred while getting the quota for a bucket" , "bucket" , bucketResource .Spec .Name )
167167 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "BucketQuotaCheckFailed" ,
168168 fmt .Sprintf ("The check for a quota on bucket [%s] has failed" , bucketResource .Spec .Name ), err )
169169 }
@@ -180,7 +180,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
180180 if effectiveQuota != quotaToResetTo {
181181 err = r .S3Client .SetQuota (bucketResource .Spec .Name , quotaToResetTo )
182182 if err != nil {
183- errorLogger .Error (err , "an error occurred while resetting the quota for a bucket" , "bucket" , bucketResource .Spec .Name , "quotaToResetTo" , quotaToResetTo )
183+ logger .Error (err , "an error occurred while resetting the quota for a bucket" , "bucket" , bucketResource .Spec .Name , "quotaToResetTo" , quotaToResetTo )
184184 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "BucketQuotaUpdateFailed" ,
185185 fmt .Sprintf ("The quota update (%v => %v) on bucket [%s] has failed" , effectiveQuota , quotaToResetTo , bucketResource .Spec .Name ), err )
186186 }
@@ -196,15 +196,15 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
196196 for _ , pathInCr := range bucketResource .Spec .Paths {
197197 pathExists , err := r .S3Client .PathExists (bucketResource .Spec .Name , pathInCr )
198198 if err != nil {
199- errorLogger .Error (err , "an error occurred while checking a path's existence on a bucket" , "bucket" , bucketResource .Spec .Name , "path" , pathInCr )
199+ logger .Error (err , "an error occurred while checking a path's existence on a bucket" , "bucket" , bucketResource .Spec .Name , "path" , pathInCr )
200200 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "BucketPathCheckFailed" ,
201201 fmt .Sprintf ("The check for path [%s] on bucket [%s] has failed" , pathInCr , bucketResource .Spec .Name ), err )
202202 }
203203
204204 if ! pathExists {
205205 err = r .S3Client .CreatePath (bucketResource .Spec .Name , pathInCr )
206206 if err != nil {
207- errorLogger .Error (err , "an error occurred while creating a path on a bucket" , "bucket" , bucketResource .Spec .Name , "path" , pathInCr )
207+ logger .Error (err , "an error occurred while creating a path on a bucket" , "bucket" , bucketResource .Spec .Name , "path" , pathInCr )
208208 return r .SetBucketStatusConditionAndUpdate (ctx , bucketResource , "OperatorFailed" , metav1 .ConditionFalse , "BucketPathCreationFailed" ,
209209 fmt .Sprintf ("The creation of path [%s] on bucket [%s] has failed" , pathInCr , bucketResource .Spec .Name ), err )
210210 }
@@ -219,44 +219,16 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
219219
220220// SetupWithManager sets up the controller with the Manager.*
221221func (r * BucketReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
222- logger := ctrl .Log .WithName ("bucketEventFilter" )
223222 return ctrl .NewControllerManagedBy (mgr ).
224223 For (& s3v1alpha1.Bucket {}).
225224 // REF : https://sdk.operatorframework.io/docs/building-operators/golang/references/event-filtering/
226225 WithEventFilter (predicate.Funcs {
227226 UpdateFunc : func (e event.UpdateEvent ) bool {
228- // Only reconcile if :
229- // - Generation has changed
230- // or
231- // - Of all Conditions matching the last generation, none is in status "True"
232- // There is an implicit assumption that in such a case, the resource was once failing, but then transitioned
233- // to a functional state. We use this ersatz because lastTransitionTime appears to not work properly - see also
234- // comment in SetBucketStatusConditionAndUpdate() below.
235- newBucket , _ := e .ObjectNew .(* s3v1alpha1.Bucket )
236-
237- // 1 - Identifying the most recent generation
238- var maxGeneration int64 = 0
239- for _ , condition := range newBucket .Status .Conditions {
240- if condition .ObservedGeneration > maxGeneration {
241- maxGeneration = condition .ObservedGeneration
242- }
243- }
244- // 2 - Checking one of the conditions in most recent generation is True
245- conditionTrueInLastGeneration := false
246- for _ , condition := range newBucket .Status .Conditions {
247- if condition .ObservedGeneration == maxGeneration && condition .Status == metav1 .ConditionTrue {
248- conditionTrueInLastGeneration = true
249- }
250- }
251- predicate := e .ObjectOld .GetGeneration () != e .ObjectNew .GetGeneration () || ! conditionTrueInLastGeneration
252- if ! predicate {
253- logger .Info ("reconcile update event is filtered out" , "resource" , e .ObjectNew .GetName ())
254- }
255- return predicate
227+ // Only reconcile if generation has changed
228+ return e .ObjectOld .GetGeneration () != e .ObjectNew .GetGeneration ()
256229 },
257230 DeleteFunc : func (e event.DeleteEvent ) bool {
258231 // Evaluates to false if the object has been confirmed deleted.
259- logger .Info ("reconcile delete event is filtered out" , "resource" , e .Object .GetName ())
260232 return ! e .DeleteStateUnknown
261233 },
262234 }).
@@ -274,26 +246,17 @@ func (r *BucketReconciler) finalizeBucket(bucketResource *s3v1alpha1.Bucket) err
274246func (r * BucketReconciler ) SetBucketStatusConditionAndUpdate (ctx context.Context , bucketResource * s3v1alpha1.Bucket , conditionType string , status metav1.ConditionStatus , reason string , message string , srcError error ) (ctrl.Result , error ) {
275247 logger := log .FromContext (ctx )
276248
277- // It would seem LastTransitionTime does not work as intended (our understanding of the intent coming from this :
278- // https://pkg.go.dev/k8s.io/[email protected] /pkg/api/meta#SetStatusCondition). Whether we set the 279- // date manually or leave it out to have default behavior, the lastTransitionTime is NOT updated if the CR
280- // had that condition at least once in the past.
281- // For instance, with the following updates to a CR :
282- // - gen 1 : condition type = A
283- // - gen 2 : condition type = B
284- // - gen 3 : condition type = A again
285- // Then the condition with type A in CR Status will still have the lastTransitionTime dating back to gen 1.
286- // Because of this, lastTransitionTime cannot be reliably used to determine current state, which in turn had
287- // us turn to a less than ideal event filter (see above in SetupWithManager())
288- meta .SetStatusCondition (& bucketResource .Status .Conditions ,
289- metav1.Condition {
290- Type : conditionType ,
291- Status : status ,
292- Reason : reason ,
293- // LastTransitionTime: metav1.NewTime(time.Now()),
294- Message : message ,
295- ObservedGeneration : bucketResource .GetGeneration (),
296- })
249+ // We moved away from meta.SetStatusCondition, as the implementation did not allow for updating
250+ // lastTransitionTime if a Condition (as identified by Reason instead of Type) was previously
251+ // obtained and updated to again.
252+ bucketResource .Status .Conditions = utils .UpdateConditions (bucketResource .Status .Conditions , metav1.Condition {
253+ Type : conditionType ,
254+ Status : status ,
255+ Reason : reason ,
256+ LastTransitionTime : metav1 .NewTime (time .Now ()),
257+ Message : message ,
258+ ObservedGeneration : bucketResource .GetGeneration (),
259+ })
297260
298261 err := r .Status ().Update (ctx , bucketResource )
299262 if err != nil {
0 commit comments