diff --git a/test/framework/resourceversion_helpers.go b/test/framework/resourceversion_helpers.go index 66c7fe8c577f..0c83bc157e15 100644 --- a/test/framework/resourceversion_helpers.go +++ b/test/framework/resourceversion_helpers.go @@ -19,11 +19,15 @@ package framework import ( "context" "fmt" + "strings" "time" + "github.com/google/go-cmp/cmp" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" clusterctlcluster "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" ) @@ -33,13 +37,15 @@ func ValidateResourceVersionStable(ctx context.Context, proxy ClusterProxy, name // Wait until resource versions are stable for a bit. byf("Check Resource versions are stable") var previousResourceVersions map[string]string + var previousObjects map[string]client.Object Eventually(func(g Gomega) { - objectsWithResourceVersion, err := getObjectsWithResourceVersion(ctx, proxy, namespace, ownerGraphFilterFunction) + objectsWithResourceVersion, objects, err := getObjectsWithResourceVersion(ctx, proxy, namespace, ownerGraphFilterFunction) g.Expect(err).ToNot(HaveOccurred()) defer func() { // Set current resource versions as previous resource versions for the next try. previousResourceVersions = objectsWithResourceVersion + previousObjects = objects }() // This is intentionally failing on the first run. g.Expect(objectsWithResourceVersion).To(BeComparableTo(previousResourceVersions)) @@ -48,19 +54,73 @@ func ValidateResourceVersionStable(ctx context.Context, proxy ClusterProxy, name // Verify resource versions are stable for a while. byf("Check Resource versions remain stable") Consistently(func(g Gomega) { - objectsWithResourceVersion, err := getObjectsWithResourceVersion(ctx, proxy, namespace, ownerGraphFilterFunction) + objectsWithResourceVersion, objects, err := getObjectsWithResourceVersion(ctx, proxy, namespace, ownerGraphFilterFunction) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(objectsWithResourceVersion).To(BeComparableTo(previousResourceVersions)) + g.Expect(objectsWithResourceVersion).To(BeComparableTo(previousResourceVersions), printObjectDiff(previousObjects, objects)) }, 2*time.Minute, 15*time.Second).Should(Succeed(), "Resource versions didn't stay stable") } -func getObjectsWithResourceVersion(ctx context.Context, proxy ClusterProxy, namespace string, ownerGraphFilterFunction clusterctlcluster.GetOwnerGraphFilterFunction) (map[string]string, error) { +func printObjectDiff(previousObjects, newObjects map[string]client.Object) func() string { + return func() string { + createdObjects := objectIDs(newObjects).Difference(objectIDs(previousObjects)) + deletedObjects := objectIDs(previousObjects).Difference(objectIDs(newObjects)) + preservedObjects := objectIDs(previousObjects).Intersection(objectIDs(newObjects)) + + var output strings.Builder + + if len(createdObjects) > 0 { + output.WriteString("\nDetected new objects\n") + for objID := range createdObjects { + obj := newObjects[objID] + resourceYAML, _ := yaml.Marshal(obj) + output.WriteString(fmt.Sprintf("\nNew object %s:\n%s\n", objID, resourceYAML)) + } + } + if len(deletedObjects) > 0 { + output.WriteString("\nDetected deleted objects\n") + for objID := range deletedObjects { + obj := previousObjects[objID] + resourceYAML, _ := yaml.Marshal(obj) + output.WriteString(fmt.Sprintf("\nDeleted object %s:\n%s\n", objID, resourceYAML)) + } + } + + if len(preservedObjects) > 0 { + output.WriteString("\nDetected objects with changed resourceVersion\n") + for objID := range preservedObjects { + previousObj := previousObjects[objID] + newObj := newObjects[objID] + + if previousObj.GetResourceVersion() == newObj.GetResourceVersion() { + continue + } + + previousResourceYAML, _ := yaml.Marshal(previousObj) + newResourceYAML, _ := yaml.Marshal(newObj) + output.WriteString(fmt.Sprintf("\nObject with changed resourceVersion %s:\n%s\n", objID, cmp.Diff(string(previousResourceYAML), string(newResourceYAML)))) + } + } + + return output.String() + } +} + +func objectIDs(objs map[string]client.Object) sets.Set[string] { + ret := sets.Set[string]{} + for id := range objs { + ret.Insert(id) + } + return ret +} + +func getObjectsWithResourceVersion(ctx context.Context, proxy ClusterProxy, namespace string, ownerGraphFilterFunction clusterctlcluster.GetOwnerGraphFilterFunction) (map[string]string, map[string]client.Object, error) { graph, err := clusterctlcluster.GetOwnerGraph(ctx, namespace, proxy.GetKubeconfigPath(), ownerGraphFilterFunction) if err != nil { - return nil, err + return nil, nil, err } objectsWithResourceVersion := map[string]string{} + objects := map[string]client.Object{} for _, node := range graph { nodeNamespacedName := client.ObjectKey{Namespace: node.Object.Namespace, Name: node.Object.Name} obj := &metav1.PartialObjectMetadata{ @@ -70,9 +130,10 @@ func getObjectsWithResourceVersion(ctx context.Context, proxy ClusterProxy, name }, } if err := proxy.GetClient().Get(ctx, nodeNamespacedName, obj); err != nil { - return nil, err + return nil, nil, err } objectsWithResourceVersion[fmt.Sprintf("%s/%s/%s", node.Object.Kind, node.Object.Namespace, node.Object.Name)] = obj.ResourceVersion + objects[fmt.Sprintf("%s/%s/%s", node.Object.Kind, node.Object.Namespace, node.Object.Name)] = obj } - return objectsWithResourceVersion, nil + return objectsWithResourceVersion, objects, nil }