Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.22.0-dev
v0.23.0
1 change: 1 addition & 0 deletions docs/libs/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion pkg/controller/status_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}

Expand Down Expand Up @@ -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] {
Expand Down Expand Up @@ -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()
Expand Down
22 changes: 22 additions & 0 deletions pkg/controller/status_updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down