From 71b9033ea684d42e201df1ef9af38c57051f2aa0 Mon Sep 17 00:00:00 2001 From: David Ortiz Date: Tue, 26 Jul 2022 14:06:32 +0200 Subject: [PATCH 1/3] [testutils] Add helper to create scheme --- controllers/datadogagent/controller_test.go | 18 +------ .../datadogagent/feature/test/testsuite.go | 42 ++--------------- .../datadogagent/testutils/client_utils.go | 47 +++++++++++++++++++ 3 files changed, 52 insertions(+), 55 deletions(-) create mode 100644 controllers/datadogagent/testutils/client_utils.go diff --git a/controllers/datadogagent/controller_test.go b/controllers/datadogagent/controller_test.go index c3a5d0f28..cd34dc173 100644 --- a/controllers/datadogagent/controller_test.go +++ b/controllers/datadogagent/controller_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + testutils "github.com/DataDog/datadog-operator/controllers/datadogagent/testutils" "github.com/pkg/errors" assert "github.com/stretchr/testify/require" @@ -141,22 +142,7 @@ func TestReconcileDatadogAgent_Reconcile(t *testing.T) { logf.SetLogger(zap.New(zap.UseDevMode(true))) // Register operator types with the runtime scheme. - s := scheme.Scheme - s.AddKnownTypes(datadoghqv1alpha1.GroupVersion, &datadoghqv1alpha1.DatadogAgent{}) - s.AddKnownTypes(edsdatadoghqv1alpha1.GroupVersion, &edsdatadoghqv1alpha1.ExtendedDaemonSet{}) - s.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.DaemonSet{}) - s.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.Deployment{}) - s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Secret{}) - s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.ServiceAccount{}) - s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.ConfigMap{}) - s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.ClusterRoleBinding{}) - s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.ClusterRole{}) - s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.Role{}) - s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.RoleBinding{}) - s.AddKnownTypes(policyv1.SchemeGroupVersion, &policyv1.PodDisruptionBudget{}) - s.AddKnownTypes(apiregistrationv1.SchemeGroupVersion, &apiregistrationv1.APIServiceList{}) - s.AddKnownTypes(apiregistrationv1.SchemeGroupVersion, &apiregistrationv1.APIService{}) - s.AddKnownTypes(networkingv1.SchemeGroupVersion, &networkingv1.NetworkPolicy{}) + s := testutils.TestScheme(false) defaultRequeueDuration := 15 * time.Second affinity := &corev1.Affinity{ diff --git a/controllers/datadogagent/feature/test/testsuite.go b/controllers/datadogagent/feature/test/testsuite.go index 7f6272a44..48e51a438 100644 --- a/controllers/datadogagent/feature/test/testsuite.go +++ b/controllers/datadogagent/feature/test/testsuite.go @@ -4,24 +4,15 @@ import ( "testing" "github.com/DataDog/datadog-operator/controllers/datadogagent/feature/fake" + testutils "github.com/DataDog/datadog-operator/controllers/datadogagent/testutils" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - policyv1 "k8s.io/api/policy/v1beta1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - edsdatadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" - apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" - "github.com/DataDog/datadog-operator/apis/datadoghq/v1alpha1" "github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1" "github.com/DataDog/datadog-operator/controllers/datadogagent/dependencies" "github.com/DataDog/datadog-operator/controllers/datadogagent/feature" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // FeatureTestSuite use define several tests on a Feature @@ -90,33 +81,6 @@ func (suite FeatureTestSuite) Run(t *testing.T, buildFunc feature.BuildFunc) { } } -// testScheme return a runtime.Scheme for testing purpose -func testScheme(isV2 bool) *runtime.Scheme { - s := runtime.NewScheme() - s.AddKnownTypes(edsdatadoghqv1alpha1.GroupVersion, &edsdatadoghqv1alpha1.ExtendedDaemonSet{}) - s.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.DaemonSet{}) - s.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.Deployment{}) - s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Secret{}) - s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.ServiceAccount{}) - s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.ConfigMap{}) - s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.ClusterRoleBinding{}) - s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.ClusterRole{}) - s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.Role{}) - s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.RoleBinding{}) - s.AddKnownTypes(policyv1.SchemeGroupVersion, &policyv1.PodDisruptionBudget{}) - s.AddKnownTypes(apiregistrationv1.SchemeGroupVersion, &apiregistrationv1.APIServiceList{}) - s.AddKnownTypes(apiregistrationv1.SchemeGroupVersion, &apiregistrationv1.APIService{}) - s.AddKnownTypes(networkingv1.SchemeGroupVersion, &networkingv1.NetworkPolicy{}) - - if isV2 { - s.AddKnownTypes(v2alpha1.GroupVersion, &v2alpha1.DatadogAgent{}) - } else { - s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.DatadogAgent{}) - } - - return s -} - func runTest(t *testing.T, tt FeatureTest, buildFunc feature.BuildFunc) { logf.SetLogger(zap.New(zap.UseDevMode(true))) logger := logf.Log.WithName(tt.Name) @@ -155,7 +119,7 @@ func runTest(t *testing.T, tt FeatureTest, buildFunc feature.BuildFunc) { } } if tt.StoreOption.Scheme == nil { - tt.StoreOption.Scheme = testScheme(isV2) + tt.StoreOption.Scheme = testutils.TestScheme(isV2) } // dependencies diff --git a/controllers/datadogagent/testutils/client_utils.go b/controllers/datadogagent/testutils/client_utils.go new file mode 100644 index 000000000..9f118b620 --- /dev/null +++ b/controllers/datadogagent/testutils/client_utils.go @@ -0,0 +1,47 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package testutils_test + +import ( + "github.com/DataDog/datadog-operator/apis/datadoghq/v1alpha1" + "github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1" + edsdatadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + policyv1 "k8s.io/api/policy/v1beta1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" +) + +// TestScheme return a runtime.Scheme for testing purposes +func TestScheme(isV2 bool) *runtime.Scheme { + s := scheme.Scheme + s.AddKnownTypes(edsdatadoghqv1alpha1.GroupVersion, &edsdatadoghqv1alpha1.ExtendedDaemonSet{}) + s.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.DaemonSet{}) + s.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.Deployment{}) + s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Secret{}) + s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.ServiceAccount{}) + s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.ConfigMap{}) + s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.ClusterRoleBinding{}) + s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.ClusterRole{}) + s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.Role{}) + s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.RoleBinding{}) + s.AddKnownTypes(policyv1.SchemeGroupVersion, &policyv1.PodDisruptionBudget{}) + s.AddKnownTypes(apiregistrationv1.SchemeGroupVersion, &apiregistrationv1.APIServiceList{}) + s.AddKnownTypes(apiregistrationv1.SchemeGroupVersion, &apiregistrationv1.APIService{}) + s.AddKnownTypes(networkingv1.SchemeGroupVersion, &networkingv1.NetworkPolicy{}) + + if isV2 { + s.AddKnownTypes(v2alpha1.GroupVersion, &v2alpha1.DatadogAgent{}) + } else { + s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.DatadogAgent{}) + } + + return s +} From d3c8099dc2a0bcab697d9647ae82d45bec1fcb84 Mon Sep 17 00:00:00 2001 From: David Ortiz Date: Mon, 25 Jul 2022 18:45:54 +0200 Subject: [PATCH 2/3] [dependencies/store] Add DeleteAll func --- .../datadogagent/dependencies/store.go | 43 ++++++ .../datadogagent/dependencies/store_test.go | 131 ++++++++++++++++++ 2 files changed, 174 insertions(+) diff --git a/controllers/datadogagent/dependencies/store.go b/controllers/datadogagent/dependencies/store.go index 2cd9735bf..299430ff4 100644 --- a/controllers/datadogagent/dependencies/store.go +++ b/controllers/datadogagent/dependencies/store.go @@ -41,6 +41,7 @@ type StoreClient interface { GetOrCreate(kind kubernetes.ObjectKind, namespace, name string) (client.Object, bool) GetVersionInfo() string Delete(kind kubernetes.ObjectKind, namespace string, name string) bool + DeleteAll(ctx context.Context, k8sClient client.Client) []error } // NewStore returns a new Store instance @@ -278,6 +279,48 @@ func (ds *Store) GetVersionInfo() string { return ds.versionInfo.GitVersion } +// DeleteAll deletes all the resources that are in the Store +func (ds *Store) DeleteAll(ctx context.Context, k8sClient client.Client) []error { + ds.mutex.RLock() + defer ds.mutex.RUnlock() + + var objsToDelete []client.Object + + for _, kind := range kubernetes.GetResourcesKind(ds.supportCilium) { + requirementLabel, _ := labels.NewRequirement(operatorStoreLabelKey, selection.Exists, nil) + listOptions := &client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*requirementLabel), + } + objList := kubernetes.ObjectListFromKind(kind) + if err := k8sClient.List(ctx, objList, listOptions); err != nil { + return []error{err} + } + + items, err := apimeta.ExtractList(objList) + if err != nil { + return []error{err} + } + + for _, objAPIServer := range items { + objMeta, _ := apimeta.Accessor(objAPIServer) + + idObj := buildID(objMeta.GetNamespace(), objMeta.GetName()) + if _, found := ds.deps[kind][idObj]; found { + partialObj := &metav1.PartialObjectMetadata{ + ObjectMeta: metav1.ObjectMeta{ + Name: objMeta.GetName(), + Namespace: objMeta.GetNamespace(), + }, + } + partialObj.TypeMeta.SetGroupVersionKind(objAPIServer.GetObjectKind().GroupVersionKind()) + objsToDelete = append(objsToDelete, partialObj) + } + } + } + + return deleteObjects(ctx, k8sClient, objsToDelete) +} + func listObjectToDelete(objList client.ObjectList, cacheObjects map[string]client.Object) ([]client.Object, error) { items, err := apimeta.ExtractList(objList) if err != nil { diff --git a/controllers/datadogagent/dependencies/store_test.go b/controllers/datadogagent/dependencies/store_test.go index 913b9f545..c7937b4dc 100644 --- a/controllers/datadogagent/dependencies/store_test.go +++ b/controllers/datadogagent/dependencies/store_test.go @@ -11,9 +11,11 @@ import ( "testing" "github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1" + testutils "github.com/DataDog/datadog-operator/controllers/datadogagent/testutils" "github.com/DataDog/datadog-operator/pkg/kubernetes" assert "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -585,3 +587,132 @@ func TestStore_GetOrCreate(t *testing.T) { }) } } + +func TestStore_DeleteAll(t *testing.T) { + testConfigMap1 := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns1", + Name: "some_name", + Labels: map[string]string{ + operatorStoreLabelKey: "true", + }, + }, + } + + testConfigMap2 := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns2", + Name: "another_name", + Labels: map[string]string{ + operatorStoreLabelKey: "true", + }, + }, + } + + testStore := map[kubernetes.ObjectKind]map[string]client.Object{ + kubernetes.ConfigMapKind: { + "ns1/some_name": testConfigMap1, + "ns2/another_name": testConfigMap2, + }, + } + + // ConfigMap not included in testStore + testConfigMap3 := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns3", + Name: "some_name", + Labels: map[string]string{ + operatorStoreLabelKey: "true", + }, + }, + } + + tests := []struct { + name string + dependenciesStore map[kubernetes.ObjectKind]map[string]client.Object + existingObjects []client.Object + objectsExpectedToBeDeleted []client.Object + objectsExpectedNotToBeDeleted []client.Object + }{ + { + name: "deletes all the objects in the store", + dependenciesStore: testStore, + existingObjects: []client.Object{ + testConfigMap1, + testConfigMap2, + }, + objectsExpectedToBeDeleted: []client.Object{ + testConfigMap1, + testConfigMap2, + }, + }, + { + name: "does not delete objects that are not in the store", + dependenciesStore: testStore, + existingObjects: []client.Object{ + testConfigMap1, + testConfigMap2, + testConfigMap3, // Not in dependenciesStore + }, + objectsExpectedToBeDeleted: []client.Object{ + testConfigMap1, + testConfigMap2, + }, + objectsExpectedNotToBeDeleted: []client.Object{ + testConfigMap3, // Not in dependenciesStore + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + k8sClient := fake.NewClientBuilder(). + WithScheme(testutils.TestScheme(true)). + WithObjects(test.existingObjects...). + Build() + + store := &Store{ + deps: test.dependenciesStore, + } + + errs := store.DeleteAll(context.TODO(), k8sClient) + assert.Empty(t, errs) + + for _, expectedToBeDeleted := range test.objectsExpectedToBeDeleted { + err := k8sClient.Get( + context.TODO(), + client.ObjectKey{ + Namespace: expectedToBeDeleted.GetNamespace(), + Name: expectedToBeDeleted.GetName(), + }, + &corev1.ConfigMap{}, // Adapt according to test input objects + ) + assert.True(t, errors.IsNotFound(err)) + } + + for _, expectedToExist := range test.objectsExpectedNotToBeDeleted { + err := k8sClient.Get( + context.TODO(), + client.ObjectKey{ + Namespace: expectedToExist.GetNamespace(), + Name: expectedToExist.GetName(), + }, + &corev1.ConfigMap{}, // Adapt according to test input objects + ) + assert.NoError(t, err) + } + }) + } +} From 4be3dd2dd35806a09d915cc097cbad4c2d54d316 Mon Sep 17 00:00:00 2001 From: David Ortiz Date: Mon, 25 Jul 2022 18:46:45 +0200 Subject: [PATCH 3/3] [datadogagent/finalizer] Delete dependencies in V2 --- .../datadogagent/controller_reconcile_v2.go | 12 -- controllers/datadogagent/finalizer.go | 56 ++++++++ controllers/datadogagent/finalizer_test.go | 135 +++++++++++++++++- 3 files changed, 188 insertions(+), 15 deletions(-) diff --git a/controllers/datadogagent/controller_reconcile_v2.go b/controllers/datadogagent/controller_reconcile_v2.go index 1bedb56e1..1e1316c43 100644 --- a/controllers/datadogagent/controller_reconcile_v2.go +++ b/controllers/datadogagent/controller_reconcile_v2.go @@ -10,12 +10,10 @@ import ( "time" "github.com/go-logr/logr" - apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/errors" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" datadoghqv2alpha1 "github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1" @@ -191,13 +189,3 @@ func (r *Reconciler) updateStatusIfNeededV2(logger logr.Logger, agentdeployment return result, currentError } - -func (r *Reconciler) finalizeDadV2(reqLogger logr.Logger, obj client.Object) { - dda := obj.(*datadoghqv2alpha1.DatadogAgent) - - if r.options.OperatorMetricsEnabled { - r.forwarders.Unregister(dda) - } - - reqLogger.Info("Successfully finalized DatadogAgent") -} diff --git a/controllers/datadogagent/finalizer.go b/controllers/datadogagent/finalizer.go index 40a6abc5f..3b145e56c 100644 --- a/controllers/datadogagent/finalizer.go +++ b/controllers/datadogagent/finalizer.go @@ -9,6 +9,10 @@ import ( "context" datadoghqv1alpha1 "github.com/DataDog/datadog-operator/apis/datadoghq/v1alpha1" + datadoghqv2alpha1 "github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1" + "github.com/DataDog/datadog-operator/controllers/datadogagent/dependencies" + "github.com/DataDog/datadog-operator/controllers/datadogagent/feature" + "github.com/DataDog/datadog-operator/controllers/datadogagent/override" "github.com/DataDog/datadog-operator/pkg/controller/utils" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -76,6 +80,58 @@ func (r *Reconciler) finalizeDadV1(reqLogger logr.Logger, obj client.Object) { reqLogger.Info("Successfully finalized DatadogAgent") } +func (r *Reconciler) finalizeDadV2(reqLogger logr.Logger, obj client.Object) { + // We need to apply the defaults to be able to delete the resources + // associated with those defaults. + dda := obj.(*datadoghqv2alpha1.DatadogAgent).DeepCopy() + datadoghqv2alpha1.DefaultDatadogAgent(dda) + + if r.options.OperatorMetricsEnabled { + r.forwarders.Unregister(dda) + } + + // To delete the resources associated with the DatadogAgent that we need to + // delete, we figure out its dependencies, store them in the dependencies + // store, and then call the DeleteAll function of the store. + + features, requiredComponents := feature.BuildFeatures( + dda, reconcilerOptionsToFeatureOptions(&r.options, reqLogger)) + + storeOptions := &dependencies.StoreOptions{ + SupportCilium: r.options.SupportCilium, + Logger: reqLogger, + Scheme: r.scheme, + } + depsStore := dependencies.NewStore(dda, storeOptions) + resourceManagers := feature.NewResourceManagers(depsStore) + + var errs []error + + // Set up dependencies required by enabled features + for _, feat := range features { + if featErr := feat.ManageDependencies(resourceManagers, requiredComponents); featErr != nil { + errs = append(errs, featErr) + } + } + + // Examine user configuration to override any external dependencies (e.g. RBACs) + errs = append(errs, override.Dependencies(resourceManagers, dda.Spec.Override, dda.Namespace)...) + + if len(errs) > 0 { + reqLogger.Info("Errors calculating dependencies while finalizing the DatadogAgent", "errors", errs) + } + + deleteErrs := depsStore.DeleteAll(context.TODO(), r.client) + + if len(deleteErrs) == 0 { + reqLogger.Info("Successfully finalized DatadogAgent") + } else { + for _, deleteErr := range deleteErrs { + reqLogger.Error(deleteErr, "Error deleting dependencies while finalizing the DatadogAgent") + } + } +} + func (r *Reconciler) addFinalizer(reqLogger logr.Logger, dda client.Object) error { reqLogger.Info("Adding Finalizer for the DatadogAgent") dda.SetFinalizers(append(dda.GetFinalizers(), datadogAgentFinalizer)) diff --git a/controllers/datadogagent/finalizer_test.go b/controllers/datadogagent/finalizer_test.go index f71a3c609..8268be3ee 100644 --- a/controllers/datadogagent/finalizer_test.go +++ b/controllers/datadogagent/finalizer_test.go @@ -7,6 +7,10 @@ import ( datadoghqv1alpha1 "github.com/DataDog/datadog-operator/apis/datadoghq/v1alpha1" "github.com/DataDog/datadog-operator/apis/datadoghq/v1alpha1/test" + datadoghqv2alpha1 "github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1" + "github.com/DataDog/datadog-operator/controllers/datadogagent/component/agent" + "github.com/DataDog/datadog-operator/controllers/datadogagent/component/clusteragent" + testutils "github.com/DataDog/datadog-operator/controllers/datadogagent/testutils" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -20,7 +24,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" ) -func Test_handleFinalizer(t *testing.T) { +func Test_handleFinalizer_V1(t *testing.T) { // TODO: This tests that the associated cluster roles and cluster role // bindings are deleted when the dda is marked to be deleted. However, the // finalizer does more than that. @@ -39,7 +43,7 @@ func Test_handleFinalizer(t *testing.T) { for _, clusterRoleBinding := range clusterRoleBindings { initialKubeObjects = append(initialKubeObjects, clusterRoleBinding) } - reconciler := reconcilerForFinalizerTest(initialKubeObjects) + reconciler := reconcilerV1ForFinalizerTest(initialKubeObjects) for _, resourceName := range rbacNamesForDda(dda, reconciler.versionInfo) { clusterRoles = append(clusterRoles, buildClusterRole(dda, true, resourceName, "")) @@ -70,7 +74,116 @@ func Test_handleFinalizer(t *testing.T) { } } -func reconcilerForFinalizerTest(initialKubeObjects []client.Object) Reconciler { +func Test_handleFinalizer_V2(t *testing.T) { + // This is not an exhaustive test. The finalizer should remove all the + // kubernetes resources associated with the Datadog Agent being removed, but + // to simplify a bit, this test doesn't check all the resources, it just + // checks a few ones (cluster roles, cluster role bindings). + + now := metav1.Now() + + dda := &datadoghqv2alpha1.DatadogAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "bar", + Finalizers: []string{"finalizer.agent.datadoghq.com"}, + }, + } + dda.DeletionTimestamp = &now // Mark for deletion + + initialKubeObjects := []client.Object{dda} + + // These are some cluster roles that we know that the reconciler creates by + // default + existingClusterRoles := []*rbacv1.ClusterRole{ + { + TypeMeta: metav1.TypeMeta{ + Kind: clusterRoleKind, + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: agent.GetAgentRoleName(dda), + Labels: map[string]string{ + "operator.datadoghq.com/managed-by-store": "true", + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{ + Kind: clusterRoleKind, + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: clusteragent.GetClusterAgentName(dda), + Labels: map[string]string{ + "operator.datadoghq.com/managed-by-store": "true", + }, + }, + }, + } + + // These are some cluster role bindings that we know that the reconciler + // creates by default + existingClusterRoleBindings := []*rbacv1.ClusterRoleBinding{ + { + TypeMeta: metav1.TypeMeta{ + Kind: clusterRoleBindingKind, + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: agent.GetAgentRoleName(dda), // Same name as the cluster role + Labels: map[string]string{ + "operator.datadoghq.com/managed-by-store": "true", + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{ + Kind: clusterRoleBindingKind, + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: clusteragent.GetClusterAgentName(dda), + Labels: map[string]string{ + "operator.datadoghq.com/managed-by-store": "true", + }, + }, + }, + } + + for _, clusterRole := range existingClusterRoles { + initialKubeObjects = append(initialKubeObjects, clusterRole) + } + + for _, clusterRoleBinding := range existingClusterRoleBindings { + initialKubeObjects = append(initialKubeObjects, clusterRoleBinding) + } + + reconciler := reconcilerV2ForFinalizerTest(initialKubeObjects) + + _, err := reconciler.handleFinalizer(logf.Log.WithName("Handle Finalizer V2 test"), dda, reconciler.finalizeDadV2) + assert.NoError(t, err) + + // Check that the cluster roles associated with the Datadog Agent have been deleted + for _, clusterRole := range existingClusterRoles { + err = reconciler.client.Get(context.TODO(), types.NamespacedName{Name: clusterRole.Name}, &rbacv1.ClusterRole{}) + assert.Error(t, err, fmt.Sprintf("ClusterRole %s not deleted", clusterRole.Name)) + if err != nil { + assert.True(t, apierrors.IsNotFound(err), fmt.Sprintf("Unexpected error %s", err)) + } + } + + // Check that the cluster role bindings associated with the Datadog Agent have been deleted + for _, clusterRoleBinding := range existingClusterRoleBindings { + err = reconciler.client.Get(context.TODO(), types.NamespacedName{Name: clusterRoleBinding.Name}, &rbacv1.ClusterRoleBinding{}) + assert.Error(t, err, fmt.Sprintf("ClusterRoleBinding %s not deleted", clusterRoleBinding.Name)) + if err != nil { + assert.True(t, apierrors.IsNotFound(err), fmt.Sprintf("Unexpected error %s", err)) + } + } +} + +func reconcilerV1ForFinalizerTest(initialKubeObjects []client.Object) Reconciler { reconcilerScheme := scheme.Scheme reconcilerScheme.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.ClusterRoleBinding{}, &rbacv1.ClusterRole{}) reconcilerScheme.AddKnownTypes(datadoghqv1alpha1.GroupVersion, &datadoghqv1alpha1.DatadogAgent{}) @@ -82,5 +195,21 @@ func reconcilerForFinalizerTest(initialKubeObjects []client.Object) Reconciler { scheme: reconcilerScheme, recorder: record.NewBroadcaster().NewRecorder(reconcilerScheme, corev1.EventSource{}), forwarders: dummyManager{}, + options: ReconcilerOptions{V2Enabled: false}, + } +} + +func reconcilerV2ForFinalizerTest(initialKubeObjects []client.Object) Reconciler { + s := testutils.TestScheme(true) + + fakeClient := fake.NewClientBuilder().WithObjects(initialKubeObjects...).WithScheme(s).Build() + + return Reconciler{ + client: fakeClient, + scheme: s, + recorder: record.NewBroadcaster().NewRecorder(s, corev1.EventSource{}), + forwarders: dummyManager{}, + options: ReconcilerOptions{V2Enabled: true}, + log: logf.Log.WithName("reconciler_v2"), } }