Skip to content

Commit 9ef45a3

Browse files
authored
Fix event filters (#41), lastTransitionTime not updated in conditions, and path finalize (#42)
* fix(path): added error if no bucket found * fix: various fixes - rollback event filter changes - reimpl. of Set*StatusConditionAndUpdate functions (w/ new utils.go) - unify loggers in reconcile loop
1 parent 831b09b commit 9ef45a3

File tree

5 files changed

+119
-193
lines changed

5 files changed

+119
-193
lines changed

controllers/bucket_controller.go

Lines changed: 28 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ package controllers
1919
import (
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
5960
func (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.*
221221
func (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
274246
func (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

Comments
 (0)