Skip to content

Commit 2749093

Browse files
authored
chore(core): refactor cron source (#1663)
* chore(core): refactor cron source - Make cron source independent of the garbage collection manager. Signed-off-by: Ivan Mikheykin <[email protected]>
1 parent 75ae7a0 commit 2749093

File tree

6 files changed

+81
-44
lines changed

6 files changed

+81
-44
lines changed

images/virtualization-artifact/pkg/controller/gc/cron_source.go

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"time"
2323

2424
"github.com/robfig/cron/v3"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526
"k8s.io/apimachinery/pkg/types"
2627
"k8s.io/client-go/util/workqueue"
2728
"k8s.io/utils/clock"
@@ -33,33 +34,41 @@ import (
3334
"github.com/deckhouse/virtualization-controller/pkg/logger"
3435
)
3536

37+
/**
38+
CronSource is an implementation of the controller-runtime Source interface.
39+
It periodically triggers and emit events for a list of objects.
40+
41+
The component is independent of kubernetes client: developer should implement
42+
ObjectLister interface that CronSource will use to determine what to enqueue on trigger.
43+
44+
NewSingleObjectLister can be used if the main objective is to get periodical event,
45+
but specific namespace and name are not important. Also, for this situation
46+
object name can be used to distinguish cron trigger from the kubernetes trigger.
47+
*/
48+
3649
var _ source.Source = &CronSource{}
3750

3851
const sourceName = "CronSource"
3952

40-
type SourceGCManager interface {
41-
ListForDelete(ctx context.Context, now time.Time) ([]client.Object, error)
42-
}
43-
44-
func NewCronSource(scheduleSpec string, mgr SourceGCManager, log *log.Logger) (*CronSource, error) {
53+
func NewCronSource(scheduleSpec string, objLister ObjectLister, log *log.Logger) (*CronSource, error) {
4554
schedule, err := cron.ParseStandard(scheduleSpec)
4655
if err != nil {
4756
return nil, fmt.Errorf("parsing standard spec %q: %w", scheduleSpec, err)
4857
}
4958

5059
return &CronSource{
51-
schedule: schedule,
52-
mgr: mgr,
53-
log: log.With("WatchSource", sourceName),
54-
clock: &clock.RealClock{},
60+
schedule: schedule,
61+
objLister: objLister,
62+
log: log.With("watchSource", sourceName),
63+
clock: &clock.RealClock{},
5564
}, nil
5665
}
5766

5867
type CronSource struct {
59-
schedule cron.Schedule
60-
mgr SourceGCManager
61-
log *log.Logger
62-
clock clock.Clock
68+
schedule cron.Schedule
69+
objLister ObjectLister
70+
log *log.Logger
71+
clock clock.Clock
6372
}
6473

6574
func (c *CronSource) Start(ctx context.Context, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) error {
@@ -70,28 +79,29 @@ func (c *CronSource) Start(ctx context.Context, queue workqueue.TypedRateLimitin
7079
case <-ctx.Done():
7180
return
7281
case <-c.clock.After(nextTime):
73-
c.addObjects(ctx, queue.Add)
82+
c.enqueueObjects(ctx, queue.Add)
7483
nextTime = nextScheduleTimeDuration(c.schedule, c.clock.Now())
7584
}
7685
}
7786
}()
7887
return nil
7988
}
8089

81-
func (c *CronSource) addObjects(ctx context.Context, addToQueue func(reconcile.Request)) {
82-
objs, err := c.mgr.ListForDelete(ctx, c.clock.Now())
90+
func (c *CronSource) enqueueObjects(ctx context.Context, queueAddFunc func(reconcile.Request)) {
91+
now := c.clock.Now()
92+
objs, err := c.objLister.List(ctx, now)
8393
if err != nil {
8494
c.log.Error("Failed to get ObjectList for delete", logger.SlogErr(err))
8595
return
8696
}
8797

8898
if len(objs) == 0 {
89-
c.log.Debug("No resources, skip")
99+
c.log.Debug(fmt.Sprintf("No resources at %s, skip queueing", now))
90100
return
91101
}
92102

93103
for _, obj := range objs {
94-
addToQueue(reconcile.Request{
104+
queueAddFunc(reconcile.Request{
95105
NamespacedName: types.NamespacedName{
96106
Namespace: obj.GetNamespace(),
97107
Name: obj.GetName(),
@@ -104,3 +114,33 @@ func (c *CronSource) addObjects(ctx context.Context, addToQueue func(reconcile.R
104114
func nextScheduleTimeDuration(schedule cron.Schedule, now time.Time) time.Duration {
105115
return schedule.Next(now).Sub(now)
106116
}
117+
118+
type ObjectLister interface {
119+
List(ctx context.Context, now time.Time) ([]client.Object, error)
120+
}
121+
122+
type ObjectListerImpl struct {
123+
ListFunc func(ctx context.Context, now time.Time) ([]client.Object, error)
124+
}
125+
126+
func (o *ObjectListerImpl) List(ctx context.Context, now time.Time) ([]client.Object, error) {
127+
if o.ListFunc == nil {
128+
return nil, nil
129+
}
130+
return o.ListFunc(ctx, now)
131+
}
132+
133+
func NewObjectLister(listFunc func(ctx context.Context, now time.Time) ([]client.Object, error)) *ObjectListerImpl {
134+
return &ObjectListerImpl{listFunc}
135+
}
136+
137+
func NewSingleObjectLister(namespace, name string) *ObjectListerImpl {
138+
return &ObjectListerImpl{ListFunc: func(ctx context.Context, now time.Time) ([]client.Object, error) {
139+
return []client.Object{&metav1.PartialObjectMetadata{
140+
ObjectMeta: metav1.ObjectMeta{
141+
Namespace: namespace,
142+
Name: name,
143+
},
144+
}}, nil
145+
}}
146+
}

images/virtualization-artifact/pkg/controller/gc/cron_source_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232

3333
var _ = Describe("CronSource", func() {
3434
const (
35-
// Every day a 00:00
35+
// Every day at 00:00
3636
scheduleSpec = "0 0 * * *"
3737
)
3838

@@ -58,7 +58,7 @@ var _ = Describe("CronSource", func() {
5858
})
5959

6060
newSource := func(scheduleSpec string) *CronSource {
61-
source, err := NewCronSource(scheduleSpec, mgr, log)
61+
source, err := NewCronSource(scheduleSpec, NewObjectLister(mgr.ListForDelete), log)
6262
Expect(err).NotTo(HaveOccurred())
6363
source.clock = fakeClock
6464
return source

images/virtualization-artifact/pkg/controller/gc/fake.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,7 @@ const (
102102
fakeObjectPhaseCompleted = "Completed"
103103
)
104104

105-
var (
106-
_ SourceGCManager = &fakeGCManager{}
107-
_ ReconcileGCManager = &fakeGCManager{}
108-
)
105+
var _ ReconcileGCManager = &fakeGCManager{}
109106

110107
func newFakeGCManager(client client.Client, ttl time.Duration, max int) *fakeGCManager {
111108
if ttl == 0 {

images/virtualization-artifact/pkg/controller/gc/gc_controller.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ limitations under the License.
1717
package gc
1818

1919
import (
20+
"context"
21+
"time"
22+
2023
"sigs.k8s.io/controller-runtime/pkg/client"
2124
"sigs.k8s.io/controller-runtime/pkg/manager"
22-
"sigs.k8s.io/controller-runtime/pkg/source"
2325

2426
"github.com/deckhouse/deckhouse/pkg/log"
2527
"github.com/deckhouse/virtualization-controller/pkg/logger"
@@ -28,22 +30,29 @@ import (
2830
type ReconcileGCManager interface {
2931
New() client.Object
3032
ShouldBeDeleted(obj client.Object) bool
33+
ListForDelete(ctx context.Context, now time.Time) ([]client.Object, error)
3134
}
3235

3336
func SetupGcController(
3437
controllerName string,
3538
mgr manager.Manager,
3639
log *log.Logger,
37-
watchSource source.Source,
40+
schedule string,
3841
gcMgr ReconcileGCManager,
3942
) error {
4043
log = log.With(logger.SlogController(controllerName))
44+
45+
cronSource, err := NewCronSource(schedule, NewObjectLister(gcMgr.ListForDelete), log)
46+
if err != nil {
47+
return err
48+
}
49+
4150
reconciler := NewReconciler(mgr.GetClient(),
42-
watchSource,
51+
cronSource,
4352
gcMgr,
4453
)
4554

46-
err := reconciler.SetupWithManager(controllerName, mgr, log)
55+
err = reconciler.SetupWithManager(controllerName, mgr, log)
4756
if err != nil {
4857
return err
4958
}

images/virtualization-artifact/pkg/controller/vm/gc.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,11 @@ func SetupGC(
3838
) error {
3939
mgrClient := mgr.GetClient()
4040
vmimGCMgr := newVMIMGCManager(mgrClient, gcSettings.TTL.Duration, 10)
41-
source, err := gc.NewCronSource(gcSettings.Schedule, vmimGCMgr, log.With("resource", "vmi-migration"))
42-
if err != nil {
43-
return err
44-
}
4541

4642
return gc.SetupGcController(gcVMMigrationControllerName,
4743
mgr,
48-
log,
49-
source,
44+
log.With("resource", "vmi-migration"),
45+
gcSettings.Schedule,
5046
vmimGCMgr,
5147
)
5248
}
@@ -65,6 +61,8 @@ func newVMIMGCManager(client client.Client, ttl time.Duration, max int) *vmimGCM
6561
}
6662
}
6763

64+
var _ gc.ReconcileGCManager = &vmimGCManager{}
65+
6866
type vmimGCManager struct {
6967
client client.Client
7068
ttl time.Duration

images/virtualization-artifact/pkg/controller/vmop/gc.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,11 @@ const gcControllerName = "vmop-gc-controller"
3434

3535
func SetupGC(mgr manager.Manager, log *log.Logger, gcSettings config.BaseGcSettings) error {
3636
vmopGCMgr := newVMOPGCManager(mgr.GetClient(), gcSettings.TTL.Duration, 10)
37-
source, err := gc.NewCronSource(gcSettings.Schedule, vmopGCMgr, log.With("resource", "vmop"))
38-
if err != nil {
39-
return err
40-
}
4137

4238
return gc.SetupGcController(gcControllerName,
4339
mgr,
44-
log,
45-
source,
40+
log.With("resource", "vmop"),
41+
gcSettings.Schedule,
4642
vmopGCMgr,
4743
)
4844
}
@@ -61,10 +57,7 @@ func newVMOPGCManager(client client.Client, ttl time.Duration, max int) *vmopGCM
6157
}
6258
}
6359

64-
var (
65-
_ gc.ReconcileGCManager = &vmopGCManager{}
66-
_ gc.SourceGCManager = &vmopGCManager{}
67-
)
60+
var _ gc.ReconcileGCManager = &vmopGCManager{}
6861

6962
type vmopGCManager struct {
7063
client client.Client

0 commit comments

Comments
 (0)