package status import ( "bytes" "context" "fmt" "io" "net/http" "net/http/httptest" "reflect" "testing" "time" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" kubernetesfake "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/cache" controllerruntime "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1" "github.com/karmada-io/karmada/pkg/sharedcli/ratelimiterflag" "github.com/karmada-io/karmada/pkg/util" "github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager" "github.com/karmada-io/karmada/pkg/util/fedinformer/typedmanager" "github.com/karmada-io/karmada/pkg/util/gclient" "github.com/karmada-io/karmada/pkg/util/helper" ) func TestClusterStatusController_Reconcile(t *testing.T) { tests := []struct { name string cluster *clusterv1alpha1.Cluster clusterName string expectedResult controllerruntime.Result expectedError bool }{ { name: "Cluster not found", clusterName: "test-cluster", expectedResult: controllerruntime.Result{}, expectedError: false, }, { name: "Cluster found with finalizer", clusterName: "test-cluster", cluster: &clusterv1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{ Finalizers: []string{ util.ClusterControllerFinalizer, }, }, }, expectedResult: controllerruntime.Result{}, expectedError: false, }, { name: "Cluster found without finalizer", clusterName: "test-cluster", cluster: &clusterv1alpha1.Cluster{}, expectedResult: controllerruntime.Result{Requeue: true}, expectedError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Set up the test environment. c := &ClusterStatusController{ Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).Build(), GenericInformerManager: genericmanager.GetInstance(), TypedInformerManager: typedmanager.GetInstance(), ClusterClientOption: &util.ClientOption{ QPS: 5, Burst: 10, }, ClusterClientSetFunc: util.NewClusterClientSet, } if tt.cluster != nil { // Add a cluster to the fake client. tt.cluster.ObjectMeta.Name = tt.clusterName c.Client = fake.NewClientBuilder().WithScheme(gclient.NewSchema()).WithStatusSubresource(tt.cluster).Build() if err := c.Client.Create(context.Background(), tt.cluster); err != nil { t.Fatalf("Failed to create cluster: %v", err) } } // Run the reconcile function. req := controllerruntime.Request{ NamespacedName: types.NamespacedName{ Name: tt.clusterName, }, } result, err := c.Reconcile(context.Background(), req) // Check the results. if tt.expectedError && err == nil { t.Errorf("Expected an error but got nil") } else if !tt.expectedError && err != nil { t.Errorf("Expected no error but got %v", err) } if !reflect.DeepEqual(tt.expectedResult, result) { t.Errorf("Expected result %v but got %v", tt.expectedResult, result) } }) } } func generateClusterClient(APIEndpoint string) *util.ClusterClient { clusterClient := &util.ClusterClient{ ClusterName: "test", } hostClient := fake.NewClientBuilder().WithScheme(gclient.NewSchema()).WithObjects( &clusterv1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: clusterv1alpha1.ClusterSpec{ APIEndpoint: APIEndpoint, SecretRef: &clusterv1alpha1.LocalSecretReference{Namespace: "ns1", Name: "secret1"}, InsecureSkipTLSVerification: true, }, }, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "ns1", Name: "secret1"}, Data: map[string][]byte{ clusterv1alpha1.SecretTokenKey: []byte("token"), }, }).Build() clusterClientSet, _ := util.NewClusterClientSet("test", hostClient, nil) clusterClient.KubeClient = clusterClientSet.KubeClient return clusterClient } func clusterClientSetFunc(string, client.Client, *util.ClientOption) (*util.ClusterClient, error) { clusterClient := generateClusterClient(serverAddress) // cannot mock "/readyz"'s response, so create an error return clusterClient, nil } func clusterClientSetFuncWithError(string, client.Client, *util.ClientOption) (*util.ClusterClient, error) { clusterClient := generateClusterClient(serverAddress) return clusterClient, fmt.Errorf("err") } var serverAddress string func TestClusterStatusController_syncClusterStatus(t *testing.T) { t.Run("ClusterClientSetFunc returns error", func(t *testing.T) { server := mockServer(http.StatusOK, false) defer server.Close() serverAddress = server.URL cluster := &clusterv1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: clusterv1alpha1.ClusterSpec{ APIEndpoint: server.URL, SecretRef: &clusterv1alpha1.LocalSecretReference{Namespace: "ns1", Name: "secret1"}, InsecureSkipTLSVerification: true, ProxyURL: "http://1.1.1.1", }, } c := &ClusterStatusController{ Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).WithStatusSubresource(cluster).Build(), GenericInformerManager: genericmanager.GetInstance(), TypedInformerManager: typedmanager.GetInstance(), ClusterSuccessThreshold: metav1.Duration{ Duration: time.Duration(1000), }, ClusterFailureThreshold: metav1.Duration{ Duration: time.Duration(1000), }, clusterConditionCache: clusterConditionStore{}, PredicateFunc: helper.NewClusterPredicateOnAgent("test"), RateLimiterOptions: ratelimiterflag.Options{ RateLimiterBaseDelay: time.Duration(1000), RateLimiterMaxDelay: time.Duration(1000), RateLimiterQPS: 10, RateLimiterBucketSize: 10, }, ClusterClientSetFunc: clusterClientSetFuncWithError, ClusterDynamicClientSetFunc: util.NewClusterDynamicClientSetForAgent, } if err := c.Client.Create(context.Background(), cluster); err != nil { t.Fatalf("Failed to create cluster: %v", err) } res, err := c.syncClusterStatus(cluster) expect := controllerruntime.Result{} assert.Equal(t, expect, res) assert.Empty(t, err) }) t.Run("online is false, readyCondition.Status isn't true", func(t *testing.T) { server := mockServer(http.StatusNotFound, true) defer server.Close() serverAddress = server.URL cluster := &clusterv1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: clusterv1alpha1.ClusterSpec{ APIEndpoint: server.URL, SecretRef: &clusterv1alpha1.LocalSecretReference{Namespace: "ns1", Name: "secret1"}, InsecureSkipTLSVerification: true, ProxyURL: "http://1.1.1.2", }, } c := &ClusterStatusController{ Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).WithStatusSubresource(cluster).Build(), GenericInformerManager: genericmanager.GetInstance(), TypedInformerManager: typedmanager.GetInstance(), ClusterSuccessThreshold: metav1.Duration{ Duration: time.Duration(1000), }, ClusterFailureThreshold: metav1.Duration{ Duration: time.Duration(1000), }, clusterConditionCache: clusterConditionStore{}, PredicateFunc: helper.NewClusterPredicateOnAgent("test"), RateLimiterOptions: ratelimiterflag.Options{ RateLimiterBaseDelay: time.Duration(1000), RateLimiterMaxDelay: time.Duration(1000), RateLimiterQPS: 10, RateLimiterBucketSize: 10, }, ClusterClientSetFunc: clusterClientSetFunc, ClusterDynamicClientSetFunc: util.NewClusterDynamicClientSetForAgent, } if err := c.Client.Create(context.Background(), cluster); err != nil { t.Fatalf("Failed to create cluster: %v", err) } res, err := c.syncClusterStatus(cluster) expect := controllerruntime.Result{} assert.Equal(t, expect, res) assert.Empty(t, err) }) t.Run("online and healthy is true", func(t *testing.T) { server := mockServer(http.StatusOK, false) defer server.Close() serverAddress = server.URL cluster := &clusterv1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: clusterv1alpha1.ClusterSpec{ APIEndpoint: server.URL, SecretRef: &clusterv1alpha1.LocalSecretReference{Namespace: "ns1", Name: "secret1"}, InsecureSkipTLSVerification: true, ProxyURL: "http://1.1.1.1", }, } c := &ClusterStatusController{ Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).WithStatusSubresource(cluster).Build(), GenericInformerManager: genericmanager.GetInstance(), TypedInformerManager: typedmanager.GetInstance(), ClusterSuccessThreshold: metav1.Duration{ Duration: time.Duration(1000), }, ClusterFailureThreshold: metav1.Duration{ Duration: time.Duration(1000), }, clusterConditionCache: clusterConditionStore{}, PredicateFunc: helper.NewClusterPredicateOnAgent("test"), RateLimiterOptions: ratelimiterflag.Options{ RateLimiterBaseDelay: time.Duration(1000), RateLimiterMaxDelay: time.Duration(1000), RateLimiterQPS: 10, RateLimiterBucketSize: 10, }, ClusterClientSetFunc: clusterClientSetFunc, ClusterDynamicClientSetFunc: util.NewClusterDynamicClientSetForAgent, } if err := c.Client.Create(context.Background(), cluster); err != nil { t.Fatalf("Failed to create cluster: %v", err) } res, err := c.syncClusterStatus(cluster) expect := controllerruntime.Result{} assert.Equal(t, expect, res) assert.Empty(t, err) }) } func TestGetNodeSummary(t *testing.T) { nodes := []*corev1.Node{ { Status: corev1.NodeStatus{ Conditions: []corev1.NodeCondition{ { Type: corev1.NodeReady, Status: corev1.ConditionTrue, }, }, }, }, { Status: corev1.NodeStatus{ Conditions: []corev1.NodeCondition{ { Type: corev1.NodeMemoryPressure, }, }, }, }, } res := getNodeSummary(nodes) if res.TotalNum != 2 { t.Errorf("TotalNum is %v, expect 2", res.TotalNum) } if res.ReadyNum != 1 { t.Errorf("ReadyNum is %v, expect 1", res.ReadyNum) } } func TestGenerateReadyCondition(t *testing.T) { online := true healthy := true con := generateReadyCondition(online, healthy) expect := util.NewCondition(clusterv1alpha1.ClusterConditionReady, clusterReady, clusterHealthy, metav1.ConditionTrue) assert.Equal(t, expect, con) healthy = false con = generateReadyCondition(online, healthy) expect = util.NewCondition(clusterv1alpha1.ClusterConditionReady, clusterNotReady, clusterUnhealthy, metav1.ConditionFalse) assert.Equal(t, expect, con) online = false con = generateReadyCondition(online, healthy) expect = util.NewCondition(clusterv1alpha1.ClusterConditionReady, clusterNotReachableReason, clusterNotReachableMsg, metav1.ConditionFalse) assert.Equal(t, expect, con) } func TransformFunc(interface{}) (interface{}, error) { return nil, nil } func TestListPods(t *testing.T) { stopCh := make(<-chan struct{}) clientset := kubernetesfake.NewSimpleClientset() transformFuncs := map[schema.GroupVersionResource]cache.TransformFunc{ {}: TransformFunc, } m := typedmanager.NewSingleClusterInformerManager(clientset, 0, stopCh, transformFuncs) pods, err := listPods(m) assert.Equal(t, []*corev1.Pod(nil), pods) assert.Empty(t, err, "listPods returns error") } func TestListNodes(t *testing.T) { stopCh := make(<-chan struct{}) clientset := kubernetesfake.NewSimpleClientset() transformFuncs := map[schema.GroupVersionResource]cache.TransformFunc{ {}: TransformFunc, } m := typedmanager.NewSingleClusterInformerManager(clientset, 0, stopCh, transformFuncs) nodes, err := listNodes(m) assert.Equal(t, []*corev1.Node(nil), nodes) assert.Empty(t, err, "listNodes returns error") } func TestGetResourceSummary(t *testing.T) { nodes := []*corev1.Node{ { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, } pods := []*corev1.Pod{ { Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ "test": resource.Quantity{}, }}}}, }}} resourceSummary := getResourceSummary(nodes, pods) if _, ok := resourceSummary.Allocatable["test"]; !ok { t.Errorf("key test isn't in resourceSummary.Allocatable") } if _, ok := resourceSummary.Allocating["pods"]; !ok { t.Errorf("key pods isn't in resourceSummary.Allocating") } if _, ok := resourceSummary.Allocated["test"]; ok { t.Errorf("key test is in resourceSummary.Allocated") } if len(resourceSummary.AllocatableModelings) != 0 { t.Errorf("resourceSummary.AllocatableModelings isn't 0") } } func TestGetClusterAllocatable(t *testing.T) { nodes := []*corev1.Node{ { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, } expect := make(corev1.ResourceList) expect["test"] = resource.Quantity{} actual := getClusterAllocatable(nodes) assert.Equal(t, expect, actual) } func TestGetAllocatingResource(t *testing.T) { podList := []*corev1.Pod{ { Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ "test": resource.Quantity{}, }}}}, }}, } actual := getAllocatingResource(podList) assert.NotEmpty(t, actual["pods"]) } func TestGetAllocatedResource(t *testing.T) { podList := []*corev1.Pod{ { Status: corev1.PodStatus{ Phase: corev1.PodPending, }, Spec: corev1.PodSpec{ NodeName: "node1", Containers: []corev1.Container{ { Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ "test": resource.Quantity{}, }}}}, }}, } actual := getAllocatedResource(podList) assert.NotEmpty(t, actual["pods"]) } func TestGetNodeAvailable(t *testing.T) { tests := []struct { name string allocatable corev1.ResourceList podResources *util.Resource expect corev1.ResourceList }{ { name: "resource isn't enough", allocatable: corev1.ResourceList{ corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), }, podResources: &util.Resource{ AllowedPodNumber: 11, }, expect: corev1.ResourceList(nil), }, { name: "resource is enough", allocatable: corev1.ResourceList{ corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), }, podResources: &util.Resource{ AllowedPodNumber: 9, }, expect: corev1.ResourceList{ corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), }, }, { name: "podResources is nil", allocatable: corev1.ResourceList{ corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), }, podResources: nil, expect: corev1.ResourceList{ corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), }, }, { name: "allocatedResourceList is nil", allocatable: corev1.ResourceList{ corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), }, podResources: &util.Resource{ AllowedPodNumber: 0, }, expect: corev1.ResourceList{ corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { actual := getNodeAvailable(tt.allocatable, tt.podResources) assert.Equal(t, tt.expect, actual) }) } } func TestGetAllocatableModelings(t *testing.T) { tests := []struct { name string pods []*corev1.Pod nodes []*corev1.Node cluster *clusterv1alpha1.Cluster expect []clusterv1alpha1.AllocatableModeling }{ { name: "suc to get AllocatableModeling", pods: []*corev1.Pod{ { Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ "test": resource.Quantity{}, }}}}, }}, }, nodes: []*corev1.Node{ { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, }, cluster: &clusterv1alpha1.Cluster{ Spec: clusterv1alpha1.ClusterSpec{ ResourceModels: []clusterv1alpha1.ResourceModel{ { Grade: 0, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1024, resource.DecimalSI), }, }, }, { Grade: 1, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(1, resource.DecimalSI), Max: *resource.NewQuantity(2, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(1024, resource.DecimalSI), Max: *resource.NewQuantity(1024*2, resource.DecimalSI), }, }, }, }, }, }, expect: []clusterv1alpha1.AllocatableModeling{ { Grade: 0, Count: 2, }, { Grade: 1, Count: 0, }, }, }, { name: "cluster.Spec.ResourceModels is empty", pods: []*corev1.Pod{ { Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ "test": resource.Quantity{}, }}}}, }}, }, nodes: []*corev1.Node{ { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, }, cluster: &clusterv1alpha1.Cluster{ Spec: clusterv1alpha1.ClusterSpec{ ResourceModels: []clusterv1alpha1.ResourceModel{}, }, }, expect: nil, }, { name: "InitSummary occurs error", pods: []*corev1.Pod{ { Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ "test": resource.Quantity{}, }}}}, }}, }, nodes: []*corev1.Node{ { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, }, cluster: &clusterv1alpha1.Cluster{ Spec: clusterv1alpha1.ClusterSpec{ ResourceModels: []clusterv1alpha1.ResourceModel{ { Grade: 0, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1024, resource.DecimalSI), }, }, }, { Grade: 1, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(1, resource.DecimalSI), Max: *resource.NewQuantity(2, resource.DecimalSI), }, }, }, }, }, }, expect: nil, }, { name: "pod.Spec.NodeName isn't 0", pods: []*corev1.Pod{ { Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ "test": resource.Quantity{}, }}}}, NodeName: "node1", }, Status: corev1.PodStatus{ Phase: corev1.PodRunning, }, }, }, nodes: []*corev1.Node{ { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, { Status: corev1.NodeStatus{ Allocatable: corev1.ResourceList{ "test": resource.Quantity{}, }, }, }, }, cluster: &clusterv1alpha1.Cluster{ Spec: clusterv1alpha1.ClusterSpec{ ResourceModels: []clusterv1alpha1.ResourceModel{ { Grade: 0, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1024, resource.DecimalSI), }, }, }, { Grade: 1, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(1, resource.DecimalSI), Max: *resource.NewQuantity(2, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(1024, resource.DecimalSI), Max: *resource.NewQuantity(1024*2, resource.DecimalSI), }, }, }, }, }, }, expect: []clusterv1alpha1.AllocatableModeling{ { Grade: 0, Count: 2, }, { Grade: 1, Count: 0, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { actual := getAllocatableModelings(tt.cluster, tt.nodes, tt.pods) assert.Equal(t, tt.expect, actual) }) } } func TestClusterStatusController_updateStatusIfNeeded(t *testing.T) { t.Run("cluster is in client", func(t *testing.T) { cluster := &clusterv1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster1", Namespace: "karmada", }, Status: clusterv1alpha1.ClusterStatus{ KubernetesVersion: "v1", }, Spec: clusterv1alpha1.ClusterSpec{ ResourceModels: []clusterv1alpha1.ResourceModel{ { Grade: 0, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1024, resource.DecimalSI), }, }, }, { Grade: 1, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(1, resource.DecimalSI), Max: *resource.NewQuantity(2, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(1024, resource.DecimalSI), Max: *resource.NewQuantity(1024*2, resource.DecimalSI), }, }, }, }, }, } currentClusterStatus := clusterv1alpha1.ClusterStatus{ KubernetesVersion: "v2", } c := &ClusterStatusController{ Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).WithObjects( cluster, ).WithStatusSubresource(cluster).Build(), GenericInformerManager: genericmanager.GetInstance(), TypedInformerManager: typedmanager.GetInstance(), ClusterClientOption: &util.ClientOption{ QPS: 5, Burst: 10, }, ClusterClientSetFunc: util.NewClusterClientSet, } actual, err := c.updateStatusIfNeeded(cluster, currentClusterStatus) assert.Equal(t, controllerruntime.Result{}, actual) assert.Empty(t, err, "updateStatusIfNeeded returns error") }) t.Run("cluster isn't in client", func(t *testing.T) { cluster := &clusterv1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster1", Namespace: "karmada", }, Status: clusterv1alpha1.ClusterStatus{ KubernetesVersion: "v1", }, Spec: clusterv1alpha1.ClusterSpec{ ResourceModels: []clusterv1alpha1.ResourceModel{ { Grade: 0, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1024, resource.DecimalSI), }, }, }, { Grade: 1, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(1, resource.DecimalSI), Max: *resource.NewQuantity(2, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(1024, resource.DecimalSI), Max: *resource.NewQuantity(1024*2, resource.DecimalSI), }, }, }, }, }, } currentClusterStatus := clusterv1alpha1.ClusterStatus{ KubernetesVersion: "v2", } c := &ClusterStatusController{ Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).Build(), GenericInformerManager: genericmanager.GetInstance(), TypedInformerManager: typedmanager.GetInstance(), ClusterClientOption: &util.ClientOption{ QPS: 5, Burst: 10, }, ClusterClientSetFunc: util.NewClusterClientSet, } actual, err := c.updateStatusIfNeeded(cluster, currentClusterStatus) expect := controllerruntime.Result{Requeue: true} assert.Equal(t, expect, actual) assert.NotEmpty(t, err, "updateStatusIfNeeded doesn't return error") }) } func NewClusterDynamicClientSetForAgentWithError(clusterName string, client client.Client) (*util.DynamicClusterClient, error) { return nil, fmt.Errorf("err") } func TestClusterStatusController_initializeGenericInformerManagerForCluster(t *testing.T) { t.Run("failed to create dynamicClient", func(t *testing.T) { c := &ClusterStatusController{ Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).Build(), GenericInformerManager: genericmanager.GetInstance(), TypedInformerManager: typedmanager.GetInstance(), ClusterClientOption: &util.ClientOption{ QPS: 5, Burst: 10, }, ClusterClientSetFunc: util.NewClusterClientSet, ClusterDynamicClientSetFunc: NewClusterDynamicClientSetForAgentWithError, } clusterClientSet := &util.ClusterClient{ ClusterName: "test", } c.initializeGenericInformerManagerForCluster(clusterClientSet) }) t.Run("suc to create dynamicClient", func(t *testing.T) { c := &ClusterStatusController{ Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).Build(), GenericInformerManager: genericmanager.GetInstance(), TypedInformerManager: typedmanager.GetInstance(), ClusterClientOption: &util.ClientOption{ QPS: 5, Burst: 10, }, ClusterClientSetFunc: util.NewClusterClientSet, ClusterDynamicClientSetFunc: util.NewClusterDynamicClientSetForAgent, } clusterClientSet := &util.ClusterClient{ ClusterName: "test", } c.initializeGenericInformerManagerForCluster(clusterClientSet) }) } func TestClusterStatusController_initLeaseController(t *testing.T) { c := &ClusterStatusController{ Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).Build(), GenericInformerManager: genericmanager.GetInstance(), TypedInformerManager: typedmanager.GetInstance(), ClusterClientOption: &util.ClientOption{ QPS: 5, Burst: 10, }, ClusterClientSetFunc: util.NewClusterClientSet, ClusterDynamicClientSetFunc: NewClusterDynamicClientSetForAgentWithError, } cluster := &clusterv1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster1", Namespace: "karmada", }, Status: clusterv1alpha1.ClusterStatus{ KubernetesVersion: "v1", }, Spec: clusterv1alpha1.ClusterSpec{ ResourceModels: []clusterv1alpha1.ResourceModel{ { Grade: 0, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(0, resource.DecimalSI), Max: *resource.NewQuantity(1024, resource.DecimalSI), }, }, }, { Grade: 1, Ranges: []clusterv1alpha1.ResourceModelRange{ { Name: clusterv1alpha1.ResourceCPU, Min: *resource.NewMilliQuantity(1, resource.DecimalSI), Max: *resource.NewQuantity(2, resource.DecimalSI), }, { Name: clusterv1alpha1.ResourceMemory, Min: *resource.NewMilliQuantity(1024, resource.DecimalSI), Max: *resource.NewQuantity(1024*2, resource.DecimalSI), }, }, }, }, }, } c.initLeaseController(cluster) } func mockServer(statusCode int, existError bool) *httptest.Server { respBody := "test" resp := &http.Response{ StatusCode: statusCode, Body: io.NopCloser(bytes.NewBufferString(respBody)), } // Create an HTTP test server to handle the request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Write the response to the client if existError { statusCode := statusCode errorMessage := "An error occurred" http.Error(w, errorMessage, statusCode) } else { w.WriteHeader(resp.StatusCode) _, err := io.Copy(w, resp.Body) if err != nil { fmt.Printf("failed to copy, err: %v", err) } } })) return server } func TestHealthEndpointCheck(t *testing.T) { server := mockServer(http.StatusOK, false) defer server.Close() clusterClient := generateClusterClient(server.URL) actual, err := healthEndpointCheck(clusterClient.KubeClient, "/readyz") assert.Equal(t, http.StatusOK, actual) assert.Empty(t, err) } func TestGetClusterHealthStatus(t *testing.T) { t.Run("healthz return error and StatusNotFound", func(t *testing.T) { server := mockServer(http.StatusNotFound, true) defer server.Close() clusterClient := generateClusterClient(server.URL) online, healthy := getClusterHealthStatus(clusterClient) assert.Equal(t, false, online) assert.Equal(t, false, healthy) }) t.Run("healthz return http.StatusOK", func(t *testing.T) { server := mockServer(http.StatusOK, false) defer server.Close() clusterClient := generateClusterClient(server.URL) online, healthy := getClusterHealthStatus(clusterClient) assert.Equal(t, true, online) assert.Equal(t, true, healthy) }) t.Run("healthz return http.StatusAccepted", func(t *testing.T) { server := mockServer(http.StatusAccepted, false) defer server.Close() clusterClient := generateClusterClient(server.URL) online, healthy := getClusterHealthStatus(clusterClient) assert.Equal(t, true, online) assert.Equal(t, false, healthy) }) }