/* Copyright 2022 The Karmada Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package status import ( "testing" "time" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1" ) func TestThresholdAdjustedReadyCondition(t *testing.T) { clusterSuccessThreshold := 30 * time.Second clusterFailureThreshold := 30 * time.Second tests := []struct { name string clusterData *clusterData currentCondition *metav1.Condition observedCondition *metav1.Condition expectedCondition *metav1.Condition }{ { name: "cluster just joined in ready state", clusterData: nil, // no cache yet currentCondition: nil, // no condition was set on cluster object yet observedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, expectedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, }, { name: "cluster just joined in not-ready state", clusterData: nil, // no cache yet currentCondition: nil, // no condition was set on cluster object yet observedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, expectedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, }, { name: "cluster stays ready", clusterData: &clusterData{ readyCondition: metav1.ConditionTrue, }, currentCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, observedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, expectedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, }, { name: "cluster becomes not ready but still not reach failure threshold", clusterData: &clusterData{ readyCondition: metav1.ConditionFalse, thresholdStartTime: time.Now().Add(-clusterFailureThreshold / 2), }, currentCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, observedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, expectedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, }, { name: "cluster becomes not ready and reaches failure threshold", clusterData: &clusterData{ readyCondition: metav1.ConditionFalse, thresholdStartTime: time.Now().Add(-clusterFailureThreshold), }, currentCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, observedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, expectedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, }, { name: "cluster stays not ready", clusterData: &clusterData{ readyCondition: metav1.ConditionFalse, thresholdStartTime: time.Now().Add(-2 * clusterFailureThreshold), }, currentCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, observedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, expectedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, }, { name: "cluster recovers but still not reach success threshold", clusterData: &clusterData{ readyCondition: metav1.ConditionTrue, thresholdStartTime: time.Now().Add(-clusterSuccessThreshold / 2), }, currentCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, observedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, expectedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, }, { name: "cluster recovers and reaches success threshold", clusterData: &clusterData{ readyCondition: metav1.ConditionTrue, thresholdStartTime: time.Now().Add(-clusterSuccessThreshold), }, currentCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionFalse, }, observedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, expectedCondition: &metav1.Condition{ Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cache := clusterConditionStore{ successThreshold: clusterSuccessThreshold, failureThreshold: clusterFailureThreshold, } if tt.clusterData != nil { cache.update("member", tt.clusterData) } cluster := &clusterv1alpha1.Cluster{} cluster.Name = "member" if tt.currentCondition != nil { meta.SetStatusCondition(&cluster.Status.Conditions, *tt.currentCondition) } thresholdReadyCondition := cache.thresholdAdjustedReadyCondition(cluster, tt.observedCondition) if tt.expectedCondition.Status != thresholdReadyCondition.Status { t.Fatalf("expected: %s, but got: %s", tt.expectedCondition.Status, thresholdReadyCondition.Status) } }) } } func TestClusterConditionStore_Get(t *testing.T) { // Create a new clusterConditionStore store := clusterConditionStore{} // Create a new clusterData object cluster := "test-cluster" now := time.Now() data := &clusterData{ readyCondition: metav1.ConditionTrue, thresholdStartTime: now, } // Add the clusterData object to the store store.clusterDataMap.Store(cluster, data) // Call the get function and check the returned value result := store.get(cluster) if result == nil { t.Errorf("Expected non-nil result, got nil") } if result != data { t.Errorf("Expected %v, got %v", data, result) } // Call the get function with a non-existent cluster and check the returned value result = store.get("non-existent-cluster") if result != nil { t.Errorf("Expected nil result, got %v", result) } } func TestClusterConditionStore_Update(t *testing.T) { // Create a new clusterConditionStore store := &clusterConditionStore{} // Create a new clusterData object cluster := "test-cluster" now := time.Now() data := &clusterData{ readyCondition: metav1.ConditionTrue, thresholdStartTime: now, } t.Run("Test update with new data", func(t *testing.T) { // Update the cluster with new data store.update(cluster, data) // Retrieve the updated data and verify it result := store.get(cluster) if result == nil { t.Errorf("Expected non-nil result, got nil") } if result != data { t.Errorf("Expected %v, got %v", data, result) } }) t.Run("Test update with same data", func(t *testing.T) { // Update the cluster with the same data store.update(cluster, data) // Retrieve the updated data and verify it result := store.get(cluster) if result == nil { t.Errorf("Expected non-nil result, got nil") } if result != data { t.Errorf("Expected %v, got %v", data, result) } }) t.Run("Test update with nil data", func(t *testing.T) { // Update the cluster with nil data store.update(cluster, nil) // Retrieve the updated data and verify it result := store.get(cluster) if result != nil { t.Errorf("Expected nil result, got %v", result) } }) } func TestClusterConditionStore_Delete(t *testing.T) { // Create a new clusterConditionStore store := clusterConditionStore{} // Create a new clusterData object cluster := "test-cluster" now := time.Now() data := &clusterData{ readyCondition: metav1.ConditionTrue, thresholdStartTime: now, } // Add the clusterData object to the store store.clusterDataMap.Store(cluster, data) // Delete the clusterData object from the store store.delete(cluster) // Call the get function with the deleted cluster and check the returned value result := store.get(cluster) if result != nil { t.Errorf("Expected nil result, got %v", result) } }