diff --git a/VERSION b/VERSION index af33e75..964a04d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.22.0-dev \ No newline at end of file +v0.23.0 \ No newline at end of file diff --git a/docs/libs/status.md b/docs/libs/status.md index 76fad64..a92149a 100644 --- a/docs/libs/status.md +++ b/docs/libs/status.md @@ -164,6 +164,7 @@ You can then `Build()` the status updater and run `UpdateStatus()` to do the act - By using `WithSmartRequeue`, the [smart requeuing logic](./smartrequeue.md) can be used. - A `smartrequeue.Store` is required to be configured outside of the status updater, because it has to be persisted across multiple reconciliations. - It is also possible to use the smart requeue logic explicitly and modify the `ReconcileResult`'s `Result` field with the returned value, but the integration should be easier to use, since both, the smart requeue logic as well as the status updater, return a `reconcile.Result` and an `error`, which are intended to be directly used as return values for the `Reconcile` method. + - The `WithSmartRequeue` function takes `SmartRequeueConditional`s as optional arguments, which are basically functions that take the `ReconcileResult` and return a smart requeue value (see below). This is especially useful to set the requeue depending on the object's new conditions, which would otherwise be difficult, because the conditions have not yet been updated before `UpdateStatus` is called and the requeue time has already been determined when `UpdateStatus` returns. ### The ReconcileResult diff --git a/go.mod b/go.mod index 17e4e6d..072d88b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/google/uuid v1.6.0 github.com/onsi/ginkgo/v2 v2.25.3 github.com/onsi/gomega v1.38.2 - github.com/openmcp-project/controller-utils/api v0.22.0 + github.com/openmcp-project/controller-utils/api v0.23.0 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.27.0 diff --git a/pkg/controller/status_updater.go b/pkg/controller/status_updater.go index 58704b7..53ae95d 100644 --- a/pkg/controller/status_updater.go +++ b/pkg/controller/status_updater.go @@ -122,6 +122,7 @@ func (b *StatusUpdaterBuilder[Obj]) WithCustomUpdateFunc(f func(obj Obj, rr Reco } type SmartRequeueAction string +type SmartRequeueConditional[Obj client.Object] func(rr ReconcileResult[Obj]) SmartRequeueAction const ( SR_BACKOFF SmartRequeueAction = "Backoff" @@ -135,11 +136,17 @@ const ( // - "Backoff": the object is requeued with an increasing backoff, as specified in the store. // - "Reset": the object is requeued, but the backoff is reset to its minimal value, as specified in the store. // - "NoRequeue": the object is not requeued. +// +// If any SmartRequeueConditionals are passed in, they will be evaluated in order and can override the action set in the ReconcileResult. +// As determining the requeue time is the last thing that happens in the status updater, these functions can be used react to the final status of the reconciled object, which might not be known earlier in the reconciliation +// (e.g. because the conditions were only updated in the status updater). +// // If the 'Result' field in the ReconcileResult has a non-zero RequeueAfter value set, that one is used if it is earlier than the one from smart requeue or if "NoRequeue" has been specified. // This function only has an effect if the Object in the ReconcileResult is not nil, the smart requeue store is not nil, and the action is one of the known values. // Also, if a reconciliation error occurred, the requeue interval will be reset, but no requeueAfter duration will be set, because controller-runtime will take care of requeuing the object anyway. -func (b *StatusUpdaterBuilder[Obj]) WithSmartRequeue(store *smartrequeue.Store) *StatusUpdaterBuilder[Obj] { +func (b *StatusUpdaterBuilder[Obj]) WithSmartRequeue(store *smartrequeue.Store, smartRequeueConditionals ...SmartRequeueConditional[Obj]) *StatusUpdaterBuilder[Obj] { b.internal.smartRequeueStore = store + b.internal.smartRequeueConditionals = smartRequeueConditionals return b } @@ -182,6 +189,7 @@ type statusUpdater[Obj client.Object] struct { eventRecorder record.EventRecorder eventVerbosity conditions.EventVerbosity smartRequeueStore *smartrequeue.Store + smartRequeueConditionals []SmartRequeueConditional[Obj] } func newStatusUpdater[Obj client.Object]() *statusUpdater[Obj] { @@ -295,6 +303,11 @@ func (s *statusUpdater[Obj]) UpdateStatus(ctx context.Context, c client.Client, if rr.ReconcileError != nil { srRes, _ = s.smartRequeueStore.For(rr.Object).Error(rr.ReconcileError) } else { + for _, srcFunc := range s.smartRequeueConditionals { + if srcFunc != nil { + rr.SmartRequeue = srcFunc(rr) + } + } switch rr.SmartRequeue { case SR_BACKOFF: srRes, _ = s.smartRequeueStore.For(rr.Object).Backoff() diff --git a/pkg/controller/status_updater_test.go b/pkg/controller/status_updater_test.go index 8c22c8b..d73703e 100644 --- a/pkg/controller/status_updater_test.go +++ b/pkg/controller/status_updater_test.go @@ -239,6 +239,28 @@ var _ = Describe("Status Updater", func() { Expect(res.RequeueAfter).To(Equal(30 * time.Second)) }) + It("should use the SmartRequeueConditional functions if specified", func() { + env := testutils.NewEnvironmentBuilder().WithFakeClient(coScheme).WithInitObjectPath("testdata", "test-02").WithDynamicObjectsWithStatus(&CustomObject{}).Build() + obj := &CustomObject{} + Expect(env.Client().Get(env.Ctx, controller.ObjectKey("status", "default"), obj)).To(Succeed()) + rr := controller.ReconcileResult[*CustomObject]{ + Object: obj, + Conditions: dummyConditions(), + SmartRequeue: controller.SR_NO_REQUEUE, + } + store := smartrequeue.NewStore(1*time.Second, 10*time.Second, 2.0) + su := preconfiguredStatusUpdaterBuilder().WithPhaseUpdateFunc(func(obj *CustomObject, rr controller.ReconcileResult[*CustomObject]) (string, error) { + return PhaseSucceeded, nil + }).WithSmartRequeue(store, func(rr controller.ReconcileResult[*CustomObject]) controller.SmartRequeueAction { + return controller.SR_NO_REQUEUE + }, func(rr controller.ReconcileResult[*CustomObject]) controller.SmartRequeueAction { + return controller.SR_RESET + }).Build() + res, err := su.UpdateStatus(env.Ctx, env.Client(), rr) + Expect(err).ToNot(HaveOccurred()) + Expect(res.RequeueAfter).To(Equal(1 * time.Second)) + }) + }) Context("GenerateCreateConditionFunc", func() {