Skip to content

Commit b95be72

Browse files
committed
feat(multicluster): implement multicluster lifecycle management and reconcile logic
1 parent 1d2683c commit b95be72

File tree

7 files changed

+1629
-55
lines changed

7 files changed

+1629
-55
lines changed

controller/lifecycle/controllerruntime/lifecycle.go

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,27 +64,11 @@ func (l *LifecycleManager) Spreader() *spread.Spreader {
6464
}
6565

6666
func (l *LifecycleManager) Reconcile(ctx context.Context, req ctrl.Request, instance runtimeobject.RuntimeObject) (ctrl.Result, error) {
67-
return lifecycle.Reconcile(ctx, req, instance, l.client, l)
68-
}
69-
70-
func (l *LifecycleManager) validateInterfaces(instance runtimeobject.RuntimeObject, log *logger.Logger) error {
71-
if l.Spreader() != nil {
72-
_, err := l.Spreader().ToRuntimeObjectSpreadReconcileStatusInterface(instance, log)
73-
if err != nil {
74-
return err
75-
}
76-
}
77-
if l.ConditionsManager() != nil {
78-
_, err := l.ConditionsManager().ToRuntimeObjectConditionsInterface(instance, log)
79-
if err != nil {
80-
return err
81-
}
82-
}
83-
return nil
67+
return lifecycle.Reconcile(ctx, req.NamespacedName, instance, l.client, l)
8468
}
8569

8670
func (l *LifecycleManager) SetupWithManagerBuilder(mgr ctrl.Manager, maxReconciles int, reconcilerName string, instance runtimeobject.RuntimeObject, debugLabelValue string, log *logger.Logger, eventPredicates ...predicate.Predicate) (*builder.Builder, error) {
87-
if err := l.validateInterfaces(instance, log); err != nil {
71+
if err := lifecycle.ValidateInterfaces(instance, log, l); err != nil {
8872
return nil, err
8973
}
9074

controller/lifecycle/lifecycle.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1414
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1515
"k8s.io/apimachinery/pkg/runtime"
16+
"k8s.io/apimachinery/pkg/types"
1617
ctrl "sigs.k8s.io/controller-runtime"
1718
"sigs.k8s.io/controller-runtime/pkg/client"
1819
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -43,21 +44,23 @@ type Config struct {
4344

4445
type PrepareContextFunc func(ctx context.Context, instance runtimeobject.RuntimeObject) (context.Context, errors.OperatorError)
4546

46-
func Reconcile(ctx context.Context, req ctrl.Request, instance runtimeobject.RuntimeObject, cl client.Client, l Lifecycle) (ctrl.Result, error) {
47+
func Reconcile(ctx context.Context, nName types.NamespacedName, instance runtimeobject.RuntimeObject, cl client.Client, l Lifecycle) (ctrl.Result, error) {
4748
ctx, span := otel.Tracer(l.Config().OperatorName).Start(ctx, fmt.Sprintf("%s.Reconcile", l.Config().ControllerName))
4849
defer span.End()
4950

5051
result := ctrl.Result{}
5152
reconcileId := uuid.New().String()
5253

53-
log := l.Log().MustChildLoggerWithAttributes("name", req.Name, "namespace", req.Namespace, "reconcile_id", reconcileId)
54-
sentryTags := sentry.Tags{"namespace": req.Namespace, "name": req.Name}
54+
log := l.Log().MustChildLoggerWithAttributes("name", nName.Name, "namespace", nName.Namespace, "reconcile_id", reconcileId)
55+
sentryTags := sentry.Tags{"namespace": nName.Namespace, "name": nName.Name}
5556

5657
ctx = logger.SetLoggerInContext(ctx, log)
5758
ctx = sentry.ContextWithSentryTags(ctx, sentryTags)
5859

5960
log.Info().Msg("start reconcile")
60-
err := cl.Get(ctx, req.NamespacedName, instance)
61+
62+
nn := types.NamespacedName{Namespace: nName.Namespace, Name: nName.Name}
63+
err := cl.Get(ctx, nn, instance)
6164
if err != nil {
6265
if kerrors.IsNotFound(err) {
6366
log.Info().Msg("instance not found. It was likely deleted")
@@ -380,3 +383,19 @@ func HandleOperatorError(ctx context.Context, operatorError errors.OperatorError
380383

381384
return ctrl.Result{}, nil
382385
}
386+
387+
func ValidateInterfaces(instance runtimeobject.RuntimeObject, log *logger.Logger, l Lifecycle) error {
388+
if l.Spreader() != nil {
389+
_, err := l.Spreader().ToRuntimeObjectSpreadReconcileStatusInterface(instance, log)
390+
if err != nil {
391+
return err
392+
}
393+
}
394+
if l.ConditionsManager() != nil {
395+
_, err := l.ConditionsManager().ToRuntimeObjectConditionsInterface(instance, log)
396+
if err != nil {
397+
return err
398+
}
399+
}
400+
return nil
401+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package multicluster
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
ctrl "sigs.k8s.io/controller-runtime"
8+
"sigs.k8s.io/controller-runtime/pkg/cluster"
9+
"sigs.k8s.io/controller-runtime/pkg/controller"
10+
"sigs.k8s.io/controller-runtime/pkg/predicate"
11+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
12+
mcbuilder "sigs.k8s.io/multicluster-runtime/pkg/builder"
13+
mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager"
14+
mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile"
15+
16+
"github.com/platform-mesh/golang-commons/controller/filter"
17+
"github.com/platform-mesh/golang-commons/controller/lifecycle"
18+
"github.com/platform-mesh/golang-commons/controller/lifecycle/conditions"
19+
"github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject"
20+
"github.com/platform-mesh/golang-commons/controller/lifecycle/spread"
21+
"github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine"
22+
"github.com/platform-mesh/golang-commons/logger"
23+
)
24+
25+
type ClusterGetter interface {
26+
GetCluster(ctx context.Context, clusterName string) (cluster.Cluster, error)
27+
}
28+
29+
type LifecycleManager struct {
30+
log *logger.Logger
31+
mgr ClusterGetter
32+
config lifecycle.Config
33+
subroutines []subroutine.Subroutine
34+
spreader *spread.Spreader
35+
conditionsManager *conditions.ConditionManager
36+
prepareContextFunc lifecycle.PrepareContextFunc
37+
}
38+
39+
func NewLifecycleManager(log *logger.Logger, operatorName string, controllerName string, mgr ClusterGetter, subroutines []subroutine.Subroutine) *LifecycleManager {
40+
log = log.MustChildLoggerWithAttributes("operator", operatorName, "controller", controllerName)
41+
return &LifecycleManager{
42+
log: log,
43+
mgr: mgr,
44+
subroutines: subroutines,
45+
config: lifecycle.Config{
46+
OperatorName: operatorName,
47+
ControllerName: controllerName,
48+
},
49+
}
50+
}
51+
52+
func (l *LifecycleManager) Config() lifecycle.Config {
53+
return l.config
54+
}
55+
func (l *LifecycleManager) Log() *logger.Logger {
56+
return l.log
57+
}
58+
func (l *LifecycleManager) Subroutines() []subroutine.Subroutine {
59+
return l.subroutines
60+
}
61+
func (l *LifecycleManager) PrepareContextFunc() lifecycle.PrepareContextFunc {
62+
return l.prepareContextFunc
63+
}
64+
func (l *LifecycleManager) ConditionsManager() *conditions.ConditionManager {
65+
return l.conditionsManager
66+
}
67+
func (l *LifecycleManager) Spreader() *spread.Spreader {
68+
return l.spreader
69+
}
70+
func (l *LifecycleManager) Reconcile(ctx context.Context, req mcreconcile.Request, instance runtimeobject.RuntimeObject) (ctrl.Result, error) {
71+
cl, err := l.mgr.GetCluster(ctx, req.ClusterName)
72+
if err != nil {
73+
return reconcile.Result{}, fmt.Errorf("failed to get cluster: %w", err)
74+
}
75+
client := cl.GetClient()
76+
return lifecycle.Reconcile(ctx, req.NamespacedName, instance, client, l)
77+
}
78+
func (l *LifecycleManager) SetupWithManagerBuilder(mgr mcmanager.Manager, maxReconciles int, reconcilerName string, instance runtimeobject.RuntimeObject, debugLabelValue string, log *logger.Logger, eventPredicates ...predicate.Predicate) (*mcbuilder.Builder, error) {
79+
if err := lifecycle.ValidateInterfaces(instance, log, l); err != nil {
80+
return nil, err
81+
}
82+
83+
if (l.ConditionsManager() != nil || l.Spreader() != nil) && l.Config().ReadOnly {
84+
return nil, fmt.Errorf("cannot use conditions or spread reconciles in read-only mode")
85+
}
86+
87+
eventPredicates = append([]predicate.Predicate{filter.DebugResourcesBehaviourPredicate(debugLabelValue)}, eventPredicates...)
88+
opts := controller.TypedOptions[mcreconcile.Request]{
89+
MaxConcurrentReconciles: maxReconciles,
90+
}
91+
return mcbuilder.ControllerManagedBy(mgr).
92+
Named(reconcilerName).
93+
For(instance).
94+
WithOptions(opts).
95+
WithEventFilter(predicate.And(eventPredicates...)), nil
96+
}
97+
func (l *LifecycleManager) SetupWithManager(mgr mcmanager.Manager, maxReconciles int, reconcilerName string, instance runtimeobject.RuntimeObject, debugLabelValue string, r mcreconcile.Reconciler, log *logger.Logger, eventPredicates ...predicate.Predicate) error {
98+
b, err := l.SetupWithManagerBuilder(mgr, maxReconciles, reconcilerName, instance, debugLabelValue, log, eventPredicates...)
99+
if err != nil {
100+
return err
101+
}
102+
103+
return b.Complete(r)
104+
}
105+
106+
// WithPrepareContextFunc allows to set a function that prepares the context before each reconciliation
107+
// This can be used to add additional information to the context that is needed by the subroutines
108+
// You need to return a new context and an OperatorError in case of an error
109+
func (l *LifecycleManager) WithPrepareContextFunc(prepareFunction lifecycle.PrepareContextFunc) *LifecycleManager {
110+
l.prepareContextFunc = prepareFunction
111+
return l
112+
}
113+
114+
// WithReadOnly allows to set the controller to read-only mode
115+
// In read-only mode, the controller will not update the status of the instance
116+
func (l *LifecycleManager) WithReadOnly() *LifecycleManager {
117+
l.config.ReadOnly = true
118+
return l
119+
}
120+
121+
// WithSpreadingReconciles sets the LifecycleManager to spread out the reconciles
122+
func (l *LifecycleManager) WithSpreadingReconciles() *LifecycleManager {
123+
l.spreader = spread.NewSpreader()
124+
return l
125+
}
126+
127+
func (l *LifecycleManager) WithConditionManagement() *LifecycleManager {
128+
l.conditionsManager = conditions.NewConditionManager()
129+
return l
130+
}

0 commit comments

Comments
 (0)