/* Copyright 2024 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 detector import ( "fmt" "testing" "time" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" dynamicfake "k8s.io/client-go/dynamic/fake" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1" "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2" "github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager" ) type MockAsyncWorker struct { queue []interface{} } // Note: This is a dummy implementation of Add for testing purposes. func (m *MockAsyncWorker) Add(item interface{}) { // No actual work is done in the mock; we just simulate running m.queue = append(m.queue, item) } // Note: This is a dummy implementation of AddAfter for testing purposes. func (m *MockAsyncWorker) AddAfter(item interface{}, duration time.Duration) { // No actual work is done in the mock; we just simulate running fmt.Printf("%v", duration) m.queue = append(m.queue, item) } // Note: This is a dummy implementation of Enqueue for testing purposes. func (m *MockAsyncWorker) Enqueue(obj interface{}) { // Assuming KeyFunc is used to generate a key; for simplicity, we use obj directly m.queue = append(m.queue, obj) } // Note: This is a dummy implementation of Run for testing purposes. func (m *MockAsyncWorker) Run(workerNumber int, stopChan <-chan struct{}) { // No actual work is done in the mock; we just simulate running fmt.Printf("%v", workerNumber) fmt.Printf("%v", <-stopChan) } // GetQueue returns the current state of the queue func (m *MockAsyncWorker) GetQueue() []interface{} { return m.queue } func TestHandleDeprioritizedPropagationPolicy(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) utilruntime.Must(v1alpha2.Install(scheme)) utilruntime.Must(policyv1alpha1.Install(scheme)) propagationPolicyGVR := schema.GroupVersionResource{ Group: policyv1alpha1.GroupVersion.Group, Version: policyv1alpha1.GroupVersion.Version, Resource: policyv1alpha1.ResourcePluralPropagationPolicy, } tests := []struct { name string newPolicy *policyv1alpha1.PropagationPolicy oldPolicy *policyv1alpha1.PropagationPolicy objects []runtime.Object setupClient func() *fake.ClientBuilder wantQueueSize int }{ { name: "preempt deprioritized propagation policy of len 1", newPolicy: &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](2), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, oldPolicy: &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](4), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, objects: []runtime.Object{ &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "test", Labels: map[string]string{ policyv1alpha1.PropagationPolicyPermanentIDLabel: "policy-1", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](3), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "default", }, }, }, }, }, setupClient: func() *fake.ClientBuilder { obj := &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "test", Labels: map[string]string{ policyv1alpha1.PropagationPolicyPermanentIDLabel: "policy-1", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](3), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "default", }, }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj) }, wantQueueSize: 1, }, { name: "preempt deprioritized propagation policy of len 2", newPolicy: &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](2), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, oldPolicy: &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](5), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, objects: []runtime.Object{ &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "test", Labels: map[string]string{ policyv1alpha1.PropagationPolicyPermanentIDLabel: "policy-1", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](3), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "default", }, }, }, }, &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-2", Namespace: "test", Labels: map[string]string{ policyv1alpha1.PropagationPolicyPermanentIDLabel: "policy-2", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](4), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test-2", Name: "default-2", }, }, }, }, }, setupClient: func() *fake.ClientBuilder { obj := []client.Object{ &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "test", Labels: map[string]string{ policyv1alpha1.PropagationPolicyPermanentIDLabel: "policy-1", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](3), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "default", }, }, }, }, &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-2", Namespace: "test", Labels: map[string]string{ policyv1alpha1.PropagationPolicyPermanentIDLabel: "policy-2", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](4), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test-2", Name: "default-2", }, }, }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj...) }, wantQueueSize: 2, }, { name: "no policy to preempt", newPolicy: &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](2), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, oldPolicy: &policyv1alpha1.PropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](4), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, objects: nil, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder().WithScheme(scheme) }, wantQueueSize: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fakeClient := tt.setupClient().Build() stopCh := make(chan struct{}) defer close(stopCh) fakeDynamicClient := dynamicfake.NewSimpleDynamicClient(scheme, tt.objects...) genMgr := genericmanager.NewSingleClusterInformerManager(fakeDynamicClient, 0, stopCh) resourceDetector := &ResourceDetector{ Client: fakeClient, DynamicClient: fakeDynamicClient, InformerManager: genMgr, propagationPolicyLister: genMgr.Lister(propagationPolicyGVR), } mockWorker := &MockAsyncWorker{} resourceDetector.policyReconcileWorker = mockWorker resourceDetector.InformerManager.Start() resourceDetector.InformerManager.WaitForCacheSync() resourceDetector.HandleDeprioritizedPropagationPolicy(*tt.oldPolicy, *tt.newPolicy) gotQueueSize := len(mockWorker.GetQueue()) if gotQueueSize != tt.wantQueueSize { t.Errorf("HandleDeprioritizedPropagationPolicy() want queue size %v, got %v", tt.wantQueueSize, gotQueueSize) } }) } } func TestHandleDeprioritizedClusterPropagationPolicy(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) utilruntime.Must(v1alpha2.Install(scheme)) utilruntime.Must(policyv1alpha1.Install(scheme)) clusterPropagationPolicyGVR := schema.GroupVersionResource{ Group: policyv1alpha1.GroupVersion.Group, Version: policyv1alpha1.GroupVersion.Version, Resource: policyv1alpha1.ResourcePluralClusterPropagationPolicy, } tests := []struct { name string newPolicy *policyv1alpha1.ClusterPropagationPolicy oldPolicy *policyv1alpha1.ClusterPropagationPolicy objects []runtime.Object setupClient func() *fake.ClientBuilder wantQueueSize int }{ { name: "preempt deprioritized cluster propagation policy of len 1", newPolicy: &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](2), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, oldPolicy: &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](4), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, objects: []runtime.Object{ &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "bar", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "policy-1", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](3), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "default", }, }, }, }, }, setupClient: func() *fake.ClientBuilder { obj := &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "bar", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "policy-1", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](3), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "default", }, }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj) }, wantQueueSize: 1, }, { name: "preempt deprioritized cluster propagation policy of len 2", newPolicy: &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](2), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, oldPolicy: &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](5), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, objects: []runtime.Object{ &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "bar", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "policy-1", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](3), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "default", }, }, }, }, &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-2", Namespace: "bar-2", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "policy-2", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](4), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test-2", Name: "default-2", }, }, }, }, }, setupClient: func() *fake.ClientBuilder { obj := []client.Object{ &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "bar", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "policy-1", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](3), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "default", }, }, }, }, &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-2", Namespace: "bar-2", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "policy-2", }, }, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](4), Preemption: policyv1alpha1.PreemptAlways, ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test-2", Name: "default-2", }, }, }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj...) }, wantQueueSize: 2, }, { name: "no cluster policy to preempt", newPolicy: &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](2), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, oldPolicy: &policyv1alpha1.ClusterPropagationPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test"}, Spec: policyv1alpha1.PropagationSpec{ Priority: ptr.To[int32](4), ResourceSelectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, }, }, }, }, objects: nil, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder().WithScheme(scheme) }, wantQueueSize: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fakeClient := tt.setupClient().Build() stopCh := make(chan struct{}) defer close(stopCh) fakeDynamicClient := dynamicfake.NewSimpleDynamicClient(scheme, tt.objects...) genMgr := genericmanager.NewSingleClusterInformerManager(fakeDynamicClient, 0, stopCh) resourceDetector := &ResourceDetector{ Client: fakeClient, DynamicClient: fakeDynamicClient, InformerManager: genMgr, clusterPropagationPolicyLister: genMgr.Lister(clusterPropagationPolicyGVR), } mockWorker := &MockAsyncWorker{} resourceDetector.clusterPolicyReconcileWorker = mockWorker resourceDetector.InformerManager.Start() resourceDetector.InformerManager.WaitForCacheSync() resourceDetector.HandleDeprioritizedClusterPropagationPolicy(*tt.oldPolicy, *tt.newPolicy) gotQueueSize := len(mockWorker.GetQueue()) if gotQueueSize != tt.wantQueueSize { t.Errorf("HandleDeprioritizedClusterPropagationPolicy() want queue size %v, got %v", tt.wantQueueSize, gotQueueSize) } }) } }