Skip to content

Commit ebdda34

Browse files
🌱 Improve E2E ValidateFinalizers and ValidateOwnerRef (kubernetes-sigs#10693)
* Improve E2E ValidateFinalizers and ValidateOwnerRef * Fix nil pointer exception * Check for missing finalizers * Fail if finalizer assertions are missing
1 parent 32323cf commit ebdda34

File tree

3 files changed

+100
-67
lines changed

3 files changed

+100
-67
lines changed

test/e2e/quick_start_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var _ = Describe("When following the Cluster API quick-start", func() {
4040
InfrastructureProvider: ptr.To("docker"),
4141
PostMachinesProvisioned: func(proxy framework.ClusterProxy, namespace, clusterName string) {
4242
// This check ensures that owner references are resilient - i.e. correctly re-reconciled - when removed.
43+
By("Checking that owner references are resilient")
4344
framework.ValidateOwnerReferencesResilience(ctx, proxy, namespace, clusterName, clusterctlcluster.FilterClusterObjectsWithNameFilter(clusterName),
4445
framework.CoreOwnerReferenceAssertion,
4546
framework.ExpOwnerReferenceAssertions,
@@ -49,6 +50,7 @@ var _ = Describe("When following the Cluster API quick-start", func() {
4950
framework.KubernetesReferenceAssertions,
5051
)
5152
// This check ensures that owner references are correctly updated to the correct apiVersion.
53+
By("Checking that owner references are updated to the correct API version")
5254
framework.ValidateOwnerReferencesOnUpdate(ctx, proxy, namespace, clusterName, clusterctlcluster.FilterClusterObjectsWithNameFilter(clusterName),
5355
framework.CoreOwnerReferenceAssertion,
5456
framework.ExpOwnerReferenceAssertions,
@@ -58,14 +60,16 @@ var _ = Describe("When following the Cluster API quick-start", func() {
5860
framework.KubernetesReferenceAssertions,
5961
)
6062
// This check ensures that finalizers are resilient - i.e. correctly re-reconciled - when removed.
63+
By("Checking that finalizers are resilient")
6164
framework.ValidateFinalizersResilience(ctx, proxy, namespace, clusterName, clusterctlcluster.FilterClusterObjectsWithNameFilter(clusterName),
62-
framework.CoreFinalizersAssertion,
65+
framework.CoreFinalizersAssertionWithLegacyClusters,
6366
framework.KubeadmControlPlaneFinalizersAssertion,
6467
framework.ExpFinalizersAssertion,
6568
framework.DockerInfraFinalizersAssertion,
6669
)
6770
// This check ensures that the resourceVersions are stable, i.e. it verifies there are no
6871
// continuous reconciles when everything should be stable.
72+
By("Checking that resourceVersions are stable")
6973
framework.ValidateResourceVersionStable(ctx, proxy, namespace, clusterctlcluster.FilterClusterObjectsWithNameFilter(clusterName))
7074
},
7175
}
@@ -82,8 +86,9 @@ var _ = Describe("When following the Cluster API quick-start with ClusterClass [
8286
SkipCleanup: skipCleanup,
8387
Flavor: ptr.To("topology"),
8488
InfrastructureProvider: ptr.To("docker"),
85-
// This check ensures that owner references are resilient - i.e. correctly re-reconciled - when removed.
8689
PostMachinesProvisioned: func(proxy framework.ClusterProxy, namespace, clusterName string) {
90+
// This check ensures that owner references are resilient - i.e. correctly re-reconciled - when removed.
91+
By("Checking that owner references are resilient")
8792
framework.ValidateOwnerReferencesResilience(ctx, proxy, namespace, clusterName, clusterctlcluster.FilterClusterObjectsWithNameFilter(clusterName),
8893
framework.CoreOwnerReferenceAssertion,
8994
framework.ExpOwnerReferenceAssertions,
@@ -93,6 +98,7 @@ var _ = Describe("When following the Cluster API quick-start with ClusterClass [
9398
framework.KubernetesReferenceAssertions,
9499
)
95100
// This check ensures that owner references are correctly updated to the correct apiVersion.
101+
By("Checking that owner references are updated to the correct API version")
96102
framework.ValidateOwnerReferencesOnUpdate(ctx, proxy, namespace, clusterName, clusterctlcluster.FilterClusterObjectsWithNameFilter(clusterName),
97103
framework.CoreOwnerReferenceAssertion,
98104
framework.ExpOwnerReferenceAssertions,
@@ -102,14 +108,16 @@ var _ = Describe("When following the Cluster API quick-start with ClusterClass [
102108
framework.KubernetesReferenceAssertions,
103109
)
104110
// This check ensures that finalizers are resilient - i.e. correctly re-reconciled - when removed.
111+
By("Checking that finalizers are resilient")
105112
framework.ValidateFinalizersResilience(ctx, proxy, namespace, clusterName, clusterctlcluster.FilterClusterObjectsWithNameFilter(clusterName),
106-
framework.CoreFinalizersAssertion,
113+
framework.CoreFinalizersAssertionWithClassyClusters,
107114
framework.KubeadmControlPlaneFinalizersAssertion,
108115
framework.ExpFinalizersAssertion,
109116
framework.DockerInfraFinalizersAssertion,
110117
)
111118
// This check ensures that the resourceVersions are stable, i.e. it verifies there are no
112119
// continuous reconciles when everything should be stable.
120+
By("Checking that resourceVersions are stable")
113121
framework.ValidateResourceVersionStable(ctx, proxy, namespace, clusterctlcluster.FilterClusterObjectsWithNameFilter(clusterName))
114122
},
115123
}

test/framework/finalizers_helpers.go

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import (
2020
"context"
2121
"fmt"
2222
"reflect"
23+
"strings"
2324
"time"
2425

2526
. "github.com/onsi/gomega"
2627
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28+
"k8s.io/apimachinery/pkg/types"
2729
kerrors "k8s.io/apimachinery/pkg/util/errors"
2830
"sigs.k8s.io/controller-runtime/pkg/client"
2931

@@ -37,34 +39,43 @@ import (
3739
"sigs.k8s.io/cluster-api/util/patch"
3840
)
3941

40-
// CoreFinalizersAssertion maps Cluster API core types to their expected finalizers.
41-
var CoreFinalizersAssertion = map[string][]string{
42-
"Cluster": {clusterv1.ClusterFinalizer},
43-
"Machine": {clusterv1.MachineFinalizer},
44-
"MachineSet": {clusterv1.MachineSetTopologyFinalizer},
45-
"MachineDeployment": {clusterv1.MachineDeploymentTopologyFinalizer},
42+
// CoreFinalizersAssertionWithLegacyClusters maps Cluster API core types to their expected finalizers for legacy Clusters.
43+
var CoreFinalizersAssertionWithLegacyClusters = map[string]func(types.NamespacedName) []string{
44+
clusterKind: func(_ types.NamespacedName) []string { return []string{clusterv1.ClusterFinalizer} },
45+
machineKind: func(_ types.NamespacedName) []string { return []string{clusterv1.MachineFinalizer} },
4646
}
4747

48+
// CoreFinalizersAssertionWithClassyClusters maps Cluster API core types to their expected finalizers for classy Clusters.
49+
var CoreFinalizersAssertionWithClassyClusters = func() map[string]func(types.NamespacedName) []string {
50+
r := map[string]func(types.NamespacedName) []string{}
51+
for k, v := range CoreFinalizersAssertionWithLegacyClusters {
52+
r[k] = v
53+
}
54+
r[machineSetKind] = func(_ types.NamespacedName) []string { return []string{clusterv1.MachineSetTopologyFinalizer} }
55+
r[machineDeploymentKind] = func(_ types.NamespacedName) []string { return []string{clusterv1.MachineDeploymentTopologyFinalizer} }
56+
return r
57+
}()
58+
4859
// ExpFinalizersAssertion maps experimental resource types to their expected finalizers.
49-
var ExpFinalizersAssertion = map[string][]string{
50-
"ClusterResourceSet": {addonsv1.ClusterResourceSetFinalizer},
51-
"MachinePool": {expv1.MachinePoolFinalizer},
60+
var ExpFinalizersAssertion = map[string]func(types.NamespacedName) []string{
61+
clusterResourceSetKind: func(_ types.NamespacedName) []string { return []string{addonsv1.ClusterResourceSetFinalizer} },
62+
machinePoolKind: func(_ types.NamespacedName) []string { return []string{expv1.MachinePoolFinalizer} },
5263
}
5364

5465
// DockerInfraFinalizersAssertion maps docker infrastructure resource types to their expected finalizers.
55-
var DockerInfraFinalizersAssertion = map[string][]string{
56-
"DockerMachine": {infrav1.MachineFinalizer},
57-
"DockerCluster": {infrav1.ClusterFinalizer},
58-
"DockerMachinePool": {infraexpv1.MachinePoolFinalizer},
66+
var DockerInfraFinalizersAssertion = map[string]func(types.NamespacedName) []string{
67+
dockerMachineKind: func(_ types.NamespacedName) []string { return []string{infrav1.MachineFinalizer} },
68+
dockerClusterKind: func(_ types.NamespacedName) []string { return []string{infrav1.ClusterFinalizer} },
69+
dockerMachinePoolKind: func(_ types.NamespacedName) []string { return []string{infraexpv1.MachinePoolFinalizer} },
5970
}
6071

6172
// KubeadmControlPlaneFinalizersAssertion maps Kubeadm resource types to their expected finalizers.
62-
var KubeadmControlPlaneFinalizersAssertion = map[string][]string{
63-
"KubeadmControlPlane": {controlplanev1.KubeadmControlPlaneFinalizer},
73+
var KubeadmControlPlaneFinalizersAssertion = map[string]func(types.NamespacedName) []string{
74+
kubeadmControlPlaneKind: func(_ types.NamespacedName) []string { return []string{controlplanev1.KubeadmControlPlaneFinalizer} },
6475
}
6576

6677
// ValidateFinalizersResilience checks that expected finalizers are in place, deletes them, and verifies that expected finalizers are properly added again.
67-
func ValidateFinalizersResilience(ctx context.Context, proxy ClusterProxy, namespace, clusterName string, ownerGraphFilterFunction clusterctlcluster.GetOwnerGraphFilterFunction, finalizerAssertions ...map[string][]string) {
78+
func ValidateFinalizersResilience(ctx context.Context, proxy ClusterProxy, namespace, clusterName string, ownerGraphFilterFunction clusterctlcluster.GetOwnerGraphFilterFunction, finalizerAssertions ...map[string]func(name types.NamespacedName) []string) {
6879
clusterKey := client.ObjectKey{Namespace: namespace, Name: clusterName}
6980
allFinalizerAssertions, err := concatenateFinalizerAssertions(finalizerAssertions...)
7081
Expect(err).ToNot(HaveOccurred())
@@ -107,7 +118,7 @@ func removeFinalizers(ctx context.Context, proxy ClusterProxy, namespace string,
107118
}
108119
}
109120

110-
func getObjectsWithFinalizers(ctx context.Context, proxy ClusterProxy, namespace string, allFinalizerAssertions map[string][]string, ownerGraphFilterFunction clusterctlcluster.GetOwnerGraphFilterFunction) map[string]*unstructured.Unstructured {
121+
func getObjectsWithFinalizers(ctx context.Context, proxy ClusterProxy, namespace string, allFinalizerAssertions map[string]func(name types.NamespacedName) []string, ownerGraphFilterFunction clusterctlcluster.GetOwnerGraphFilterFunction) map[string]*unstructured.Unstructured {
111122
graph, err := clusterctlcluster.GetOwnerGraph(ctx, namespace, proxy.GetKubeconfigPath(), ownerGraphFilterFunction)
112123
Expect(err).ToNot(HaveOccurred())
113124

@@ -121,11 +132,15 @@ func getObjectsWithFinalizers(ctx context.Context, proxy ClusterProxy, namespace
121132
err = proxy.GetClient().Get(ctx, nodeNamespacedName, obj)
122133
Expect(err).ToNot(HaveOccurred())
123134

135+
// assert if the expected finalizers are set on the resource (including also checking if there are unexpected finalizers)
124136
setFinalizers := obj.GetFinalizers()
137+
var expectedFinalizers []string
138+
if assertion, ok := allFinalizerAssertions[node.Object.Kind]; ok {
139+
expectedFinalizers = assertion(types.NamespacedName{Namespace: node.Object.Namespace, Name: node.Object.Name})
140+
}
125141

142+
Expect(setFinalizers).To(Equal(expectedFinalizers), "for resource type %s", node.Object.Kind)
126143
if len(setFinalizers) > 0 {
127-
// assert if the expected finalizers are set on the resource
128-
Expect(setFinalizers).To(Equal(allFinalizerAssertions[node.Object.Kind]), "for resource type %s", node.Object.Kind)
129144
objsWithFinalizers[fmt.Sprintf("%s/%s/%s", node.Object.Kind, node.Object.Namespace, node.Object.Name)] = obj
130145
}
131146
}
@@ -134,24 +149,27 @@ func getObjectsWithFinalizers(ctx context.Context, proxy ClusterProxy, namespace
134149
}
135150

136151
// assertFinalizersExist ensures that current Finalizers match those in the initialObjectsWithFinalizers.
137-
func assertFinalizersExist(ctx context.Context, proxy ClusterProxy, namespace string, initialObjsWithFinalizers map[string]*unstructured.Unstructured, allFinalizerAssertions map[string][]string, ownerGraphFilterFunction clusterctlcluster.GetOwnerGraphFilterFunction) {
152+
func assertFinalizersExist(ctx context.Context, proxy ClusterProxy, namespace string, initialObjsWithFinalizers map[string]*unstructured.Unstructured, allFinalizerAssertions map[string]func(name types.NamespacedName) []string, ownerGraphFilterFunction clusterctlcluster.GetOwnerGraphFilterFunction) {
138153
Eventually(func() error {
139154
var allErrs []error
140155
finalObjsWithFinalizers := getObjectsWithFinalizers(ctx, proxy, namespace, allFinalizerAssertions, ownerGraphFilterFunction)
141156

157+
// Check if all the initial objects with finalizers have them back.
142158
for objKindNamespacedName, obj := range initialObjsWithFinalizers {
143159
// verify if finalizers for this resource were set on reconcile
144160
if _, valid := finalObjsWithFinalizers[objKindNamespacedName]; !valid {
145-
allErrs = append(allErrs, fmt.Errorf("no finalizers set for %s",
146-
objKindNamespacedName))
161+
allErrs = append(allErrs, fmt.Errorf("no finalizers set for %s, at the beginning of the test it has %s",
162+
objKindNamespacedName, obj.GetFinalizers()))
147163
continue
148164
}
149165

150166
// verify if this resource has the appropriate Finalizers set
151-
expectedFinalizers, assert := allFinalizerAssertions[obj.GetKind()]
152-
if !assert {
153-
continue
154-
}
167+
expectedFinalizersF, assert := allFinalizerAssertions[obj.GetKind()]
168+
// NOTE: this case should never happen because all the initialObjsWithFinalizers have been already checked
169+
// against a finalizer assertion.
170+
Expect(assert).To(BeTrue(), "finalizer assertions for %s are missing", objKindNamespacedName)
171+
parts := strings.Split(objKindNamespacedName, "/")
172+
expectedFinalizers := expectedFinalizersF(types.NamespacedName{Namespace: parts[1], Name: parts[2]})
155173

156174
setFinalizers := finalObjsWithFinalizers[objKindNamespacedName].GetFinalizers()
157175
if !reflect.DeepEqual(expectedFinalizers, setFinalizers) {
@@ -160,21 +178,28 @@ func assertFinalizersExist(ctx context.Context, proxy ClusterProxy, namespace st
160178
}
161179
}
162180

181+
// Check if there are objects with finalizers not existing initially
182+
for objKindNamespacedName, obj := range finalObjsWithFinalizers {
183+
// verify if finalizers for this resource were set on reconcile
184+
if _, valid := initialObjsWithFinalizers[objKindNamespacedName]; !valid {
185+
allErrs = append(allErrs, fmt.Errorf("%s has finalizers not existing at the beginning of the test: %s",
186+
objKindNamespacedName, obj.GetFinalizers()))
187+
}
188+
}
189+
163190
return kerrors.NewAggregate(allErrs)
164191
}).WithTimeout(1 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())
165192
}
166193

167194
// concatenateFinalizerAssertions concatenates all finalizer assertions into one map. It reports errors if assertions already exist.
168-
func concatenateFinalizerAssertions(finalizerAssertions ...map[string][]string) (map[string][]string, error) {
195+
func concatenateFinalizerAssertions(finalizerAssertions ...map[string]func(name types.NamespacedName) []string) (map[string]func(name types.NamespacedName) []string, error) {
169196
var allErrs []error
170-
allFinalizerAssertions := make(map[string][]string, 0)
197+
allFinalizerAssertions := make(map[string]func(name types.NamespacedName) []string, 0)
171198

172199
for i := range finalizerAssertions {
173200
for kind, finalizers := range finalizerAssertions[i] {
174201
if _, alreadyExists := allFinalizerAssertions[kind]; alreadyExists {
175-
allErrs = append(allErrs, fmt.Errorf("finalizer assertion cannot be applied as it already exists for kind: %s, existing value: %v, new value: %v",
176-
kind, allFinalizerAssertions[kind], finalizers))
177-
202+
allErrs = append(allErrs, fmt.Errorf("finalizer assertion cannot be applied as it already exists for kind: %s", kind))
178203
continue
179204
}
180205

0 commit comments

Comments
 (0)