diff --git a/controllers/hcloudmachine_controller_test.go b/controllers/hcloudmachine_controller_test.go index 1ebd8fcff..6061839d7 100644 --- a/controllers/hcloudmachine_controller_test.go +++ b/controllers/hcloudmachine_controller_test.go @@ -17,6 +17,7 @@ limitations under the License. package controllers import ( + "testing" "time" "github.com/hetznercloud/hcloud-go/v2/hcloud" @@ -37,6 +38,188 @@ import ( "github.com/syself/cluster-api-provider-hetzner/pkg/utils" ) +func TestIgnoreInsignificantHCloudMachineStatusUpdates(t *testing.T) { + logger := klog.Background() + predicate := IgnoreInsignificantHCloudMachineStatusUpdates(logger) + + testCases := []struct { + name string + oldObj *infrav1.HCloudMachine + newObj *infrav1.HCloudMachine + expected bool + }{ + { + name: "No significant changes", + oldObj: &infrav1.HCloudMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Status: infrav1.HCloudMachineStatus{ + Ready: true, + }, + }, + newObj: &infrav1.HCloudMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + ResourceVersion: "2", + }, + Status: infrav1.HCloudMachineStatus{ + Ready: true, + }, + }, + expected: false, + }, + { + name: "Significant changes in spec", + oldObj: &infrav1.HCloudMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Spec: infrav1.HCloudMachineSpec{ + Type: "cx11", + }, + }, + newObj: &infrav1.HCloudMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Spec: infrav1.HCloudMachineSpec{ + Type: "cx21", + }, + }, + expected: true, + }, + { + name: "Empty status in new object", + oldObj: &infrav1.HCloudMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Status: infrav1.HCloudMachineStatus{ + InstanceState: stPtr(hcloud.ServerStatusRunning), + }, + }, + newObj: &infrav1.HCloudMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Status: infrav1.HCloudMachineStatus{}, + }, + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + updateEvent := event.UpdateEvent{ + ObjectOld: tc.oldObj, + ObjectNew: tc.newObj, + } + result := predicate.Update(updateEvent) + if result != tc.expected { + t.Errorf("Expected %v, but got %v", tc.expected, result) + } + }) + } +} +func stPtr(h hcloud.ServerStatus) *hcloud.ServerStatus { + return &h +} +func TestIgnoreInsignificantMachineStatusUpdates(t *testing.T) { + logger := klog.Background() + predicate := IgnoreInsignificantMachineStatusUpdates(logger) + + testCases := []struct { + name string + oldObj *clusterv1.Machine + newObj *clusterv1.Machine + expected bool + }{ + { + name: "No significant changes", + oldObj: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Status: clusterv1.MachineStatus{}, + }, + newObj: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + ResourceVersion: "2", + }, + Status: clusterv1.MachineStatus{}, + }, + expected: false, + }, + { + name: "Significant changes in spec", + oldObj: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Spec: clusterv1.MachineSpec{ + ClusterName: "old-cluster", + }, + }, + newObj: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Spec: clusterv1.MachineSpec{ + ClusterName: "new-cluster", + }, + }, + expected: true, + }, + { + name: "Changes only in status", + oldObj: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Status: clusterv1.MachineStatus{ + Phase: "Pending", + }, + }, + newObj: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + Status: clusterv1.MachineStatus{ + Phase: "Running", + }, + }, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + updateEvent := event.UpdateEvent{ + ObjectOld: tc.oldObj, + ObjectNew: tc.newObj, + } + result := predicate.Update(updateEvent) + if result != tc.expected { + t.Errorf("Expected %v, but got %v", tc.expected, result) + } + }) + } +} + var _ = Describe("HCloudMachineReconciler", func() { var ( capiCluster *clusterv1.Cluster diff --git a/controllers/hetznercluster_controller_test.go b/controllers/hetznercluster_controller_test.go index ab0528851..1a1cce888 100644 --- a/controllers/hetznercluster_controller_test.go +++ b/controllers/hetznercluster_controller_test.go @@ -26,17 +26,233 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" "k8s.io/utils/ptr" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/conditions" "sigs.k8s.io/cluster-api/util/patch" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" infrav1 "github.com/syself/cluster-api-provider-hetzner/api/v1beta1" "github.com/syself/cluster-api-provider-hetzner/pkg/utils" "github.com/syself/cluster-api-provider-hetzner/test/helpers" ) +func TestIgnoreInsignificantClusterStatusUpdates(t *testing.T) { + logger := klog.Background() + predicate := IgnoreInsignificantClusterStatusUpdates(logger) + + testCases := []struct { + name string + oldObj *clusterv1.Cluster + newObj *clusterv1.Cluster + expected bool + }{ + { + name: "No significant changes", + oldObj: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "default", + }, + Status: clusterv1.ClusterStatus{ + Phase: "Provisioned", + }, + }, + newObj: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "default", + ResourceVersion: "2", + }, + Status: clusterv1.ClusterStatus{ + Phase: "Provisioned", + }, + }, + expected: false, + }, + { + name: "Significant changes in spec", + oldObj: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "default", + }, + Spec: clusterv1.ClusterSpec{ + ClusterNetwork: &clusterv1.ClusterNetwork{ + Pods: &clusterv1.NetworkRanges{ + CIDRBlocks: []string{"192.168.0.0/16"}, + }, + }, + }, + }, + newObj: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "default", + }, + Spec: clusterv1.ClusterSpec{ + ClusterNetwork: &clusterv1.ClusterNetwork{ + Pods: &clusterv1.NetworkRanges{ + CIDRBlocks: []string{"10.0.0.0/16"}, + }, + }, + }, + }, + expected: true, + }, + { + name: "Changes only in status", + oldObj: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "default", + }, + Status: clusterv1.ClusterStatus{ + Phase: "Provisioning", + }, + }, + newObj: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "default", + }, + Status: clusterv1.ClusterStatus{ + Phase: "Provisioned", + }, + }, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + updateEvent := event.UpdateEvent{ + ObjectOld: tc.oldObj, + ObjectNew: tc.newObj, + } + result := predicate.Update(updateEvent) + if result != tc.expected { + t.Errorf("Expected %v, but got %v", tc.expected, result) + } + }) + } +} + +func TestIgnoreInsignificantHetznerClusterStatusUpdates(t *testing.T) { + logger := klog.Background() + predicate := IgnoreInsignificantHetznerClusterStatusUpdates(logger) + + testCases := []struct { + name string + oldObj *infrav1.HetznerCluster + newObj *infrav1.HetznerCluster + expected bool + }{ + { + name: "No significant changes", + oldObj: &infrav1.HetznerCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hetzner-cluster", + Namespace: "default", + }, + Status: infrav1.HetznerClusterStatus{ + Ready: true, + }, + }, + newObj: &infrav1.HetznerCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hetzner-cluster", + Namespace: "default", + ResourceVersion: "2", + }, + Status: infrav1.HetznerClusterStatus{ + Ready: true, + }, + }, + expected: false, + }, + { + name: "Significant changes in spec", + oldObj: &infrav1.HetznerCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hetzner-cluster", + Namespace: "default", + }, + Spec: infrav1.HetznerClusterSpec{ + ControlPlaneRegions: []infrav1.Region{"fsn1"}, + }, + }, + newObj: &infrav1.HetznerCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hetzner-cluster", + Namespace: "default", + }, + Spec: infrav1.HetznerClusterSpec{ + ControlPlaneRegions: []infrav1.Region{"nbg1"}, + }, + }, + expected: true, + }, + { + name: "Empty status in new object", + oldObj: &infrav1.HetznerCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hetzner-cluster", + Namespace: "default", + }, + Status: infrav1.HetznerClusterStatus{ + Ready: true, + }, + }, + newObj: &infrav1.HetznerCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hetzner-cluster", + Namespace: "default", + }, + Status: infrav1.HetznerClusterStatus{}, + }, + expected: true, + }, + { + name: "Changes only in status", + oldObj: &infrav1.HetznerCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hetzner-cluster", + Namespace: "default", + }, + Status: infrav1.HetznerClusterStatus{ + Ready: false, + }, + }, + newObj: &infrav1.HetznerCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hetzner-cluster", + Namespace: "default", + }, + Status: infrav1.HetznerClusterStatus{ + Ready: true, + }, + }, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + updateEvent := event.UpdateEvent{ + ObjectOld: tc.oldObj, + ObjectNew: tc.newObj, + } + result := predicate.Update(updateEvent) + if result != tc.expected { + t.Errorf("Expected %v, but got %v", tc.expected, result) + } + }) + } +} + var _ = Describe("Hetzner ClusterReconciler", func() { Context("cluster tests", func() { var (