Skip to content

Commit b94c4ee

Browse files
authored
[improvement] Implement owner refs for linode resources (#746)
* Update CONTROLLER_GEN path to use CACHE_BIN in Makefile * Implement owner reference setting for Linode resources * Add comprehensive tests for setting owner references in LinodeCluster This commit introduces a new test function, TestSetOwnerReferenceToLinodeCluster, which validates the functionality of setting owner references for LinodeCluster resources. The tests cover various scenarios, including successful cases and error handling. Additionally, the import statements in helpers.go have been reorganized to fix lint error * Add Resource Ownership documentation This commit introduces a new document on Resource Ownership in Kubernetes, detailing the use of `ownerReferences` within the Cluster API Provider Linode (CAPL). It explains the implications of ownership for resource lifecycle management, including automatic garbage collection of dependent resources when a `LinodeCluster` is deleted. Additionally, the SUMMARY.md file is updated to include a link to this new documentation. * Refactor TestSetOwnerReferenceToLinodeCluster to initialize mock controller and client within the test function * Enhance error handling in Linode controllers to manage cluster retrieval failures This commit updates the Linode controllers to improve error handling when fetching clusters from metadata. If a cluster is not found, the controllers now log an informational message and continue with the deletion process instead of returning an error. This change ensures smoother resource management during deletion scenarios. Additionally, comments have been added to clarify the handling of cases where the cluster may not be found.
1 parent f0a3202 commit b94c4ee

11 files changed

+415
-20
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ CTLPTL ?= $(LOCALBIN)/ctlptl
347347
CLUSTERCTL ?= $(LOCALBIN)/clusterctl
348348
CRD_REF_DOCS ?= $(CACHE_BIN)/crd-ref-docs
349349
KUBEBUILDER ?= $(LOCALBIN)/kubebuilder
350-
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
350+
CONTROLLER_GEN ?= $(CACHE_BIN)/controller-gen
351351
CONVERSION_GEN ?= $(CACHE_BIN)/conversion-gen
352352
TILT ?= $(LOCALBIN)/tilt
353353
KIND ?= $(LOCALBIN)/kind

cmd/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,6 @@ func setupControllers(mgr manager.Manager, flags flagVars, linodeClientConfig, d
328328
// LinodeObjectStorageKey Controller
329329
if err := (&controller.LinodeObjectStorageKeyReconciler{
330330
Client: mgr.GetClient(),
331-
Scheme: mgr.GetScheme(),
332331
Logger: ctrl.Log.WithName("LinodeObjectStorageKeyReconciler"),
333332
Recorder: mgr.GetEventRecorderFor("LinodeObjectStorageKeyReconciler"),
334333
WatchFilterValue: flags.objectStorageKeyWatchFilter,

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
- [VPC](./topics/vpc.md)
3232
- [Firewalling](./topics/firewalling.md)
3333
- [Placement Groups](./topics/placement-groups.md)
34+
- [Resource Ownership](./topics/resource-ownership.md)
3435
- [Cluster Object Store](./topics/cluster-object-store.md)
3536
- [Linode Cloud Controller Manager](./topics/linode-cloud-controller-manager.md)
3637
- [Development](./developers/development.md)

docs/src/topics/resource-ownership.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Resource Ownership and Lifecycle
2+
3+
In Kubernetes, `ownerReferences` are a mechanism to specify the relationship between objects, where one object (the owner) owns another object (the dependent). This is crucial for managing the lifecycle of related resources.
4+
5+
## Owner References in Cluster API Provider Linode (CAPL)
6+
7+
Cluster API Provider Linode (CAPL) utilizes `ownerReferences` to link various Linode-specific resources to their parent `LinodeCluster`. This means that the `LinodeCluster` acts as the owner for resources such as:
8+
9+
* `LinodeFirewall`
10+
* `LinodeObjectStorageBucket`
11+
* `LinodeObjectStorageKey`
12+
* `LinodePlacementGroup`
13+
* `LinodeVPC`
14+
15+
When a `LinodeCluster` is created, and these associated resources are also created as part of the cluster definition or by controllers, CAPL automatically sets an `ownerReference` on these dependent resources, pointing back to the `LinodeCluster`.
16+
17+
## Implications of Ownership
18+
19+
The primary implication of this ownership model is **garbage collection**. When the `LinodeCluster` object is deleted, the Kubernetes garbage collector will automatically delete all the resources that are owned by it. This simplifies cluster teardown and helps prevent orphaned resources in your Linode account.
20+
21+
For example, if you delete a `LinodeCluster`:
22+
* Any `LinodeVPC` created for that cluster will be deleted.
23+
* Any `LinodeFirewall` associated with that cluster will be deleted.
24+
* Any `LinodeObjectStorageBucket` used by that cluster (and owned by it) will be deleted.
25+
* And so on for other owned resources.
26+
27+
This ensures that the lifecycle of these infrastructure components is tightly coupled with the lifecycle of the Kubernetes cluster itself, as managed by Cluster API.
28+
29+
## Verifying Ownership
30+
31+
You can inspect the `ownerReferences` of a resource using `kubectl describe` or `kubectl get <resource> <name> -o yaml`. Look for the `metadata.ownerReferences` field.
32+
33+
```yaml
34+
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2
35+
kind: LinodeVPC
36+
metadata:
37+
name: my-cluster-vpc
38+
namespace: default
39+
ownerReferences:
40+
- apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2
41+
blockOwnerDeletion: true
42+
controller: true
43+
kind: LinodeCluster
44+
name: my-cluster
45+
uid: <uid-of-linodecluster>
46+
# ... other fields
47+
```
48+
49+
In the example above, the `LinodeVPC` named `my-cluster-vpc` is owned by the `LinodeCluster` named `my-cluster`.
50+
51+
Understanding these ownership relationships is key to effectively managing your cluster resources with CAPL.

internal/controller/linodefirewall_controller.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,21 @@ func (r *LinodeFirewallReconciler) Reconcile(ctx context.Context, req ctrl.Reque
8484
if _, ok := linodeFirewall.Labels[clusterv1.ClusterNameLabel]; ok {
8585
cluster, err = kutil.GetClusterFromMetadata(ctx, r.TracedClient(), linodeFirewall.ObjectMeta)
8686
if err != nil {
87-
// If we're deleting and cluster isn't found, that's okay
88-
if !linodeFirewall.DeletionTimestamp.IsZero() && apierrors.IsNotFound(err) {
89-
log.Info("Cluster not found but LinodeFirewall is being deleted, continuing with deletion")
90-
} else {
87+
if client.IgnoreNotFound(err) != nil {
9188
log.Error(err, "failed to fetch cluster from metadata")
9289
return ctrl.Result{}, err
9390
}
91+
log.Info("Cluster not found but LinodeFirewall is being deleted, continuing with deletion")
92+
}
93+
94+
// Set ownerRef to LinodeCluster
95+
// It will handle the case where the cluster is not found
96+
if err := util.SetOwnerReferenceToLinodeCluster(ctx, r.TracedClient(), cluster, linodeFirewall, r.Scheme()); err != nil {
97+
log.Error(err, "Failed to set owner reference to LinodeCluster")
98+
return ctrl.Result{}, err
9499
}
95100
}
101+
96102
// Create the firewall scope.
97103
fwScope, err := scope.NewFirewallScope(
98104
ctx,

internal/controller/linodeobjectstoragebucket_controller.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,23 @@ func (r *LinodeObjectStorageBucketReconciler) Reconcile(ctx context.Context, req
8888
return ctrl.Result{}, err
8989
}
9090

91+
if _, ok := objectStorageBucket.Labels[clusterv1.ClusterNameLabel]; ok {
92+
cluster, err := kutil.GetClusterFromMetadata(ctx, r.TracedClient(), objectStorageBucket.ObjectMeta)
93+
if err != nil {
94+
if client.IgnoreNotFound(err) != nil {
95+
logger.Error(err, "failed to fetch cluster from metadata")
96+
return ctrl.Result{}, err
97+
}
98+
logger.Info("Cluster not found but LinodeObjectStorageBucket is being deleted, continuing with deletion")
99+
}
100+
101+
// It will handle the case where the cluster is not found
102+
if err := util.SetOwnerReferenceToLinodeCluster(ctx, r.TracedClient(), cluster, objectStorageBucket, r.Scheme()); err != nil {
103+
logger.Error(err, "Failed to set owner reference to LinodeCluster")
104+
return ctrl.Result{}, err
105+
}
106+
}
107+
91108
bScope, err := scope.NewObjectStorageBucketScope(
92109
ctx,
93110
r.LinodeClientConfig,

internal/controller/linodeobjectstoragekey_controller.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
corev1 "k8s.io/api/core/v1"
2828
apierrors "k8s.io/apimachinery/pkg/api/errors"
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30-
"k8s.io/apimachinery/pkg/runtime"
3130
utilerrors "k8s.io/apimachinery/pkg/util/errors"
3231
"k8s.io/client-go/tools/record"
3332
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -58,7 +57,6 @@ type LinodeObjectStorageKeyReconciler struct {
5857
Recorder record.EventRecorder
5958
LinodeClientConfig scope.ClientConfig
6059
WatchFilterValue string
61-
Scheme *runtime.Scheme
6260
ReconcileTimeout time.Duration
6361
}
6462

@@ -95,6 +93,23 @@ func (r *LinodeObjectStorageKeyReconciler) Reconcile(ctx context.Context, req ct
9593
return ctrl.Result{}, err
9694
}
9795

96+
if _, ok := objectStorageKey.Labels[clusterv1.ClusterNameLabel]; ok {
97+
cluster, err := kutil.GetClusterFromMetadata(ctx, r.TracedClient(), objectStorageKey.ObjectMeta)
98+
if err != nil {
99+
if client.IgnoreNotFound(err) != nil {
100+
logger.Error(err, "failed to fetch cluster from metadata")
101+
return ctrl.Result{}, err
102+
}
103+
logger.Info("Cluster not found but LinodeObjectStorageKey is being deleted, continuing with deletion")
104+
}
105+
106+
// It will handle the case where the cluster is not found
107+
if err := util.SetOwnerReferenceToLinodeCluster(ctx, r.TracedClient(), cluster, objectStorageKey, r.Scheme()); err != nil {
108+
logger.Error(err, "Failed to set owner reference to LinodeCluster")
109+
return ctrl.Result{}, err
110+
}
111+
}
112+
98113
keyScope, err := scope.NewObjectStorageKeyScope(
99114
ctx,
100115
r.LinodeClientConfig,

internal/controller/linodeplacementgroup_controller.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,18 @@ func (r *LinodePlacementGroupReconciler) Reconcile(ctx context.Context, req ctrl
8989
if _, ok := linodeplacementgroup.Labels[clusterv1.ClusterNameLabel]; ok {
9090
cluster, err = kutil.GetClusterFromMetadata(ctx, r.TracedClient(), linodeplacementgroup.ObjectMeta)
9191
if err != nil {
92-
// If we're deleting and cluster isn't found, that's okay
93-
if !linodeplacementgroup.DeletionTimestamp.IsZero() && apierrors.IsNotFound(err) {
94-
log.Info("Cluster not found but LinodePlacementGroup is being deleted, continuing with deletion")
95-
} else {
92+
if client.IgnoreNotFound(err) != nil {
9693
log.Error(err, "failed to fetch cluster from metadata")
9794
return ctrl.Result{}, err
9895
}
96+
log.Info("Cluster not found but LinodePlacementGroup is being deleted, continuing with deletion")
97+
}
98+
99+
// Set ownerRef to LinodeCluster
100+
// It will handle the case where the cluster is not found
101+
if err := util.SetOwnerReferenceToLinodeCluster(ctx, r.TracedClient(), cluster, linodeplacementgroup, r.Scheme()); err != nil {
102+
log.Error(err, "Failed to set owner reference to LinodeCluster")
103+
return ctrl.Result{}, err
99104
}
100105
}
101106

internal/controller/linodevpc_controller.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
corev1 "k8s.io/api/core/v1"
2828
apierrors "k8s.io/apimachinery/pkg/api/errors"
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30-
"k8s.io/apimachinery/pkg/runtime"
3130
utilerrors "k8s.io/apimachinery/pkg/util/errors"
3231
"k8s.io/client-go/tools/record"
3332
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -58,7 +57,6 @@ type LinodeVPCReconciler struct {
5857
Recorder record.EventRecorder
5958
LinodeClientConfig scope.ClientConfig
6059
WatchFilterValue string
61-
Scheme *runtime.Scheme
6260
ReconcileTimeout time.Duration
6361
}
6462

@@ -89,23 +87,28 @@ func (r *LinodeVPCReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
8987
if err = client.IgnoreNotFound(err); err != nil {
9088
log.Error(err, "Failed to fetch LinodeVPC")
9189
}
92-
9390
return ctrl.Result{}, err
9491
}
92+
9593
var cluster *clusterv1.Cluster
9694
var err error
9795
if _, ok := linodeVPC.Labels[clusterv1.ClusterNameLabel]; ok {
9896
cluster, err = kutil.GetClusterFromMetadata(ctx, r.TracedClient(), linodeVPC.ObjectMeta)
9997
if err != nil {
100-
// If we're deleting and cluster isn't found, that's okay
101-
if !linodeVPC.DeletionTimestamp.IsZero() && apierrors.IsNotFound(err) {
102-
log.Info("Cluster not found but LinodeVPC is being deleted, continuing with deletion")
103-
} else {
98+
if client.IgnoreNotFound(err) != nil {
10499
log.Error(err, "failed to fetch cluster from metadata")
105100
return ctrl.Result{}, err
106101
}
102+
log.Info("Cluster not found but LinodeVPC is being deleted, continuing with deletion")
103+
}
104+
105+
// It will handle the case where the cluster is not found
106+
if err := util.SetOwnerReferenceToLinodeCluster(ctx, r.TracedClient(), cluster, linodeVPC, r.Scheme()); err != nil {
107+
log.Error(err, "Failed to set owner reference to LinodeCluster")
108+
return ctrl.Result{}, err
107109
}
108110
}
111+
109112
vpcScope, err := scope.NewVPCScope(
110113
ctx,
111114
r.LinodeClientConfig,
@@ -117,7 +120,6 @@ func (r *LinodeVPCReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
117120
)
118121
if err != nil {
119122
log.Error(err, "Failed to create VPC scope")
120-
121123
return ctrl.Result{}, fmt.Errorf("failed to create VPC scope: %w", err)
122124
}
123125

util/helpers.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package util
22

33
import (
4+
"context"
45
"errors"
56
"io"
67
"net"
@@ -10,6 +11,14 @@ import (
1011
"strings"
1112

1213
"github.com/linode/linodego"
14+
"k8s.io/apimachinery/pkg/runtime"
15+
"k8s.io/apimachinery/pkg/types"
16+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
19+
"sigs.k8s.io/controller-runtime/pkg/log"
20+
21+
infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2"
1322
)
1423

1524
// Pointer returns the pointer of any type
@@ -83,3 +92,39 @@ func IsLinodePrivateIP(ipAddress string) bool {
8392
// Check if the IP is contained in the Linode private network
8493
return linodePrivateNet.Contains(ip)
8594
}
95+
96+
// SetOwnerReferenceToLinodeCluster fetches the LinodeCluster and sets it as the owner reference of a given object.
97+
func SetOwnerReferenceToLinodeCluster(ctx context.Context, k8sclient client.Client, cluster *clusterv1.Cluster, obj client.Object, scheme *runtime.Scheme) error {
98+
logger := log.Log.WithName("SetOwnerReferenceToLinodeCluster")
99+
100+
if cluster == nil || cluster.Spec.InfrastructureRef == nil {
101+
logger.Info("the Cluster or InfrastructureRef is nil, cannot fetch LinodeCluster")
102+
return nil
103+
}
104+
105+
var linodeCluster infrav1alpha2.LinodeCluster
106+
key := types.NamespacedName{
107+
Namespace: cluster.Spec.InfrastructureRef.Namespace,
108+
Name: cluster.Spec.InfrastructureRef.Name,
109+
}
110+
if err := k8sclient.Get(ctx, key, &linodeCluster); err != nil {
111+
if client.IgnoreNotFound(err) != nil {
112+
logger.Error(err, "Failed to fetch LinodeCluster")
113+
return err
114+
}
115+
logger.Info("LinodeCluster not found, skipping owner reference setting")
116+
return nil
117+
}
118+
119+
if err := controllerutil.SetControllerReference(&linodeCluster, obj, scheme); err != nil {
120+
logger.Error(err, "Failed to set owner reference to LinodeCluster")
121+
return err
122+
}
123+
124+
if err := k8sclient.Update(ctx, obj); err != nil {
125+
logger.Error(err, "Failed to update object")
126+
return err
127+
}
128+
129+
return nil
130+
}

0 commit comments

Comments
 (0)