Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Reconciliation Standardization #19

Merged
merged 5 commits into from
Nov 12, 2024
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
Original file line number Diff line number Diff line change
@@ -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
Expand Down
133 changes: 133 additions & 0 deletions architecture-decision-records/008-greenhouse-controller-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# 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.
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

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, <statusFunc>)
}
```

> 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 invoke 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)
Loading