Skip to content

Commit

Permalink
add schedule reconciler
Browse files Browse the repository at this point in the history
  • Loading branch information
airycanon committed Nov 9, 2023
1 parent cd1e7de commit 793ff5a
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 0 deletions.
71 changes: 71 additions & 0 deletions controllers/sync_reconciler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package controllers

// Importing necessary packages.
import (
"context"
"fmt"
"time"

"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// minSyncPeriod defines the minimum synchronization period
const minSyncPeriod = 30 * time.Second

type ScheduleOption func(options *ScheduleOptions)

// scheduleReconciler is a wrapper for Reconciler interface from controller-runtime package.
type scheduleReconciler struct {
reconciler reconcile.Reconciler

syncPeriod time.Duration
}

type ScheduleOptions struct {
// define the synchronization interval.
SyncPeriod time.Duration
}

// defaultOpts set default values for ScheduleOptions
func defaultOpts(opts ScheduleOptions) ScheduleOptions {
if opts.SyncPeriod <= minSyncPeriod {
opts.SyncPeriod = minSyncPeriod
}

return opts
}

// NewScheduleReconciler creates a new scheduleReconciler with the provided Reconciler and ScheduleOptions.
func NewScheduleReconciler(r reconcile.Reconciler, opts ...ScheduleOption) reconcile.Reconciler {
options := ScheduleOptions{}
for _, option := range opts {
option(&options)
}
options = defaultOpts(options)

return &scheduleReconciler{
reconciler: r,
syncPeriod: options.SyncPeriod,
}
}

// Reconcile is the method that will be called whenever an event occurs that the Reconciler should handle.
// It calls the Reconcile method of the embedded Reconciler and adjusts the RequeueAfter
func (s *scheduleReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
if s.reconciler == nil {
return reconcile.Result{}, fmt.Errorf("reconciler should not be empty")
}

result, err := s.reconciler.Reconcile(ctx, request)
if err != nil {
return result, err
}

// set the requeue after to the SyncPeriod of the scheduleReconciler
// ensure that the reconcile run when the result does not require requeue
if !result.Requeue && result.RequeueAfter <= 0 {
result.RequeueAfter = s.syncPeriod
}

return result, nil
}
107 changes: 107 additions & 0 deletions controllers/sync_reconciler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package controllers

import (
"context"
"fmt"
"testing"
"time"

. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func TestNewScheduleReconciler(t *testing.T) {
r := NewScheduleReconciler(nil)
sr, ok := r.(*scheduleReconciler)

g := NewGomegaWithT(t)
g.Expect(ok).To(BeTrue())
g.Expect(sr.syncPeriod).To(Equal(minSyncPeriod))

r = NewScheduleReconciler(reconcile.Func(emptyReconciler), func(options *ScheduleOptions) {
options.SyncPeriod = 1 * time.Minute
})
sr, ok = r.(*scheduleReconciler)
g.Expect(ok).To(BeTrue())
g.Expect(sr.syncPeriod).To(Equal(1 * time.Minute))
}

func TestSyncReconcilerReconcile(t *testing.T) {
cases := map[string]struct {
r reconcile.Reconciler
eval func(types.Gomega, reconcile.Result, error)
}{
"nil reconciler": {
r: nil,
eval: func(g types.Gomega, result reconcile.Result, err error) {
g.Expect(result.IsZero()).To(BeTrue())
g.Expect(err).NotTo(BeNil())
},
},
"empty reconciler": {
r: reconcile.Func(emptyReconciler),
eval: func(g types.Gomega, result reconcile.Result, err error) {
g.Expect(result.Requeue).To(BeFalse())
g.Expect(result.RequeueAfter).To(Equal(1 * time.Minute))
g.Expect(err).To(BeNil())
},
},
"reconciler with error": {
r: reconcile.Func(reconcilerWithError),
eval: func(g types.Gomega, result reconcile.Result, err error) {
g.Expect(result.IsZero()).To(BeTrue())
g.Expect(err).NotTo(BeNil())
},
},
"reconciler with requeue": {
r: reconcile.Func(reconcilerWithRequeue),
eval: func(g types.Gomega, result reconcile.Result, err error) {
g.Expect(result.Requeue).To(BeTrue())
g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
g.Expect(err).To(BeNil())
},
},
"reconciler with requeue after": {
r: reconcile.Func(reconcilerWithRequeueAfter),
eval: func(g types.Gomega, result reconcile.Result, err error) {
g.Expect(result.Requeue).To(BeFalse())
g.Expect(result.RequeueAfter).To(Equal(2 * time.Minute))
g.Expect(err).To(BeNil())
},
},
}

for name, item := range cases {
t.Run(name, func(t *testing.T) {
g := NewGomegaWithT(t)
r := NewScheduleReconciler(item.r, func(options *ScheduleOptions) {
options.SyncPeriod = 1 * time.Minute
})
result, err := r.Reconcile(context.Background(), reconcile.Request{})
item.eval(g, result, err)
})
}
}

func emptyReconciler(_ context.Context, _ reconcile.Request) (reconcile.Result, error) {
return reconcile.Result{}, nil
}

func reconcilerWithError(_ context.Context, _ reconcile.Request) (reconcile.Result, error) {
result := reconcile.Result{}

return result, fmt.Errorf("test error")
}

func reconcilerWithRequeue(_ context.Context, _ reconcile.Request) (reconcile.Result, error) {
result := reconcile.Result{Requeue: true}

return result, nil
}

func reconcilerWithRequeueAfter(_ context.Context, _ reconcile.Request) (reconcile.Result, error) {
result := reconcile.Result{RequeueAfter: 2 * time.Minute}

return result, nil
}

0 comments on commit 793ff5a

Please sign in to comment.