From a03efa2ccae16a2bf42ee93347ea42aac5eff6ab Mon Sep 17 00:00:00 2001 From: Abhijith Ravindra Date: Fri, 18 Oct 2024 14:07:45 +0200 Subject: [PATCH 1/4] (chore): mark ADR 004 partially superseded --- .../004-greenhouse-resource-status-reporting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/architecture-decision-records/004-greenhouse-resource-status-reporting.md b/architecture-decision-records/004-greenhouse-resource-status-reporting.md index 4bfb6c5..251e7c2 100644 --- a/architecture-decision-records/004-greenhouse-resource-status-reporting.md +++ b/architecture-decision-records/004-greenhouse-resource-status-reporting.md @@ -1,6 +1,6 @@ # 004 Greenhouse Resource States -- Status: proposed +- Status: proposed | partially superseded by [008 Greenhouse Controller Lifecycle](008-greenhouse-controller-lifecycle.md) - Deciders: Ivo Gosemann, Uwe Mayer - Date: 2023-12-18 - Tags: greenhouse From 7b3b07fe5f8cfa99f75bb6c188a76e06c5e3e12f Mon Sep 17 00:00:00 2001 From: Abhijith Ravindra Date: Fri, 18 Oct 2024 14:08:12 +0200 Subject: [PATCH 2/4] (chore): controller lifecycle ADR --- .../008-greenhouse-controller-lifecycle.md | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 architecture-decision-records/008-greenhouse-controller-lifecycle.md diff --git a/architecture-decision-records/008-greenhouse-controller-lifecycle.md b/architecture-decision-records/008-greenhouse-controller-lifecycle.md new file mode 100644 index 0000000..0d9098a --- /dev/null +++ b/architecture-decision-records/008-greenhouse-controller-lifecycle.md @@ -0,0 +1,132 @@ +# 008 Greenhouse Controller Lifecycle + +- Status: proposed +- Deciders: Ivo Gosemann, Uwe Mayer, David Gogl, Abhijith Ravindra +- Date: 2024-10-18 +- Tags: greenhouse +- Technical Story: [greenhouse#414](https://github.com/cloudoperators/greenhouse/issues/414) + +## Context and Problem Statement + +Greenhouse contains multiple controllers for custom resources such as `Organization`, `Cluster`, `Plugin`, etc. + +These controllers need a unified approach for their reconciliation lifecycle. + +Therefore, this ADR addresses the following concerns: + +- A standard reconcile interface that should be adopted by existing controllers and all new controllers. +- A common place to set the status of the resource as proposed by the ADR [004 Greenhouse Resource States](004-greenhouse-resource-status-reporting.md). + + +## Decision Drivers + +- Uniformity: + * All controllers implement the same interface for reconciliation. + * Reducing code duplication + +- Expandability: + * Existing controllers should be easily able to adopt the interface. + +- Ease of use: + * New controllers should be able to implement the interface with minimal effort. + +- Simplicity: + * Controllers adopting the interface do not need to implement the reconciliation logic themselves. + * `Create / Update` and `Delete` logic is separated. + +## Decision Outcome + +As described in the issue [greenhouse#414](https://github.com/cloudoperators/greenhouse/issues/414) we will introduce a `Reconciler` interface that controllers can implement. + +## Reconciler and RuntimeObject Interface + +The RuntimeObject interface allows the `Reconciler` to work with any CR object in a generic way, allowing the reconciler to access `DeepCopyObject` and the `ObjectMeta` of the CR object - `GetNamespace()`, `GetName()`, etc. + +https://github.com/cloudoperators/greenhouse/blob/bb57e128014102f963c9879ef78af80bc1820bd4/pkg/lifecycle/reconcile.go#L48-L56 + +The `Reconciler` interface will have the following methods `EnsureCreated` and `EnsureDeleted` that the calling controller should implement. + +https://github.com/cloudoperators/greenhouse/blob/bb57e128014102f963c9879ef78af80bc1820bd4/pkg/lifecycle/reconcile.go#L58-L62 + +`Reconcile` - is a generic function that is used to reconcile the state of a resource +It standardizes the reconciliation loop and provides a common way to set finalizers, remove finalizers, and update the status of the resource + +It splits the reconciliation into two phases, `EnsureCreated` and `EnsureDeleted` to keep the `create / update` and `delete` logic in controllers segregated + +https://github.com/cloudoperators/greenhouse/blob/bb57e128014102f963c9879ef78af80bc1820bd4/pkg/lifecycle/reconcile.go#L64-L135 + +Some controllers need to calculate their `ReadyCondition` based on certain criteria, in such cases a function of the type `Conditioner` can be passed to the `Reconcile` function. + +```go +type Conditioner func(context.Context, RuntimeObject) +``` + +At the end of reconciliation, the status of the resource is patched by merging the original object stored in the context with the modified object. + +https://github.com/cloudoperators/greenhouse/blob/bb57e128014102f963c9879ef78af80bc1820bd4/pkg/lifecycle/reconcile.go#L174-L184 + +## Interface Adoption + +Controllers that need to adopt the `Reconciler` interface should first implement the `RuntimeObject` interface for their respective CR object. + +ex: `Cluster` CR object + +```go +func (c *Cluster) GetConditions() StatusConditions { + return c.Status.StatusConditions +} + +func (c *Cluster) SetCondition(condition Condition) { + c.Status.StatusConditions.SetConditions(condition) +} +``` + +Then the controller should implement the `Reconciler` interface from `lifecycle` package + +```go +func (r *RemoteClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return lifecycle.Reconcile(ctx, r.Client, req.NamespacedName, &greenhousev1alpha1.Cluster{}, r, ) +} +``` + +> Note: If no statusFunc is passed then `lifecycle` package will set default status based on the result of `EnsureCreated`. + +The `EnsureCreated` and `EnsureDeleted` methods should be implemented in the controller so that the `Reconcile` function can call them. + +```go +func (r *RemoteClusterReconciler) EnsureCreated(ctx context.Context, resource lifecycle.RuntimeObject) (ctrl.Result, lifecycle.ReconcileResult, error) { + cluster := resource.(*greenhousev1alpha1.Cluster) + .... + return ctrl.Result{}, lifecycle.Success, nil +``` + +```go +func (r *RemoteClusterReconciler) EnsureDeleted(ctx context.Context, resource lifecycle.RuntimeObject) (ctrl.Result, lifecycle.ReconcileResult, error) { + cluster := resource.(*greenhousev1alpha1.Cluster) + .... + return ctrl.Result{}, lifecycle.Success, nil +} +``` + +`lifecycle.ReconcileResult` is a type that can be used to indicate the result of the reconciliation. + +It can be one of the following: + +```go +type ReconcileResult string + +const( + // Success should be returned in case the operator reached its target state + Success ReconcileResult = "Success" + + // Failed should be returned in case the operator wasn't able to reach its target state and without external changes it's unlikely that this will succeed in the next try + Failed ReconcileResult = "Failed" + + // Pending should be returned in case the operator is still trying to reach the target state (Requeue, waiting for remote resource to be cleaned up, etc.) + Pending ReconcileResult = "Pending" +) +``` + +## Related Decision Records + +Partially supersedes [004 Greenhouse Resource States](004-greenhouse-resource-status-reporting.md) \ No newline at end of file From a3cf3d40d8294dd01988429ef022e57976aa567a Mon Sep 17 00:00:00 2001 From: Abhijith Ravindra <137736216+abhijith-darshan@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:11:19 +0200 Subject: [PATCH 3/4] Update 008-greenhouse-controller-lifecycle.md --- .../008-greenhouse-controller-lifecycle.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/architecture-decision-records/008-greenhouse-controller-lifecycle.md b/architecture-decision-records/008-greenhouse-controller-lifecycle.md index 0d9098a..4af5c22 100644 --- a/architecture-decision-records/008-greenhouse-controller-lifecycle.md +++ b/architecture-decision-records/008-greenhouse-controller-lifecycle.md @@ -91,7 +91,7 @@ func (r *RemoteClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques > Note: If no statusFunc is passed then `lifecycle` package will set default status based on the result of `EnsureCreated`. -The `EnsureCreated` and `EnsureDeleted` methods should be implemented in the controller so that the `Reconcile` function can call them. +The `EnsureCreated` and `EnsureDeleted` methods should be implemented in the controller so that the `Reconcile` function can invoke them. ```go func (r *RemoteClusterReconciler) EnsureCreated(ctx context.Context, resource lifecycle.RuntimeObject) (ctrl.Result, lifecycle.ReconcileResult, error) { @@ -129,4 +129,4 @@ const( ## Related Decision Records -Partially supersedes [004 Greenhouse Resource States](004-greenhouse-resource-status-reporting.md) \ No newline at end of file +Partially supersedes [004 Greenhouse Resource States](004-greenhouse-resource-status-reporting.md) From 605de3eda42c58d08fd1ec733e63387367bbda2e Mon Sep 17 00:00:00 2001 From: Abhijith Ravindra Date: Tue, 12 Nov 2024 13:52:05 +0100 Subject: [PATCH 4/4] (chore): update adr --- .../008-greenhouse-controller-lifecycle.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/architecture-decision-records/008-greenhouse-controller-lifecycle.md b/architecture-decision-records/008-greenhouse-controller-lifecycle.md index 4af5c22..6bcc3e1 100644 --- a/architecture-decision-records/008-greenhouse-controller-lifecycle.md +++ b/architecture-decision-records/008-greenhouse-controller-lifecycle.md @@ -49,7 +49,8 @@ The `Reconciler` interface will have the following methods `EnsureCreated` and ` https://github.com/cloudoperators/greenhouse/blob/bb57e128014102f963c9879ef78af80bc1820bd4/pkg/lifecycle/reconcile.go#L58-L62 `Reconcile` - is a generic function that is used to reconcile the state of a resource -It standardizes the reconciliation loop and provides a common way to set finalizers, remove finalizers, and update the status of the resource +It standardizes the reconciliation loop and provides a common way to set finalizers, remove finalizers, and update the status of the resource. +At the start of the reconciliation loop, the original object is stored in `context` to update the status of the resource at the end of the reconciliation. It splits the reconciliation into two phases, `EnsureCreated` and `EnsureDeleted` to keep the `create / update` and `delete` logic in controllers segregated